From be7ed277a7d0a405d2a2ac83e52e88df03df4399 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 15:22:08 +0200 Subject: [PATCH 01/12] Modernize the package. --- .github/workflows/main.yml | 26 +++++++++----------------- .pre-commit-config.yaml | 4 ---- justfile | 26 ++++++++++++++++++++++++++ pyproject.toml | 20 +++++++++++++------- tests/test_backends.py | 4 ---- tests/test_capture.py | 1 - tests/test_config.py | 2 -- tests/test_execute.py | 14 -------------- tests/test_remote.py | 7 ------- 9 files changed, 48 insertions(+), 56 deletions(-) create mode 100644 justfile diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1611fb2..897e4fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,8 +30,11 @@ jobs: python-version-file: .python-version allow-prereleases: true cache: pip + - name: Install just + uses: extractions/setup-just@v2 - run: pip install tox-uv - - run: tox -e typing + - name: Run type checking + run: just typing run-tests: @@ -51,24 +54,13 @@ jobs: python-version: ${{ matrix.python-version }} cache: pip allow-prereleases: true + - name: Install just + uses: extractions/setup-just@v2 - run: pip install tox - # Unit, integration, and end-to-end tests. - - - name: Run unit tests and doctests. + - name: Run tests shell: bash -l {0} - run: tox -e test -- tests -m "unit or (not integration and not end_to_end)" --cov=src --cov=tests --cov-report=xml + run: just test - - name: Upload unit test coverage reports to Codecov with GitHub Action + - name: Upload test coverage reports to Codecov with GitHub Action uses: codecov/codecov-action@v5 - with: - flags: unit - - - name: Run end-to-end tests. - shell: bash -l {0} - run: tox -e test -- tests -m end_to_end --cov=src --cov=tests --cov-report=xml - - - name: Upload end_to_end test coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v5 - with: - flags: end_to_end diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d50a1ca..9143cb1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,10 +27,6 @@ repos: hooks: - id: ruff - id: ruff-format -- repo: https://github.com/dosisod/refurb - rev: v2.1.0 - hooks: - - id: refurb - repo: https://github.com/kynan/nbstripout rev: 0.8.1 hooks: diff --git a/justfile b/justfile new file mode 100644 index 0000000..a32c271 --- /dev/null +++ b/justfile @@ -0,0 +1,26 @@ +# Install all dependencies +install: + uv sync --all-groups + +# Run tests +test: + uv run --group test pytest --cov=src --cov=tests --cov-report=xml + +# Run type checking +typing: + uv run --group typing --group test ty check + +# Run linting and formatting +lint: + uvx --with pre-commit-uv pre-commit run -a + +# Run all checks (format, lint, typing, test) +check: lint typing test + +# Run tests with lowest dependency resolution +test-lowest: + uv run --group test --resolution lowest-direct pytest + +# Run tests with highest dependency resolution +test-highest: + uv run --group test --resolution highest pytest diff --git a/pyproject.toml b/pyproject.toml index 6f548f5..c1473bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ classifiers = [ requires-python = ">=3.9" dependencies = [ "attrs>=21.3.0", - "click", + "click>=8.1.8,!=8.2.0", "cloudpickle", "loky", "pluggy>=1.0.0", @@ -24,7 +24,7 @@ dynamic = ["version"] name = "Tobias Raabe" email = "raabe@posteo.de" -[project.optional-dependencies] +[dependency-groups] coiled = ["coiled>=0.9.4"] dask = ["dask[complete]", "distributed"] docs = [ @@ -40,7 +40,17 @@ docs = [ "sphinx-toolbox", "sphinxext-opengraph", ] -test = ["pytask-parallel[coiled,dask]", "nbmake", "pytest", "pytest-cov"] +test = [ + "nbmake", + "pytest>=8.4.0", + "pytest-cov>=5.0.0", + {include-group = "coiled"}, + {include-group = "dask"}, +] +typing = [ + "pytask-parallel", + "ty", +] [project.readme] file = "README.md" @@ -105,7 +115,6 @@ disallow_untyped_defs = false ignore_errors = true [tool.ruff] -target-version = "py39" fix = true unsafe-fixes = true @@ -134,9 +143,6 @@ addopts = ["--nbmake"] testpaths = ["tests"] markers = [ "wip: Tests that are work-in-progress.", - "unit: Flag for unit tests which target mainly a single function.", - "integration: Flag for integration tests which may comprise of multiple unit tests.", - "end_to_end: Flag for tests that cover the whole program.", ] norecursedirs = [".idea", ".tox"] diff --git a/tests/test_backends.py b/tests/test_backends.py index bc442fe..591375a 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -1,6 +1,5 @@ import textwrap -import pytest from pytask import ExitCode from pytask import cli @@ -8,7 +7,6 @@ from pytask_parallel import registry -@pytest.mark.end_to_end def test_error_requesting_custom_backend_without_registration(runner, tmp_path): tmp_path.joinpath("task_example.py").write_text("def task_example(): pass") result = runner.invoke(cli, [tmp_path.as_posix(), "--parallel-backend", "custom"]) @@ -16,7 +14,6 @@ def test_error_requesting_custom_backend_without_registration(runner, tmp_path): assert "No registered parallel backend found" in result.output -@pytest.mark.end_to_end def test_error_while_instantiating_custom_backend(runner, tmp_path): hook_source = """ from pytask_parallel import ParallelBackend, registry @@ -35,7 +32,6 @@ def task_example(): pass assert "Could not instantiate parallel backend 'custom'." in result.output -@pytest.mark.end_to_end def test_register_custom_backend(runner, tmp_path): source = """ from loky import get_reusable_executor diff --git a/tests/test_capture.py b/tests/test_capture.py index 2ae605c..557858f 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -7,7 +7,6 @@ from pytask_parallel import ParallelBackend -@pytest.mark.end_to_end @pytest.mark.parametrize( "parallel_backend", [ diff --git a/tests/test_config.py b/tests/test_config.py index b51bf29..1662d22 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,7 +10,6 @@ from pytask_parallel import ParallelBackend -@pytest.mark.end_to_end @pytest.mark.parametrize( ("pdb", "n_workers", "expected"), [ @@ -26,7 +25,6 @@ def test_interplay_between_debugging_and_parallel(tmp_path, pdb, n_workers, expe assert session.config["n_workers"] == expected -@pytest.mark.end_to_end @pytest.mark.parametrize( ("configuration_option", "value", "exit_code"), [ diff --git a/tests/test_execute.py b/tests/test_execute.py index bb3d4d1..5b2659a 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -25,7 +25,6 @@ ] -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_parallel_execution(tmp_path, parallel_backend): source = """ @@ -47,7 +46,6 @@ def task_2(path: Annotated[Path, Product] = Path("out_2.txt")): assert tmp_path.joinpath("out_2.txt").exists() -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_parallel_execution_w_cli(runner, tmp_path, parallel_backend): source = """ @@ -77,7 +75,6 @@ def task_2(path: Annotated[Path, Product] = Path("out_2.txt")): assert tmp_path.joinpath("out_2.txt").exists() -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_stop_execution_when_max_failures_is_reached(tmp_path, parallel_backend): source = """ @@ -105,7 +102,6 @@ def task_3(): time.sleep(3) assert len(session.execution_reports) == 2 -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_task_priorities(tmp_path, parallel_backend): source = """ @@ -146,7 +142,6 @@ def task_5(): assert last_task_name.endswith(("task_2", "task_5")) -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) @pytest.mark.parametrize("show_locals", [True, False]) def test_rendering_of_tracebacks_with_rich( @@ -172,7 +167,6 @@ def task_raising_error(): assert ("[0, 1, 2, 3, 4]" in result.output) is show_locals -@pytest.mark.end_to_end @pytest.mark.parametrize( "parallel_backend", # Capturing warnings is not thread-safe. @@ -207,7 +201,6 @@ def task_example(produces): assert "task_example.py::task_example[1]" in warnings_block -@pytest.mark.unit def test_sleeper(): sleeper = _Sleeper(timings=[1, 2, 3], timing_idx=0) @@ -229,7 +222,6 @@ def test_sleeper(): assert 1 <= end - start <= 2 -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_task_that_return(runner, tmp_path, parallel_backend): source = """ @@ -249,7 +241,6 @@ def task_example() -> Annotated[str, Path("file.txt")]: ) -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_task_without_path_that_return(runner, tmp_path, parallel_backend): source = """ @@ -270,7 +261,6 @@ def test_task_without_path_that_return(runner, tmp_path, parallel_backend): ) -@pytest.mark.end_to_end @pytest.mark.parametrize("flag", ["--pdb", "--trace", "--dry-run"]) @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_parallel_execution_is_deactivated(runner, tmp_path, flag, parallel_backend): @@ -283,7 +273,6 @@ def test_parallel_execution_is_deactivated(runner, tmp_path, flag, parallel_back assert "Started 2 workers" not in result.output -@pytest.mark.end_to_end @pytest.mark.parametrize("code", ["breakpoint()", "import pdb; pdb.set_trace()"]) @pytest.mark.parametrize( "parallel_backend", @@ -298,7 +287,6 @@ def test_raise_error_on_breakpoint(runner, tmp_path, code, parallel_backend): assert "You cannot use 'breakpoint()'" in result.output -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_task_partialed(runner, tmp_path, parallel_backend): source = """ @@ -321,7 +309,6 @@ def create_text(text): assert tmp_path.joinpath("file.txt").exists() -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_execute_tasks_and_pass_values_by_python_node_return( runner, tmp_path, parallel_backend @@ -349,7 +336,6 @@ def task_create_file( assert tmp_path.joinpath("file.txt").read_text() == "This is the text." -@pytest.mark.end_to_end @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) def test_execute_tasks_and_pass_values_by_python_node_product( runner, tmp_path, parallel_backend diff --git a/tests/test_remote.py b/tests/test_remote.py index e123466..854212e 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -24,7 +24,6 @@ def custom_builder(n_workers): tmp_path.joinpath("config.py").write_text(textwrap.dedent(source)) -@pytest.mark.end_to_end def test_python_node(runner, tmp_path): source = """ from pathlib import Path @@ -65,7 +64,6 @@ def task_third( assert tmp_path.joinpath("output.txt").read_text() == "Hello World!" -@pytest.mark.end_to_end def test_local_path_as_input(runner, tmp_path): source = """ from pathlib import Path @@ -92,7 +90,6 @@ def task_example(path: Path = Path("in.txt")) -> Annotated[str, Path("output.txt assert tmp_path.joinpath("output.txt").read_text() == "Hello World!" -@pytest.mark.end_to_end def test_local_path_as_product(runner, tmp_path): source = """ from pytask import Product @@ -119,7 +116,6 @@ def task_example(path: Annotated[Path, Product] = Path("output.txt")): assert tmp_path.joinpath("output.txt").read_text() == "Hello World!" -@pytest.mark.end_to_end def test_local_path_as_return(runner, tmp_path): source = """ from pathlib import Path @@ -145,7 +141,6 @@ def task_example() -> Annotated[str, Path("output.txt")]: assert tmp_path.joinpath("output.txt").read_text() == "Hello World!" -@pytest.mark.end_to_end def test_pickle_node_with_local_path_as_input(runner, tmp_path): source = """ from pytask import PickleNode @@ -175,7 +170,6 @@ def task_example( assert tmp_path.joinpath("output.txt").read_text() == "Hello World!" -@pytest.mark.end_to_end def test_pickle_node_with_local_path_as_product(runner, tmp_path): source = """ from pytask import PickleNode, Product @@ -204,7 +198,6 @@ def task_example( assert pickle.loads(tmp_path.joinpath("data.pkl").read_bytes()) == "Hello World!" # noqa: S301 -@pytest.mark.end_to_end def test_pickle_node_with_local_path_as_return(runner, tmp_path): source = """ from pytask import PickleNode From 487b0212829030f1ab9bf030d65be88b7908c924 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 15:25:30 +0200 Subject: [PATCH 02/12] Fix. --- .github/workflows/main.yml | 24 ++++++++---------------- justfile | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 897e4fe..db0754c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,8 +5,6 @@ concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true -env: - CONDA_EXE: mamba on: push: @@ -25,14 +23,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version-file: .python-version - allow-prereleases: true - cache: pip + - name: Install uv + uses: astral-sh/setup-uv@v3 + - name: Set up Python + run: uv python install - name: Install just uses: extractions/setup-just@v2 - - run: pip install tox-uv - name: Run type checking run: just typing @@ -49,17 +45,13 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: pip - allow-prereleases: true + - name: Install uv + uses: astral-sh/setup-uv@v3 + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} - name: Install just uses: extractions/setup-just@v2 - - run: pip install tox - - name: Run tests - shell: bash -l {0} run: just test - name: Upload test coverage reports to Codecov with GitHub Action diff --git a/justfile b/justfile index a32c271..30688d1 100644 --- a/justfile +++ b/justfile @@ -8,7 +8,7 @@ test: # Run type checking typing: - uv run --group typing --group test ty check + uv run --group typing ty check # Run linting and formatting lint: From a238202d9f60709a9ab93be4f7455d02e1c7fbc9 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 15:43:17 +0200 Subject: [PATCH 03/12] FIx. --- docs/source/conf.py | 14 +++++++------- docs_src/custom_executors.py | 2 +- justfile | 2 +- pyproject.toml | 2 ++ src/pytask_parallel/backends.py | 11 +++++++---- src/pytask_parallel/execute.py | 2 +- src/pytask_parallel/wrappers.py | 6 +++--- tests/test_config.py | 4 ++-- .../test_functional_interface.ipynb | 17 ++--------------- ...t_functional_interface_w_relative_path.ipynb | 17 ++--------------- 10 files changed, 28 insertions(+), 49 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 97a2cbb..7c4f503 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,7 @@ import pytask_parallel if TYPE_CHECKING: - import sphinx + import sphinx # ty: ignore[unresolved-import] # -- Project information --------------------------------------------------------------- @@ -30,7 +30,7 @@ # The version, including alpha/beta/rc tags, but not commit hash and datestamps release = version("pytask_parallel") # The short X.Y version. -version = ".".join(release.split(".")[:2]) +version = ".".join(release.split(".")[:2]) # ty: ignore[invalid-assignment] # -- General configuration ------------------------------------------------------------- @@ -100,7 +100,7 @@ # Linkcode, based on numpy doc/source/conf.py -def linkcode_resolve(domain: str, info: dict[str, str]) -> str: # noqa: C901 +def linkcode_resolve(domain: str, info: dict[str, str]) -> str | None: # noqa: C901 """Determine the URL corresponding to Python object.""" if domain != "py": return None @@ -123,10 +123,10 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> str: # noqa: C901 return None try: - fn = inspect.getsourcefile(inspect.unwrap(obj)) + fn = inspect.getsourcefile(inspect.unwrap(obj)) # ty: ignore[invalid-argument-type] except TypeError: try: # property - fn = inspect.getsourcefile(inspect.unwrap(obj.fget)) + fn = inspect.getsourcefile(inspect.unwrap(obj.fget)) # ty: ignore[possibly-unbound-attribute,invalid-argument-type] except (AttributeError, TypeError): fn = None if not fn: @@ -136,7 +136,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> str: # noqa: C901 source, lineno = inspect.getsourcelines(obj) except TypeError: try: # property - source, lineno = inspect.getsourcelines(obj.fget) + source, lineno = inspect.getsourcelines(obj.fget) # ty: ignore[possibly-unbound-attribute] except (AttributeError, TypeError): lineno = None except OSError: @@ -202,7 +202,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> str: # noqa: C901 } -def setup(app: sphinx.application.Sphinx) -> None: +def setup(app: sphinx.application.Sphinx) -> None: # ty: ignore[unresolved-attribute] """Configure sphinx.""" app.add_object_type( "confval", diff --git a/docs_src/custom_executors.py b/docs_src/custom_executors.py index 1d6ddba..79a1d4b 100644 --- a/docs_src/custom_executors.py +++ b/docs_src/custom_executors.py @@ -1,6 +1,6 @@ from concurrent.futures import Executor -from my_project.executor import CustomExecutor +from my_project.executor import CustomExecutor # ty: ignore[unresolved-import] from pytask_parallel import ParallelBackend from pytask_parallel import WorkerType diff --git a/justfile b/justfile index 30688d1..4ea0144 100644 --- a/justfile +++ b/justfile @@ -8,7 +8,7 @@ test: # Run type checking typing: - uv run --group typing ty check + uv run --group typing --group test --isolated ty check # Run linting and formatting lint: diff --git a/pyproject.toml b/pyproject.toml index c1473bb..1897535 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,8 @@ test = [ typing = [ "pytask-parallel", "ty", + {include-group = "coiled"}, + {include-group = "dask"}, ] [project.readme] diff --git a/src/pytask_parallel/backends.py b/src/pytask_parallel/backends.py index b889118..67f26e5 100644 --- a/src/pytask_parallel/backends.py +++ b/src/pytask_parallel/backends.py @@ -8,7 +8,7 @@ from concurrent.futures import ProcessPoolExecutor from concurrent.futures import ThreadPoolExecutor from enum import Enum -from typing import Any +from typing import TYPE_CHECKING, Any from typing import Callable from typing import ClassVar @@ -47,10 +47,13 @@ def submit( def _get_dask_executor(n_workers: int) -> Executor: """Get an executor from a dask client.""" _rich_traceback_guard = True - from pytask import import_optional_dependency # noqa: PLC0415 - distributed = import_optional_dependency("distributed") - assert distributed # noqa: S101 + try: + import distributed # noqa: PLC0415 + except ImportError: + msg = "The distributed package is not installed. Please install it." + raise ImportError(msg) from None + try: client = distributed.Client.current() except ValueError: diff --git a/src/pytask_parallel/execute.py b/src/pytask_parallel/execute.py index 84e1858..f7cea9f 100644 --- a/src/pytask_parallel/execute.py +++ b/src/pytask_parallel/execute.py @@ -209,7 +209,7 @@ def pytask_execute_task(session: Session, task: PTask) -> Future[WrapperResult]: task_module = get_module(task.function, getattr(task, "path", None)) cloudpickle.register_pickle_by_value(task_module) - return wrapper_func.submit( + return wrapper_func.submit( # ty: ignore[possibly-unbound-attribute,invalid-return-type] task=task, console_options=console.options, kwargs=kwargs, diff --git a/src/pytask_parallel/wrappers.py b/src/pytask_parallel/wrappers.py index acf6434..ad00e10 100644 --- a/src/pytask_parallel/wrappers.py +++ b/src/pytask_parallel/wrappers.py @@ -192,8 +192,8 @@ def _patch_set_trace_and_breakpoint() -> None: import pdb # noqa: PLC0415, T100 import sys # noqa: PLC0415 - pdb.set_trace = _raise_exception_on_breakpoint - sys.breakpointhook = _raise_exception_on_breakpoint + pdb.set_trace = _raise_exception_on_breakpoint # ty: ignore[invalid-assignment] + sys.breakpointhook = _raise_exception_on_breakpoint # ty: ignore[invalid-assignment] def _render_traceback_to_string( @@ -205,7 +205,7 @@ def _render_traceback_to_string( traceback = Traceback(exc_info, show_locals=show_locals) segments = console.render(traceback, options=console_options) text = "".join(segment.text for segment in segments) - return (*exc_info[:2], text) + return (*exc_info[:2], text) # ty: ignore[invalid-return-type] def _handle_function_products( diff --git a/tests/test_config.py b/tests/test_config.py index 1662d22..ebd0b61 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -17,7 +17,7 @@ (True, 1, 1), (True, 2, 2), (False, 2, 2), - (False, "auto", os.cpu_count() - 1), + (False, "auto", (os.cpu_count() or 1) - 1), ], ) def test_interplay_between_debugging_and_parallel(tmp_path, pdb, n_workers, expected): @@ -65,6 +65,6 @@ def test_reading_values_from_config_file( assert session.exit_code == exit_code if value == "auto": - value = os.cpu_count() - 1 + value = (os.cpu_count() or 1) - 1 if value != "unknown_backend": assert session.config[configuration_option] == value diff --git a/tests/test_jupyter/test_functional_interface.ipynb b/tests/test_jupyter/test_functional_interface.ipynb index 7b885cc..bc7791d 100644 --- a/tests/test_jupyter/test_functional_interface.ipynb +++ b/tests/test_jupyter/test_functional_interface.ipynb @@ -22,20 +22,7 @@ "id": "1", "metadata": {}, "outputs": [], - "source": [ - "node_text = PythonNode(name=\"text\", hash=True)\n", - "\n", - "\n", - "def create_text() -> Annotated[int, node_text]:\n", - " return \"This is the text.\"\n", - "\n", - "\n", - "node_file = PathNode.from_path(Path(\"file.txt\").resolve())\n", - "\n", - "\n", - "def create_file(text: Annotated[int, node_text]) -> Annotated[str, node_file]:\n", - " return text" - ] + "source": "node_text = PythonNode(name=\"text\", hash=True)\n\n\ndef create_text() -> Annotated[str, node_text]:\n return \"This is the text.\"\n\n\nnode_file = PathNode.from_path(Path(\"file.txt\").resolve())\n\n\ndef create_file(text: Annotated[str, node_text]) -> Annotated[str, node_file]:\n return text" }, { "cell_type": "code", @@ -70,4 +57,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb b/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb index 02b2635..28a77a5 100644 --- a/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb +++ b/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb @@ -22,20 +22,7 @@ "id": "1", "metadata": {}, "outputs": [], - "source": [ - "node_text = PythonNode(name=\"text\", hash=True)\n", - "\n", - "\n", - "def create_text() -> Annotated[int, node_text]:\n", - " return \"This is the text.\"\n", - "\n", - "\n", - "node_file = PathNode(name=\"product\", path=Path(\"file.txt\"))\n", - "\n", - "\n", - "def create_file(text: Annotated[int, node_text]) -> Annotated[str, node_file]:\n", - " return text" - ] + "source": "node_text = PythonNode(name=\"text\", hash=True)\n\n\ndef create_text() -> Annotated[str, node_text]:\n return \"This is the text.\"\n\n\nnode_file = PathNode(name=\"product\", path=Path(\"file.txt\"))\n\n\ndef create_file(text: Annotated[str, node_text]) -> Annotated[str, node_file]:\n return text" }, { "cell_type": "code", @@ -70,4 +57,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From 6d5da401389d5cb21b338a5c1df693c92448953d Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 15:43:38 +0200 Subject: [PATCH 04/12] fix. --- src/pytask_parallel/backends.py | 2 +- .../test_functional_interface.ipynb | 17 +++++++++++++++-- ...t_functional_interface_w_relative_path.ipynb | 17 +++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/pytask_parallel/backends.py b/src/pytask_parallel/backends.py index 67f26e5..06a3f5e 100644 --- a/src/pytask_parallel/backends.py +++ b/src/pytask_parallel/backends.py @@ -8,7 +8,7 @@ from concurrent.futures import ProcessPoolExecutor from concurrent.futures import ThreadPoolExecutor from enum import Enum -from typing import TYPE_CHECKING, Any +from typing import Any from typing import Callable from typing import ClassVar diff --git a/tests/test_jupyter/test_functional_interface.ipynb b/tests/test_jupyter/test_functional_interface.ipynb index bc7791d..a4506c7 100644 --- a/tests/test_jupyter/test_functional_interface.ipynb +++ b/tests/test_jupyter/test_functional_interface.ipynb @@ -22,7 +22,20 @@ "id": "1", "metadata": {}, "outputs": [], - "source": "node_text = PythonNode(name=\"text\", hash=True)\n\n\ndef create_text() -> Annotated[str, node_text]:\n return \"This is the text.\"\n\n\nnode_file = PathNode.from_path(Path(\"file.txt\").resolve())\n\n\ndef create_file(text: Annotated[str, node_text]) -> Annotated[str, node_file]:\n return text" + "source": [ + "node_text = PythonNode(name=\"text\", hash=True)\n", + "\n", + "\n", + "def create_text() -> Annotated[str, node_text]:\n", + " return \"This is the text.\"\n", + "\n", + "\n", + "node_file = PathNode.from_path(Path(\"file.txt\").resolve())\n", + "\n", + "\n", + "def create_file(text: Annotated[str, node_text]) -> Annotated[str, node_file]:\n", + " return text" + ] }, { "cell_type": "code", @@ -57,4 +70,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb b/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb index 28a77a5..a09b40d 100644 --- a/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb +++ b/tests/test_jupyter/test_functional_interface_w_relative_path.ipynb @@ -22,7 +22,20 @@ "id": "1", "metadata": {}, "outputs": [], - "source": "node_text = PythonNode(name=\"text\", hash=True)\n\n\ndef create_text() -> Annotated[str, node_text]:\n return \"This is the text.\"\n\n\nnode_file = PathNode(name=\"product\", path=Path(\"file.txt\"))\n\n\ndef create_file(text: Annotated[str, node_text]) -> Annotated[str, node_file]:\n return text" + "source": [ + "node_text = PythonNode(name=\"text\", hash=True)\n", + "\n", + "\n", + "def create_text() -> Annotated[str, node_text]:\n", + " return \"This is the text.\"\n", + "\n", + "\n", + "node_file = PathNode(name=\"product\", path=Path(\"file.txt\"))\n", + "\n", + "\n", + "def create_file(text: Annotated[str, node_text]) -> Annotated[str, node_file]:\n", + " return text" + ] }, { "cell_type": "code", @@ -57,4 +70,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From fba2a04b329e3959287b5d80ad3cf1d7b5326eaa Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 15:51:54 +0200 Subject: [PATCH 05/12] Fix. --- .readthedocs.yaml | 19 ++++++++++--------- justfile | 8 ++++++++ pyproject.toml | 29 +---------------------------- 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 1478786..707af96 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,17 +1,18 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: - python: "3.10" + python: "3.12" + jobs: + create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --group docs + install: + - "true" sphinx: configuration: docs/source/conf.py fail_on_warning: true - -python: - install: - - method: pip - path: . - extra_requirements: - - docs diff --git a/justfile b/justfile index 4ea0144..dbda750 100644 --- a/justfile +++ b/justfile @@ -14,6 +14,14 @@ typing: lint: uvx --with pre-commit-uv pre-commit run -a +# Build documentation +docs: + uv run --group docs sphinx-build docs/source docs/build + +# Serve documentation with auto-reload +docs-serve: + uv run --group docs sphinx-autobuild docs/source docs/build + # Run all checks (format, lint, typing, test) check: lint typing test diff --git a/pyproject.toml b/pyproject.toml index 1897535..1b3452d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ docs = [ "myst-parser", "nbsphinx", "sphinx", + "sphinx-autobuild", "sphinx-click", "sphinx-copybutton", "sphinx-design>=0.3", @@ -75,14 +76,6 @@ pytask_parallel = "pytask_parallel.plugin" requires = ["hatchling", "hatch_vcs"] build-backend = "hatchling.build" -[tool.rye] -managed = true -dev-dependencies = ["s3fs>=2024.3.1", "tox-uv>=1.7.0"] - -[tool.rye.scripts] -clean-docs = { cmd = "rm -rf docs/build" } -build-docs = { cmd = "sphinx-build -b html docs/source docs/build" } - [tool.hatch.build.hooks.vcs] version-file = "src/pytask_parallel/_version.py" @@ -100,22 +93,6 @@ source = "vcs" [tool.hatch.metadata] allow-direct-references = true -[tool.mypy] -files = ["src", "tests"] -check_untyped_defs = true -disallow_any_generics = true -disallow_incomplete_defs = true -disallow_untyped_defs = true -no_implicit_optional = true -warn_redundant_casts = true -warn_unused_ignores = true -ignore_missing_imports = true - -[[tool.mypy.overrides]] -module = "tests.*" -disallow_untyped_defs = false -ignore_errors = true - [tool.ruff] fix = true unsafe-fixes = true @@ -143,10 +120,6 @@ convention = "numpy" addopts = ["--nbmake"] # Do not add src since it messes with the loading of pytask-parallel as a plugin. testpaths = ["tests"] -markers = [ - "wip: Tests that are work-in-progress.", -] -norecursedirs = [".idea", ".tox"] [tool.coverage.report] exclude_also = [ From 4c1957c36d7b160cdc13f33546dc34f200f1ddb8 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 15:58:15 +0200 Subject: [PATCH 06/12] Fix. --- .github/workflows/main.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db0754c..acdfeec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,10 +23,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v3 - - name: Set up Python - run: uv python install + - uses: astral-sh/setup-uv@v6 + with: + enable-cache: true - name: Install just uses: extractions/setup-just@v2 - name: Run type checking @@ -45,10 +44,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install uv - uses: astral-sh/setup-uv@v3 - - name: Set up Python ${{ matrix.python-version }} - run: uv python install ${{ matrix.python-version }} + - uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} + enable-cache: true - name: Install just uses: extractions/setup-just@v2 - name: Run tests From 878c16bed6dadd92bd189b1b89c7eb80cb9d10e9 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sat, 26 Jul 2025 19:05:16 +0200 Subject: [PATCH 07/12] Add timeout. --- justfile | 20 ++++++++++---------- pyproject.toml | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/justfile b/justfile index dbda750..03d5be6 100644 --- a/justfile +++ b/justfile @@ -3,8 +3,16 @@ install: uv sync --all-groups # Run tests -test: - uv run --group test pytest --cov=src --cov=tests --cov-report=xml +test *args="": + uv run --group test pytest --cov=src --cov=tests --cov-report=xml --timeout=30 {{args}} + +# Run tests with lowest dependency resolution +test-lowest *args="": + uv run --group test --resolution lowest-direct pytest --timeout=30 {{args}} + +# Run tests with highest dependency resolution +test-highest *args="": + uv run --group test --resolution highest pytest --timeout=30 {{args}} # Run type checking typing: @@ -24,11 +32,3 @@ docs-serve: # Run all checks (format, lint, typing, test) check: lint typing test - -# Run tests with lowest dependency resolution -test-lowest: - uv run --group test --resolution lowest-direct pytest - -# Run tests with highest dependency resolution -test-highest: - uv run --group test --resolution highest pytest diff --git a/pyproject.toml b/pyproject.toml index 1b3452d..c1c5697 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ test = [ "nbmake", "pytest>=8.4.0", "pytest-cov>=5.0.0", + "pytest-timeout>=2.4.0", {include-group = "coiled"}, {include-group = "dask"}, ] From c052aee8e38d812bcf44e5b67593c96611914d10 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 27 Jul 2025 08:47:29 +0200 Subject: [PATCH 08/12] Skip test. --- tests/test_execute.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_execute.py b/tests/test_execute.py index 5b2659a..248a4f3 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys import textwrap from time import time @@ -76,6 +77,11 @@ def task_2(path: Annotated[Path, Product] = Path("out_2.txt")): @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) +@pytest.mark.skipif( + (sys.version_info[:2] == (3, 12) and sys.platform == "win32") + or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), + reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450/", +) def test_stop_execution_when_max_failures_is_reached(tmp_path, parallel_backend): source = """ import time From 1b335a2a9c7d4738c784cf7eff63431af0f6571f Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 27 Jul 2025 08:52:32 +0200 Subject: [PATCH 09/12] remove pytest-timeout. --- justfile | 6 +++--- pyproject.toml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/justfile b/justfile index 03d5be6..d63d31d 100644 --- a/justfile +++ b/justfile @@ -4,15 +4,15 @@ install: # Run tests test *args="": - uv run --group test pytest --cov=src --cov=tests --cov-report=xml --timeout=30 {{args}} + uv run --group test pytest --cov=src --cov=tests --cov-report=xml {{args}} # Run tests with lowest dependency resolution test-lowest *args="": - uv run --group test --resolution lowest-direct pytest --timeout=30 {{args}} + uv run --group test --resolution lowest-direct pytest {{args}} # Run tests with highest dependency resolution test-highest *args="": - uv run --group test --resolution highest pytest --timeout=30 {{args}} + uv run --group test --resolution highest pytest {{args}} # Run type checking typing: diff --git a/pyproject.toml b/pyproject.toml index c1c5697..1b3452d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,6 @@ test = [ "nbmake", "pytest>=8.4.0", "pytest-cov>=5.0.0", - "pytest-timeout>=2.4.0", {include-group = "coiled"}, {include-group = "dask"}, ] From 33eaabf848d670892147f74d1ca1c8f946ceaed8 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 27 Jul 2025 09:14:25 +0200 Subject: [PATCH 10/12] FIx. --- tests/test_capture.py | 10 +++++++++- tests/test_config.py | 21 ++++++++++++--------- tests/test_execute.py | 14 ++++++++------ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/tests/test_capture.py b/tests/test_capture.py index 557858f..5723f57 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -1,3 +1,4 @@ +import sys import textwrap import pytest @@ -16,7 +17,14 @@ reason="dask cannot handle dynamically imported modules." ), ), - ParallelBackend.LOKY, + pytest.param( + ParallelBackend.LOKY, + marks=pytest.mark.skipif( + (sys.version_info[:2] == (3, 12) and sys.platform == "win32") + or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), + reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", # noqa: E501 + ), + ), ParallelBackend.PROCESSES, ], ) diff --git a/tests/test_config.py b/tests/test_config.py index ebd0b61..58ed508 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import sys import textwrap import pytest @@ -32,16 +33,18 @@ def test_interplay_between_debugging_and_parallel(tmp_path, pdb, n_workers, expe ("n_workers", 1, ExitCode.OK), ("n_workers", 2, ExitCode.OK), ("parallel_backend", "unknown_backend", ExitCode.CONFIGURATION_FAILED), - ] - + [ - ("parallel_backend", parallel_backend, ExitCode.OK) - for parallel_backend in ( + pytest.param( + "parallel_backend", ParallelBackend.LOKY, - ParallelBackend.PROCESSES, - ParallelBackend.THREADS, - ) - ] - + [ + ExitCode.OK, + marks=pytest.mark.skipif( + (sys.version_info[:2] == (3, 12) and sys.platform == "win32") + or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), + reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", # noqa: E501 + ), + ), + ("parallel_backend", ParallelBackend.PROCESSES, ExitCode.OK), + ("parallel_backend", ParallelBackend.THREADS, ExitCode.OK), pytest.param( "parallel_backend", "dask", diff --git a/tests/test_execute.py b/tests/test_execute.py index 248a4f3..3aa0f4f 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -20,7 +20,14 @@ reason="dask cannot handle dynamically imported modules." ), ), - ParallelBackend.LOKY, + pytest.param( + ParallelBackend.LOKY, + marks=pytest.mark.skipif( + (sys.version_info[:2] == (3, 12) and sys.platform == "win32") + or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), + reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", # noqa: E501 + ), + ), ParallelBackend.PROCESSES, ParallelBackend.THREADS, ] @@ -77,11 +84,6 @@ def task_2(path: Annotated[Path, Product] = Path("out_2.txt")): @pytest.mark.parametrize("parallel_backend", _IMPLEMENTED_BACKENDS) -@pytest.mark.skipif( - (sys.version_info[:2] == (3, 12) and sys.platform == "win32") - or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), - reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450/", -) def test_stop_execution_when_max_failures_is_reached(tmp_path, parallel_backend): source = """ import time From 20fc10fb3c02e5e93ae8441a8f126e2125e1fa49 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 27 Jul 2025 10:41:10 +0200 Subject: [PATCH 11/12] Add last skipif. --- tests/test_remote.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_remote.py b/tests/test_remote.py index 854212e..8a1210f 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,10 +1,17 @@ import pickle +import sys import textwrap import pytest from pytask import ExitCode from pytask import cli +pytestmark = pytest.mark.skipif( + (sys.version_info[:2] == (3, 12) and sys.platform == "win32") + or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), + reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", +) + @pytest.fixture(autouse=True) def _setup_remote_backend(tmp_path): From 8145bd5cd8a3d4fe5d0f6d61656ee6006b1fa257 Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 27 Jul 2025 10:52:19 +0200 Subject: [PATCH 12/12] Refactor and extend skipif. --- tests/conftest.py | 7 +++++++ tests/test_capture.py | 11 ++--------- tests/test_config.py | 8 ++------ tests/test_execute.py | 11 ++--------- tests/test_remote.py | 9 +++------ 5 files changed, 16 insertions(+), 30 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4a6ce5f..084397a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -79,3 +79,10 @@ def pytest_collection_modifyitems(session, config, items) -> None: # noqa: ARG0 for item in items: if isinstance(item, NotebookItem): item.add_marker(pytest.mark.xfail(reason="The tests are flaky.")) + + +skip_if_deadlock = pytest.mark.skipif( + (sys.version_info[:2] in [(3, 12), (3, 13)] and sys.platform == "win32") + or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), + reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", +) diff --git a/tests/test_capture.py b/tests/test_capture.py index 5723f57..6b2b48a 100644 --- a/tests/test_capture.py +++ b/tests/test_capture.py @@ -1,4 +1,3 @@ -import sys import textwrap import pytest @@ -6,6 +5,7 @@ from pytask import cli from pytask_parallel import ParallelBackend +from tests.conftest import skip_if_deadlock @pytest.mark.parametrize( @@ -17,14 +17,7 @@ reason="dask cannot handle dynamically imported modules." ), ), - pytest.param( - ParallelBackend.LOKY, - marks=pytest.mark.skipif( - (sys.version_info[:2] == (3, 12) and sys.platform == "win32") - or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), - reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", # noqa: E501 - ), - ), + pytest.param(ParallelBackend.LOKY, marks=skip_if_deadlock), ParallelBackend.PROCESSES, ], ) diff --git a/tests/test_config.py b/tests/test_config.py index 58ed508..e265d6f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,6 @@ from __future__ import annotations import os -import sys import textwrap import pytest @@ -9,6 +8,7 @@ from pytask import build from pytask_parallel import ParallelBackend +from tests.conftest import skip_if_deadlock @pytest.mark.parametrize( @@ -37,11 +37,7 @@ def test_interplay_between_debugging_and_parallel(tmp_path, pdb, n_workers, expe "parallel_backend", ParallelBackend.LOKY, ExitCode.OK, - marks=pytest.mark.skipif( - (sys.version_info[:2] == (3, 12) and sys.platform == "win32") - or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), - reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", # noqa: E501 - ), + marks=skip_if_deadlock, ), ("parallel_backend", ParallelBackend.PROCESSES, ExitCode.OK), ("parallel_backend", ParallelBackend.THREADS, ExitCode.OK), diff --git a/tests/test_execute.py b/tests/test_execute.py index 3aa0f4f..761169a 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys import textwrap from time import time @@ -12,6 +11,7 @@ from pytask_parallel import ParallelBackend from pytask_parallel.execute import _Sleeper from tests.conftest import restore_sys_path_and_module_after_test_execution +from tests.conftest import skip_if_deadlock _IMPLEMENTED_BACKENDS = [ pytest.param( @@ -20,14 +20,7 @@ reason="dask cannot handle dynamically imported modules." ), ), - pytest.param( - ParallelBackend.LOKY, - marks=pytest.mark.skipif( - (sys.version_info[:2] == (3, 12) and sys.platform == "win32") - or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), - reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", # noqa: E501 - ), - ), + pytest.param(ParallelBackend.LOKY, marks=skip_if_deadlock), ParallelBackend.PROCESSES, ParallelBackend.THREADS, ] diff --git a/tests/test_remote.py b/tests/test_remote.py index 8a1210f..2dc90be 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,16 +1,13 @@ import pickle -import sys import textwrap import pytest from pytask import ExitCode from pytask import cli -pytestmark = pytest.mark.skipif( - (sys.version_info[:2] == (3, 12) and sys.platform == "win32") - or (sys.version_info[:2] == (3, 13) and sys.platform == "linux"), - reason="Deadlock in loky/backend/resource_tracker.py, line 181, maybe related to https://github.com/joblib/loky/pull/450", -) +from tests.conftest import skip_if_deadlock + +pytestmark = skip_if_deadlock @pytest.fixture(autouse=True)