From 8e7f1fa895a05cc9092ddc59d90ff2b9cce8e8d6 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Tue, 30 Dec 2025 18:59:55 +0000 Subject: [PATCH 1/7] Python 3.14 support --- .github/workflows/tests.yaml | 12 +--- continuous_integration/environment-3.14.yaml | 55 +++++++++++++++++++ distributed/client.py | 14 ++++- distributed/cluster_dump.py | 3 +- distributed/distributed.yaml | 15 ++--- distributed/protocol/tests/test_protocol.py | 13 ++--- .../shuffle/tests/test_shuffle_plugins.py | 2 +- pyproject.toml | 1 + 8 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 continuous_integration/environment-3.14.yaml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f221cefc745..1f56dd057d6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -26,23 +26,13 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - environment: [mindeps, "3.10", "3.11", "3.12", "3.13"] + environment: [mindeps, "3.10", "3.11", "3.12", "3.13", "3.14"] label: [default] extra_packages: [null] # Cherry-pick test modules to split the overall runtime roughly in half partition: [ci1, not ci1] exclude: - # MacOS CI does not have any hosts available; run it on 3.12 only - - os: macos-latest - environment: mindeps - - os: macos-latest - environment: "3.10" - - os: macos-latest - environment: "3.11" - - os: macos-latest - environment: "3.13" - - os: windows-latest environment: mindeps diff --git a/continuous_integration/environment-3.14.yaml b/continuous_integration/environment-3.14.yaml new file mode 100644 index 00000000000..eb16f9e93e9 --- /dev/null +++ b/continuous_integration/environment-3.14.yaml @@ -0,0 +1,55 @@ +name: dask-distributed-313 +channels: + - conda-forge +dependencies: + - python=3.14 + - packaging + - pip + - asyncssh + - bokeh>3 + - click + - cloudpickle + - coverage + - dask # overridden by git tip below + - fsspec # overridden by git tip below + # - gilknocker # conda-forge package not yet available for Python 3.14 + - h5py + - ipykernel + - ipywidgets + - jinja2 + - jupyter_events<0.11 + - jupyter-server-proxy + - jupyterlab + - locket + - msgpack-python + - netcdf4 + - paramiko + - pre-commit + - prometheus_client + - psutil + - pyarrow + - pytest<8.4 # Pin due to https://github.com/pytest-dev/pytest-cov/issues/693 + - pytest-cov + - pytest-faulthandler + - pytest-repeat + - pytest-rerunfailures + - pytest-timeout + - requests + - scikit-learn + - scipy + - sortedcollections + - tblib !=3.2.0,!=3.2.1 + - toolz + - tornado + - zict # overridden by git tip below + - zstandard + # Temporary fix for https://github.com/pypa/setuptools/issues/4496 + - setuptools < 71 + # Temporary fix for https://github.com/jupyterlab/jupyterlab/issues/17012 + - httpx<0.28.0 + - pip: + - git+https://github.com/dask/dask + - git+https://github.com/dask/zict + - git+https://github.com/fsspec/filesystem_spec + - keras + - gilknocker # conda-forge package not yet available for Python 3.14 diff --git a/distributed/client.py b/distributed/client.py index 82462fb0d30..bc39b956d2c 100644 --- a/distributed/client.py +++ b/distributed/client.py @@ -3250,8 +3250,20 @@ def _get_computation_code( "|".join([f"(?:{mod})" for mod in ignore_modules]) ) if ignore_files: + # Given ignore-files = [foo], match: + # /path/to/foo + # /path/to/foo.py[c] + # /path/to/foo/bar.py[c] + # \path\to\foo + # \path\to\foo.py[c] + # \path\to\foo\bar.py[c] + # + # Do not match files that have 'foo' as a substring, + # unless the user explicitly states '.*foo.*'. + ignore_files_or = "|".join(mod for mod in ignore_files) fname_pattern = re.compile( - r".*[\\/](" + "|".join(mod for mod in ignore_files) + r")([\\/]|$)" + rf".*[\\/]({ignore_files_or})([\\/]|\.pyc?$|$)" + rf"|$" ) else: # stacklevel 0 or less - shows dask internals which likely isn't helpful diff --git a/distributed/cluster_dump.py b/distributed/cluster_dump.py index b2158e4381c..e0ccd45dee4 100644 --- a/distributed/cluster_dump.py +++ b/distributed/cluster_dump.py @@ -304,7 +304,8 @@ def to_yamls( import yaml root_dir = Path(root_dir) if root_dir else Path.cwd() - dumper = yaml.CSafeDumper + # libyaml C bindings may be missing + dumper = getattr(yaml, "CSafeDumper", yaml.SafeDumper) scheduler_expand_keys = set(scheduler_expand_keys) worker_expand_keys = set(worker_expand_keys) diff --git a/distributed/distributed.yaml b/distributed/distributed.yaml index 25033eaa5fa..cdede887c3d 100644 --- a/distributed/distributed.yaml +++ b/distributed/distributed.yaml @@ -289,14 +289,15 @@ distributed: - __channelexec__ # more xdist - execnet # more xdist ignore-files: - - runpy\.py # `python -m pytest` (or other module) shell command - - pytest # `pytest` shell command - - py\.test # `py.test` shell command - - pytest-script\.py # `pytest` shell command in Windows - - _pytest # pytest implementation + # `python -m pytest` (or other module) + # runpy.py on Python <=3.13; on >=3.14 + - runpy + # Many variations of pytest: + # pytest, py.test, pytest-script (on Windows), + # _pytest (implementation), vscode_pytest + - .*py\.?test.* - pycharm # Run pytest from PyCharm GUI - - vscode_pytest - - get_output_via_markers\.py + - get_output_via_markers erred-tasks: max-history: 100 diff --git a/distributed/protocol/tests/test_protocol.py b/distributed/protocol/tests/test_protocol.py index abe7a63e04a..6cbf0f294a5 100644 --- a/distributed/protocol/tests/test_protocol.py +++ b/distributed/protocol/tests/test_protocol.py @@ -162,16 +162,15 @@ def test_sizeof_serialize(Wrapper, Wrapped): @pytest.mark.skipif(WINDOWS, reason="On windows this is triggering a stackoverflow") def test_deeply_nested_structures(): # These kind of deeply nested structures are generated in our profiling code - def gen_deeply_nested(depth, msg=None): - d = msg or {} - while depth: - depth -= 1 + def gen_deeply_nested(depth): + d = {} + for _ in range(depth): d = {"children": d} return d - msg = {} - for _ in range(10): - msg = gen_deeply_nested(sys.getrecursionlimit() // 2, msg=msg) + # Note: Python <=3.13 already fails with 2x the recursion limit; + # 3.14 keeps working until 14x for some reason + msg = gen_deeply_nested(sys.getrecursionlimit() * 14) with pytest.raises(RecursionError): copy.deepcopy(msg) diff --git a/distributed/shuffle/tests/test_shuffle_plugins.py b/distributed/shuffle/tests/test_shuffle_plugins.py index b8465b46a44..ff23685519b 100644 --- a/distributed/shuffle/tests/test_shuffle_plugins.py +++ b/distributed/shuffle/tests/test_shuffle_plugins.py @@ -1,11 +1,11 @@ from __future__ import annotations -from asyncio import iscoroutinefunction import pytest from distributed.shuffle._scheduler_plugin import ShuffleSchedulerPlugin from distributed.shuffle._worker_plugin import ShuffleWorkerPlugin +from distributed.utils import iscoroutinefunction from distributed.utils_test import gen_cluster pd = pytest.importorskip("pandas") diff --git a/pyproject.toml b/pyproject.toml index 8179887aaee..eb530b9e6a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Scientific/Engineering", "Topic :: System :: Distributed Computing", ] From 58f8bbcb8552675920f3d6bae35d3c7529a84d7c Mon Sep 17 00:00:00 2001 From: crusaderky Date: Wed, 31 Dec 2025 13:14:08 +0000 Subject: [PATCH 2/7] lint --- distributed/shuffle/tests/test_shuffle_plugins.py | 1 - 1 file changed, 1 deletion(-) diff --git a/distributed/shuffle/tests/test_shuffle_plugins.py b/distributed/shuffle/tests/test_shuffle_plugins.py index ff23685519b..22b65cafc9f 100644 --- a/distributed/shuffle/tests/test_shuffle_plugins.py +++ b/distributed/shuffle/tests/test_shuffle_plugins.py @@ -1,6 +1,5 @@ from __future__ import annotations - import pytest from distributed.shuffle._scheduler_plugin import ShuffleSchedulerPlugin From 517c985d2ed5c62b393e08bd3e34a80369f53035 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Wed, 31 Dec 2025 13:49:10 +0000 Subject: [PATCH 3/7] Increase nest level --- distributed/protocol/tests/test_protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distributed/protocol/tests/test_protocol.py b/distributed/protocol/tests/test_protocol.py index 6cbf0f294a5..659832c03d3 100644 --- a/distributed/protocol/tests/test_protocol.py +++ b/distributed/protocol/tests/test_protocol.py @@ -169,8 +169,8 @@ def gen_deeply_nested(depth): return d # Note: Python <=3.13 already fails with 2x the recursion limit; - # 3.14 keeps working until 14x for some reason - msg = gen_deeply_nested(sys.getrecursionlimit() * 14) + # 3.14 keeps working until much later (and the exact limit changes by platform) + msg = gen_deeply_nested(sys.getrecursionlimit() * 20) with pytest.raises(RecursionError): copy.deepcopy(msg) From d82a4d03f68147589b0b28bd51951d95d8298534 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Wed, 31 Dec 2025 14:38:29 +0000 Subject: [PATCH 4/7] Increase again --- distributed/protocol/tests/test_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distributed/protocol/tests/test_protocol.py b/distributed/protocol/tests/test_protocol.py index 659832c03d3..270ab0cb117 100644 --- a/distributed/protocol/tests/test_protocol.py +++ b/distributed/protocol/tests/test_protocol.py @@ -170,7 +170,7 @@ def gen_deeply_nested(depth): # Note: Python <=3.13 already fails with 2x the recursion limit; # 3.14 keeps working until much later (and the exact limit changes by platform) - msg = gen_deeply_nested(sys.getrecursionlimit() * 20) + msg = gen_deeply_nested(sys.getrecursionlimit() * 100) with pytest.raises(RecursionError): copy.deepcopy(msg) From 17738b995bf4aa321173a16fd759682801d448cf Mon Sep 17 00:00:00 2001 From: crusaderky Date: Wed, 31 Dec 2025 14:47:58 +0000 Subject: [PATCH 5/7] Bump env to match #9172 --- continuous_integration/environment-3.14.yaml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/continuous_integration/environment-3.14.yaml b/continuous_integration/environment-3.14.yaml index eb16f9e93e9..8cd9ea51211 100644 --- a/continuous_integration/environment-3.14.yaml +++ b/continuous_integration/environment-3.14.yaml @@ -17,7 +17,7 @@ dependencies: - ipykernel - ipywidgets - jinja2 - - jupyter_events<0.11 + - jupyter_events - jupyter-server-proxy - jupyterlab - locket @@ -28,7 +28,7 @@ dependencies: - prometheus_client - psutil - pyarrow - - pytest<8.4 # Pin due to https://github.com/pytest-dev/pytest-cov/issues/693 + - pytest - pytest-cov - pytest-faulthandler - pytest-repeat @@ -38,15 +38,11 @@ dependencies: - scikit-learn - scipy - sortedcollections - - tblib !=3.2.0,!=3.2.1 + - tblib - toolz - tornado - zict # overridden by git tip below - zstandard - # Temporary fix for https://github.com/pypa/setuptools/issues/4496 - - setuptools < 71 - # Temporary fix for https://github.com/jupyterlab/jupyterlab/issues/17012 - - httpx<0.28.0 - pip: - git+https://github.com/dask/dask - git+https://github.com/dask/zict From b59a2f7c166080411eafdde2a8d9898cbd04e82c Mon Sep 17 00:00:00 2001 From: crusaderky Date: Wed, 31 Dec 2025 15:12:33 +0000 Subject: [PATCH 6/7] Revert MacOS changes --- .github/workflows/tests.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1f56dd057d6..cd3e97260a0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,6 +33,18 @@ jobs: partition: [ci1, not ci1] exclude: + # MacOS CI does not have many hosts available; run it on 3.14 only + - os: macos-latest + environment: mindeps + - os: macos-latest + environment: "3.10" + - os: macos-latest + environment: "3.11" + - os: macos-latest + environment: "3.12" + - os: macos-latest + environment: "3.13" + - os: windows-latest environment: mindeps From 19ca8e4f329f272ba8d8f252ada15c9c8f0b16b2 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 2 Jan 2026 14:20:13 +0000 Subject: [PATCH 7/7] Use gilknocker from conda-forge --- continuous_integration/environment-3.14.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/continuous_integration/environment-3.14.yaml b/continuous_integration/environment-3.14.yaml index 8cd9ea51211..3dea58af26d 100644 --- a/continuous_integration/environment-3.14.yaml +++ b/continuous_integration/environment-3.14.yaml @@ -12,7 +12,7 @@ dependencies: - coverage - dask # overridden by git tip below - fsspec # overridden by git tip below - # - gilknocker # conda-forge package not yet available for Python 3.14 + - gilknocker - h5py - ipykernel - ipywidgets @@ -48,4 +48,3 @@ dependencies: - git+https://github.com/dask/zict - git+https://github.com/fsspec/filesystem_spec - keras - - gilknocker # conda-forge package not yet available for Python 3.14