From 31e241e5c41e2b1b4dfd032a0148f1a1c2970ad9 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 4 Nov 2026 10:36:06 +0100 Subject: [PATCH 1/4] Skip new failing test on PyPy This is probably due to a new CFFI or new pyzmq. ANyway I don't think we should still support pypy (and Python 3.11 now that 3.15 is out). So skipping the test for now and will disable 3.11 totally in a subsequent PR. --- tests/test_subshells.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_subshells.py b/tests/test_subshells.py index 6dcfaec35..b5d9c2c56 100644 --- a/tests/test_subshells.py +++ b/tests/test_subshells.py @@ -5,6 +5,7 @@ from __future__ import annotations import platform +import sys import time from collections import Counter from queue import Empty @@ -189,6 +190,10 @@ def on_reply(message): @pytest.mark.parametrize("are_subshells", [(False, True), (True, False), (True, True)]) @pytest.mark.parametrize("overlap", [True, False]) +@pytest.mark.skipif( + "__pypy__" in sys.builtin_module_names, + reason="fails on pypy", +) def test_run_concurrently_sequence(are_subshells, overlap, request): if request.config.getvalue("--cov"): pytest.skip("Skip time-sensitive subshell tests if measuring coverage") From 3a59ff04da6040fe8a99d141da22f2281179dd77 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 4 Nov 2026 10:44:28 +0100 Subject: [PATCH 2/4] Remove Testing on Pypy This take valuable ci time and regularly break. I'm not sure we have amny users anyway. I would prefer to focus on 3.15 --- .github/workflows/ci.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index febb5cca9..c8f8a45d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,6 @@ jobs: - "3.14" # 3.14t needs a jupyter-core release # - "3.14t" - - "pypy-3.11" exclude: # qt6 not supported on 3.14 yet - python-version: "3.14" @@ -69,19 +68,13 @@ jobs: - name: Run the tests timeout-minutes: 15 - if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} + if: ${{ !startsWith(matrix.os, 'windows') }} run: | hatch run cov:test --cov-fail-under 50 - - name: Run the tests on pypy - timeout-minutes: 15 - if: ${{ startsWith( matrix.python-version, 'pypy' ) }} - run: | - hatch run test:nowarn --ignore=tests/test_debugger.py - - name: Run the tests on Windows timeout-minutes: 15 - if: ${{ !startsWith( matrix.python-version, 'pypy' ) && startsWith(matrix.os, 'windows') }} + if: ${{ startsWith(matrix.os, 'windows') }} run: | hatch run cov:nowarn From 5df7439e65014a4fa62949de2571c830e99b61eb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 4 Nov 2026 10:46:09 +0100 Subject: [PATCH 3/4] start testing on 3.15 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8f8a45d1..2e3b962c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,11 @@ jobs: - "3.14" # 3.14t needs a jupyter-core release # - "3.14t" + - "3.15" exclude: # qt6 not supported on 3.14 yet + - python-version: "3.15" + qt: qt6 - python-version: "3.14" qt: qt6 - python-version: "3.13" From b00ec2195ec4cf11f218df0e633188c4bc11be52 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 4 Nov 2026 10:51:51 +0100 Subject: [PATCH 4/4] Drop Python 3.11 ass 3.15 Remove from CI, cleanup a few places in code that are now dead. In particulr the asyncio patch is always dead code. And I hoist a local function that does not capture any variable and was doing version conditional work. --- .github/workflows/ci.yml | 7 ++--- examples/embedding/inprocess_terminal.py | 33 ------------------------ pyproject.toml | 2 +- tests/test_kernel.py | 22 +++++++--------- 4 files changed, 12 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e3b962c3..499d8c000 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,6 @@ jobs: - qt5 - qt6 python-version: - - "3.11" - "3.12" - "3.13" - "3.14" @@ -46,8 +45,6 @@ jobs: qt: qt5 - python-version: "3.12" qt: qt5 - - python-version: "3.11" - qt: qt5 steps: - name: Checkout @@ -161,7 +158,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.11"] + python-version: ["3.12"] steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -195,7 +192,7 @@ jobs: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: - python-version: "3.11" + python-version: "3.12" - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@925e40fa6066f4533e0c40803bd4b6d0d2f4a781 # v1.1.0 diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 221a0c473..6ba94a17b 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -1,9 +1,7 @@ """An in-process terminal example.""" import os -import sys -import tornado from jupyter_console.ptshell import ZMQTerminalInteractiveShell from ipykernel.inprocess.manager import InProcessKernelManager @@ -14,36 +12,6 @@ def print_process_id(): print("Process ID is:", os.getpid()) -def init_asyncio_patch(): - """set default asyncio policy to be compatible with tornado - Tornado 6 (at least) is not compatible with the default - asyncio implementation on Windows - Pick the older SelectorEventLoopPolicy on Windows - if the known-incompatible default policy is in use. - do this as early as possible to make it a low priority and overridable - ref: https://github.com/tornadoweb/tornado/issues/2608 - FIXME: if/when tornado supports the defaults in asyncio, - remove and bump tornado requirement for py38 - """ - if ( - sys.platform.startswith("win") - and sys.version_info >= (3, 8) - and tornado.version_info < (6, 1) - ): - import asyncio - - try: - from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy - except ImportError: - pass - # not affected - else: - if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: - # WindowsProactorEventLoopPolicy is not compatible with tornado 6 - # fallback to the pre-3.8 default of Selector - asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - - def main(): """The main function.""" print_process_id() @@ -51,7 +19,6 @@ def main(): # Create an in-process kernel # >>> print_process_id() # will print the same process ID as the main process - init_asyncio_patch() kernel_manager = InProcessKernelManager() kernel_manager.start_kernel() kernel = kernel_manager.kernel diff --git a/pyproject.toml b/pyproject.toml index c65e93f2c..e1673bf21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", ] -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ "debugpy>=1.6.5", "ipython>=7.23.1", diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 270165b1a..eafc9dbc2 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -768,6 +768,13 @@ def test_control_thread_priority(): assert control_dates[-1] <= shell_dates[0] +def _ensure_datetime(arg): + # Support arg which is a datetime or str. + if isinstance(arg, str): + return datetime.fromisoformat(arg) + return arg + + def test_sequential_control_messages(): with new_kernel() as kc: msg_id = kc.execute("import time") @@ -790,22 +797,11 @@ def test_sequential_control_messages(): # Get replies replies = [get_reply(kc, msg_id, channel="control") for msg_id in msg_ids] - def ensure_datetime(arg): - # Support arg which is a datetime or str. - if isinstance(arg, str): - if sys.version_info[:2] < (3, 11) and arg.endswith("Z"): - # Python < 3.11 doesn't support "Z" suffix in datetime.fromisoformat, - # so use alternative timezone format. - # https://github.com/python/cpython/issues/80010 - arg = arg[:-1] + "+00:00" - return datetime.fromisoformat(arg) - return arg - # Check messages are processed in order, one at a time, and of a sensible duration. previous_end = None for reply, sleep in zip(replies, sleeps, strict=False): - start = ensure_datetime(reply["metadata"]["started"]) - end = ensure_datetime(reply["header"]["date"]) + start = _ensure_datetime(reply["metadata"]["started"]) + end = _ensure_datetime(reply["header"]["date"]) if previous_end is not None: assert start >= previous_end