diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2713ec11..5783e9edf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,6 @@ jobs: - qt5 - qt6 python-version: - - "3.10" - "3.11" - "3.12" - "3.13" @@ -153,7 +152,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.10"] + python-version: ["3.11"] steps: - name: Checkout uses: actions/checkout@v7 @@ -183,7 +182,7 @@ jobs: - uses: actions/setup-python@v7 with: - python-version: "3.10" + python-version: "3.11" - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a87ce7f17..2341b8f7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,7 +73,6 @@ repos: additional_dependencies: - "pyyaml" - "packaging" - - "tomli; python_version < '3.11'" - repo: https://github.com/adamchainz/blacken-docs rev: "1.20.0" diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index b74f22f8c..3f0de81f4 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -121,7 +121,7 @@ def run(self): while True: try: - zmq.device(zmq.QUEUE, self.socket, self.socket) # type:ignore[attr-defined] + zmq.device(zmq.QUEUE, self.socket, self.socket) except zmq.ZMQError as e: if e.errno == errno.EINTR: # signal interrupt, resume heartbeat diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index fed17d69d..26eefbc39 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -551,10 +551,6 @@ def schedule_next(): # begin polling the eventloop schedule_next() - async def _create_control_lock(self): - # This can be removed when minimum python increases to 3.10 - self._control_lock = asyncio.Lock() - def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() @@ -562,13 +558,7 @@ def start(self): if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) - if self.control_thread and sys.version_info < (3, 10): - # Before Python 3.10 we need to ensure the _control_lock is created in the - # thread that uses it. When our minimum python is 3.10 we can remove this - # and always use the else below, or just assign it where it is declared. - self.control_thread.io_loop.add_callback(self._create_control_lock) - else: - self._control_lock = asyncio.Lock() + self._control_lock = asyncio.Lock() if self.shell_stream: if self.shell_channel_thread: diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index bdee4b02e..aca6aaced 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -174,7 +174,7 @@ def install( python_arguments = None # addresses the debugger warning from debugpy about frozen modules - if sys.version_info >= (3, 11) and platform.python_implementation() == "CPython": + if platform.python_implementation() == "CPython": if not frozen_modules: # disable frozen modules python_arguments = ["-Xfrozen_modules=off"] diff --git a/ipykernel/utils.py b/ipykernel/utils.py index 50fbe8ba2..ab7006161 100644 --- a/ipykernel/utils.py +++ b/ipykernel/utils.py @@ -3,11 +3,10 @@ from __future__ import annotations import asyncio -import sys import typing as t from collections.abc import Mapping from contextvars import copy_context -from functools import partial, wraps +from functools import wraps if t.TYPE_CHECKING: from collections.abc import Callable @@ -46,36 +45,13 @@ def _async_in_context( ) -> Callable[..., t.Coroutine[T, U, V]]: """ Wrapper to run a coroutine in a persistent ContextVar Context. - - Backports asyncio.create_task(context=...) behavior from Python 3.11 """ if context is None: context = copy_context() - if sys.version_info >= (3, 11): - - @wraps(f) - async def run_in_context(*args, **kwargs): - coro = f(*args, **kwargs) - return await asyncio.create_task(coro, context=context) - - return run_in_context - - # don't need this backport when we require 3.11 - # context_holder so we have a modifiable container for later calls - context_holder = [context] # type: ignore[unreachable] - - async def preserve_context(f, *args, **kwargs): - """call a coroutine, preserving the context after it is called""" - try: - return await f(*args, **kwargs) - finally: - # persist changes to the context for future calls - context_holder[0] = copy_context() - @wraps(f) - async def run_in_context_pre311(*args, **kwargs): - ctx = context_holder[0] - return await ctx.run(partial(asyncio.create_task, preserve_context(f, *args, **kwargs))) + async def run_in_context(*args, **kwargs): + coro = f(*args, **kwargs) + return await asyncio.create_task(coro, context=context) - return run_in_context_pre311 + return run_in_context diff --git a/pyproject.toml b/pyproject.toml index 4075fb79f..8cbd12bbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", ] -requires-python = ">=3.10" +requires-python = ">=3.11" dependencies = [ "debugpy>=1.6.5", "ipython>=7.23.1", diff --git a/scripts/check_mypy_deps.py b/scripts/check_mypy_deps.py index 1d75a2c87..fae2fa370 100755 --- a/scripts/check_mypy_deps.py +++ b/scripts/check_mypy_deps.py @@ -8,11 +8,7 @@ from __future__ import annotations import sys - -try: - import tomllib -except ImportError: - import tomli as tomllib # type: ignore[no-reuse-def] +import tomllib import yaml from packaging.requirements import Requirement diff --git a/tests/test_eventloop.py b/tests/test_eventloop.py index c353542c2..a95d6338d 100644 --- a/tests/test_eventloop.py +++ b/tests/test_eventloop.py @@ -107,10 +107,6 @@ def test_cocoa_loop(kernel): def test_qt_enable_gui(gui, kernel, capsys): if os.getenv("GITHUB_ACTIONS", None) == "true" and gui == "qt5": pytest.skip("Qt5 and GitHub action crash CPython") - if gui == "qt6" and sys.version_info < (3, 10): - pytest.skip( - "qt6 fails on 3.9 with AttributeError: module 'PySide6.QtPrintSupport' has no attribute 'QApplication'" - ) if sys.platform == "linux" and gui == "qt6" and os.getenv("GITHUB_ACTIONS", None) == "true": pytest.skip("qt6 fails on github CI with missing libEGL.so.1") enable_gui(gui, kernel) diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index 744b77ec4..34ebeeb80 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -154,7 +154,7 @@ def test_install_env(tmp_path, env): assert "env" not in spec -@pytest.mark.skipif(sys.version_info < (3, 11) or not is_cpython, reason="requires cPython 3.11") +@pytest.mark.skipif(not is_cpython, reason="requires CPython") def test_install_frozen_modules_on(): system_jupyter_dir = tempfile.mkdtemp() @@ -168,7 +168,7 @@ def test_install_frozen_modules_on(): assert "-Xfrozen_modules=off" not in spec["argv"] -@pytest.mark.skipif(sys.version_info < (3, 11) or not is_cpython, reason="requires cPython 3.11") +@pytest.mark.skipif(not is_cpython, reason="requires CPython") def test_install_frozen_modules_off(): system_jupyter_dir = tempfile.mkdtemp() @@ -182,13 +182,9 @@ def test_install_frozen_modules_off(): assert spec["argv"][1] == "-Xfrozen_modules=off" -@pytest.mark.skipif( - sys.version_info >= (3, 11) or is_cpython, - reason="checks versions older than 3.11 and other Python implementations", -) +@pytest.mark.skipif(is_cpython, reason="checks non-CPython implementations") def test_install_frozen_modules_no_op(): - # ensure we do not add add Xfrozen_modules on older Python versions - # (although cPython does not error out on unknown X options as of 3.8) + # ensure we do not add -Xfrozen_modules on non-CPython implementations system_jupyter_dir = tempfile.mkdtemp() with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]):