From b41c7a980fb3b427d4bf0d2f37b0be5dabaad2a1 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Mon, 12 Oct 2026 09:11:21 +0200 Subject: [PATCH 1/8] test: deflake recurring task test and kill hung tests with a thread timeout --- pyproject.toml | 2 ++ tests/unit/_utils/test_recurring_task.py | 11 +++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ba67de154..cb719af42d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -267,6 +267,8 @@ addopts = "-r a --verbose --dist worksteal" asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" timeout = 1800 +# A thread timer kills the stuck worker even when its main thread is blocked and a SIGALRM handler can never run. +timeout_method = "thread" markers = [ "run_alone: marks tests that must run in isolation", ] diff --git a/tests/unit/_utils/test_recurring_task.py b/tests/unit/_utils/test_recurring_task.py index 0e347b0b28..680089dc12 100644 --- a/tests/unit/_utils/test_recurring_task.py +++ b/tests/unit/_utils/test_recurring_task.py @@ -42,18 +42,17 @@ async def test_start_and_stop(function: AsyncMock, delay: timedelta) -> None: assert rt.task.done() -@pytest.mark.run_alone async def test_execution(function: AsyncMock, delay: timedelta) -> None: + """The recurring task keeps calling its function until stopped.""" + third_call_done = asyncio.Event() + function.side_effect = lambda: third_call_done.set() if function.call_count >= 3 else None task = RecurringTask(function, delay) task.start() - await asyncio.sleep(0.2) # Wait enough for the task to execute a few times + await asyncio.wait_for(third_call_done.wait(), timeout=5) await task.stop() - assert isinstance(task.func, AsyncMock) # To let type checker know that the function is a mock - assert task.func.call_count >= 3 - - await task.stop() + assert function.call_count >= 3 async def test_execution_continues_after_exception(delay: timedelta, caplog: pytest.LogCaptureFixture) -> None: From 144e4014aa4a3308857c77e7ccd64ca30c0f83b7 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Mon, 12 Oct 2026 10:17:34 +0200 Subject: [PATCH 2/8] test: dump stacks of tests stuck for 10 minutes via faulthandler --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index cb719af42d..dd45088ac4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -269,6 +269,9 @@ asyncio_mode = "auto" timeout = 1800 # A thread timer kills the stuck worker even when its main thread is blocked and a SIGALRM handler can never run. timeout_method = "thread" +# pytest-timeout writes its stack dump to the xdist worker's stdout, which never reaches the console. faulthandler +# writes straight to the stderr file descriptor, so a test stuck this long leaves its stacks in the log. +faulthandler_timeout = 600 markers = [ "run_alone: marks tests that must run in isolation", ] From 095bafea483f1b779fc72bc168e48fd992195a84 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Mon, 12 Oct 2026 12:37:38 +0200 Subject: [PATCH 3/8] test: add temporary hang diagnostics --- tests/unit/conftest.py | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 3b9b36af7b..c21d7266db 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -291,3 +291,83 @@ async def http_client(request: pytest.FixtureRequest) -> AsyncGenerator[HttpClie @pytest.fixture def redis_client() -> FakeAsyncRedis: return FakeAsyncRedis() + + +# --- Temporary hang diagnostics (not for merge) --------------------------------------------------------------------- + +import asyncio as _diag_asyncio # noqa: E402 +import gc as _diag_gc # noqa: E402 +import subprocess as _diag_subprocess # noqa: E402 +import threading as _diag_threading # noqa: E402 +import traceback as _diag_traceback # noqa: E402 + +_DIAG_WORKER = os.environ.get('PYTEST_XDIST_WORKER', 'main') +_DIAG_AFTER = float(os.environ.get('CRAWLEE_HANG_DIAG_AFTER', '300')) + +_diag_stream: Any = None + + +def pytest_configure(config: pytest.Config) -> None: # noqa: ARG001 + # Capture is suspended while plugins configure, so fd 2 is the real stderr here; the dup survives fd capture. + global _diag_stream # noqa: PLW0603 + _diag_stream = open(os.dup(2), 'w', buffering=1) # noqa: PTH123, SIM115 + handler = logging.StreamHandler(_diag_stream) + handler.setLevel(logging.ERROR) + handler.setFormatter(logging.Formatter(f'HANG-DIAG [{_DIAG_WORKER}] asyncio logger: %(message)s')) + logging.getLogger('asyncio').addHandler(handler) + + +def _diag_dump(nodeid: str) -> None: + out: list[str] = [f'===== HANG-DIAG [{_DIAG_WORKER}] pid={os.getpid()} stuck in {nodeid} ====='] + try: + loops = [o for o in _diag_gc.get_objects() if isinstance(o, _diag_asyncio.AbstractEventLoop)] + for loop in loops: + if loop.is_closed(): + continue + out.append(f'--- loop {loop!r} running={loop.is_running()}') + out.append(f'ready={list(getattr(loop, "_ready", []))!r}') + out.append(f'scheduled={list(getattr(loop, "_scheduled", []))[:20]!r}') + out.append(f'current_task={getattr(_diag_asyncio.tasks, "_current_tasks", {}).get(loop)!r}') + for task in list(_diag_asyncio.all_tasks(loop)): + out.append(f'task {task!r} type={type(task).__module__}.{type(task).__qualname__}') + for frame in task.get_stack(): + out.extend(' ' + line for line in _diag_traceback.format_stack(frame, limit=1)) + waiter = getattr(task, '_fut_waiter', None) + out.append(f' fut_waiter={waiter!r}') + except Exception: + out.append(_diag_traceback.format_exc()) + try: + for tid in sorted(os.listdir('/proc/self/task')): # noqa: PTH208 + with open(f'/proc/self/task/{tid}/comm') as f: # noqa: PTH123 + comm = f.read().strip() + with open(f'/proc/self/task/{tid}/wchan') as f: # noqa: PTH123 + wchan = f.read().strip() + out.append(f'native thread {tid} comm={comm} wchan={wchan}') + out.append( + _diag_subprocess.run(['ss', '-tanpe'], capture_output=True, text=True, timeout=20, check=False).stdout # noqa: S607 + ) + out.append( + _diag_subprocess.run( + ['ps', '-eo', 'pid,ppid,stat,etime,wchan:20,args'], # noqa: S607 + capture_output=True, + text=True, + timeout=20, + check=False, + ).stdout + ) + except Exception: + out.append(_diag_traceback.format_exc()) + out.append(f'===== HANG-DIAG [{_DIAG_WORKER}] end =====') + _diag_stream.write('\n'.join(out) + '\n') + _diag_stream.flush() + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_protocol(item: pytest.Item) -> Iterator[None]: + timer = _diag_threading.Timer(_DIAG_AFTER, _diag_dump, (item.nodeid,)) + timer.daemon = True + timer.start() + try: + yield + finally: + timer.cancel() From a0d019a1eb1b611380d0683942e829f6ea3d4032 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Mon, 12 Oct 2026 15:01:45 +0200 Subject: [PATCH 4/8] test: fix event loop hang on Python 3.10 caused by backports runner context patch --- tests/unit/conftest.py | 123 ++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 81 deletions(-) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c21d7266db..27fc3cceae 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,8 +1,12 @@ from __future__ import annotations +import contextlib +import contextvars import logging import os import socket +import sys +import threading import warnings from typing import TYPE_CHECKING, Any, cast @@ -23,7 +27,7 @@ from tests.unit.server import TestServer, app, no_robots_app, serve_in_thread if TYPE_CHECKING: - from collections.abc import AsyncGenerator, Callable, Iterator + from collections.abc import AsyncGenerator, Callable, Generator, Iterator from pathlib import Path from yarl import URL @@ -31,6 +35,43 @@ from crawlee.http_clients._base import HttpClient +if sys.version_info < (3, 11): + from backports.asyncio.runner import runner as backports_runner # ty: ignore[unresolved-import] + + original_copy_context = contextvars.copy_context + original_patch_object = backports_runner._patch_object + copy_context_override = threading.local() + + def copy_context() -> contextvars.Context: + factory = getattr(copy_context_override, 'factory', None) + return original_copy_context() if factory is None else factory() + + @contextlib.contextmanager + def patch_object(target: Any, attribute: str, new: Any) -> Generator[None]: + """Thread-local stand-in for the `_patch_object` helper of `backports.asyncio.runner`. + + On Python 3.10, pytest-asyncio's `Runner.run(coro, context=...)` swaps the module-global + `contextvars.copy_context` for a lambda returning the fixture's context while it creates the task. If the main + thread releases the GIL meanwhile (e.g. a GC finalizer closing a socket), the test server's event loop thread + builds its handles with that same context. When it runs one of them while the main thread steps the fixture + task, `Context.run` raises "cannot enter context: ... is already entered", the task step is dropped, and the + fixture never finishes. Keeping the override thread-local confines it to the thread that creates the task. + """ + if target is not contextvars or attribute != 'copy_context': + with original_patch_object(target, attribute, new): + yield + return + previous = getattr(copy_context_override, 'factory', None) + copy_context_override.factory = new + try: + yield + finally: + copy_context_override.factory = previous + + contextvars.copy_context = copy_context # ty: ignore[invalid-assignment] + backports_runner._patch_object = patch_object + + @pytest.fixture(autouse=True) async def suppress_user_warning() -> AsyncGenerator[None, None]: """Suppress user warnings during tests. @@ -291,83 +332,3 @@ async def http_client(request: pytest.FixtureRequest) -> AsyncGenerator[HttpClie @pytest.fixture def redis_client() -> FakeAsyncRedis: return FakeAsyncRedis() - - -# --- Temporary hang diagnostics (not for merge) --------------------------------------------------------------------- - -import asyncio as _diag_asyncio # noqa: E402 -import gc as _diag_gc # noqa: E402 -import subprocess as _diag_subprocess # noqa: E402 -import threading as _diag_threading # noqa: E402 -import traceback as _diag_traceback # noqa: E402 - -_DIAG_WORKER = os.environ.get('PYTEST_XDIST_WORKER', 'main') -_DIAG_AFTER = float(os.environ.get('CRAWLEE_HANG_DIAG_AFTER', '300')) - -_diag_stream: Any = None - - -def pytest_configure(config: pytest.Config) -> None: # noqa: ARG001 - # Capture is suspended while plugins configure, so fd 2 is the real stderr here; the dup survives fd capture. - global _diag_stream # noqa: PLW0603 - _diag_stream = open(os.dup(2), 'w', buffering=1) # noqa: PTH123, SIM115 - handler = logging.StreamHandler(_diag_stream) - handler.setLevel(logging.ERROR) - handler.setFormatter(logging.Formatter(f'HANG-DIAG [{_DIAG_WORKER}] asyncio logger: %(message)s')) - logging.getLogger('asyncio').addHandler(handler) - - -def _diag_dump(nodeid: str) -> None: - out: list[str] = [f'===== HANG-DIAG [{_DIAG_WORKER}] pid={os.getpid()} stuck in {nodeid} ====='] - try: - loops = [o for o in _diag_gc.get_objects() if isinstance(o, _diag_asyncio.AbstractEventLoop)] - for loop in loops: - if loop.is_closed(): - continue - out.append(f'--- loop {loop!r} running={loop.is_running()}') - out.append(f'ready={list(getattr(loop, "_ready", []))!r}') - out.append(f'scheduled={list(getattr(loop, "_scheduled", []))[:20]!r}') - out.append(f'current_task={getattr(_diag_asyncio.tasks, "_current_tasks", {}).get(loop)!r}') - for task in list(_diag_asyncio.all_tasks(loop)): - out.append(f'task {task!r} type={type(task).__module__}.{type(task).__qualname__}') - for frame in task.get_stack(): - out.extend(' ' + line for line in _diag_traceback.format_stack(frame, limit=1)) - waiter = getattr(task, '_fut_waiter', None) - out.append(f' fut_waiter={waiter!r}') - except Exception: - out.append(_diag_traceback.format_exc()) - try: - for tid in sorted(os.listdir('/proc/self/task')): # noqa: PTH208 - with open(f'/proc/self/task/{tid}/comm') as f: # noqa: PTH123 - comm = f.read().strip() - with open(f'/proc/self/task/{tid}/wchan') as f: # noqa: PTH123 - wchan = f.read().strip() - out.append(f'native thread {tid} comm={comm} wchan={wchan}') - out.append( - _diag_subprocess.run(['ss', '-tanpe'], capture_output=True, text=True, timeout=20, check=False).stdout # noqa: S607 - ) - out.append( - _diag_subprocess.run( - ['ps', '-eo', 'pid,ppid,stat,etime,wchan:20,args'], # noqa: S607 - capture_output=True, - text=True, - timeout=20, - check=False, - ).stdout - ) - except Exception: - out.append(_diag_traceback.format_exc()) - out.append(f'===== HANG-DIAG [{_DIAG_WORKER}] end =====') - _diag_stream.write('\n'.join(out) + '\n') - _diag_stream.flush() - - -@pytest.hookimpl(hookwrapper=True) -def pytest_runtest_protocol(item: pytest.Item) -> Iterator[None]: - timer = _diag_threading.Timer(_DIAG_AFTER, _diag_dump, (item.nodeid,)) - timer.daemon = True - timer.start() - try: - yield - finally: - timer.cancel() From 1c6184b4cba0156c4046b942a35ba23a40612896 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Tue, 13 Oct 2026 09:32:45 +0200 Subject: [PATCH 5/8] test: skip tests that hang on Linux with Python 3.10 --- tests/unit/conftest.py | 43 +------------------ tests/unit/crawlers/_playwright/test_utils.py | 7 +++ .../test_sitemap_request_loader.py | 6 +++ 3 files changed, 14 insertions(+), 42 deletions(-) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 27fc3cceae..3b9b36af7b 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,12 +1,8 @@ from __future__ import annotations -import contextlib -import contextvars import logging import os import socket -import sys -import threading import warnings from typing import TYPE_CHECKING, Any, cast @@ -27,7 +23,7 @@ from tests.unit.server import TestServer, app, no_robots_app, serve_in_thread if TYPE_CHECKING: - from collections.abc import AsyncGenerator, Callable, Generator, Iterator + from collections.abc import AsyncGenerator, Callable, Iterator from pathlib import Path from yarl import URL @@ -35,43 +31,6 @@ from crawlee.http_clients._base import HttpClient -if sys.version_info < (3, 11): - from backports.asyncio.runner import runner as backports_runner # ty: ignore[unresolved-import] - - original_copy_context = contextvars.copy_context - original_patch_object = backports_runner._patch_object - copy_context_override = threading.local() - - def copy_context() -> contextvars.Context: - factory = getattr(copy_context_override, 'factory', None) - return original_copy_context() if factory is None else factory() - - @contextlib.contextmanager - def patch_object(target: Any, attribute: str, new: Any) -> Generator[None]: - """Thread-local stand-in for the `_patch_object` helper of `backports.asyncio.runner`. - - On Python 3.10, pytest-asyncio's `Runner.run(coro, context=...)` swaps the module-global - `contextvars.copy_context` for a lambda returning the fixture's context while it creates the task. If the main - thread releases the GIL meanwhile (e.g. a GC finalizer closing a socket), the test server's event loop thread - builds its handles with that same context. When it runs one of them while the main thread steps the fixture - task, `Context.run` raises "cannot enter context: ... is already entered", the task step is dropped, and the - fixture never finishes. Keeping the override thread-local confines it to the thread that creates the task. - """ - if target is not contextvars or attribute != 'copy_context': - with original_patch_object(target, attribute, new): - yield - return - previous = getattr(copy_context_override, 'factory', None) - copy_context_override.factory = new - try: - yield - finally: - copy_context_override.factory = previous - - contextvars.copy_context = copy_context # ty: ignore[invalid-assignment] - backports_runner._patch_object = patch_object - - @pytest.fixture(autouse=True) async def suppress_user_warning() -> AsyncGenerator[None, None]: """Suppress user warnings during tests. diff --git a/tests/unit/crawlers/_playwright/test_utils.py b/tests/unit/crawlers/_playwright/test_utils.py index acd56cb39a..935142dfc4 100644 --- a/tests/unit/crawlers/_playwright/test_utils.py +++ b/tests/unit/crawlers/_playwright/test_utils.py @@ -1,3 +1,5 @@ +import sys + import pytest from playwright.async_api import async_playwright from yarl import URL @@ -65,6 +67,11 @@ async def test_infinite_scroll_no_page_without_scroll(server_url: URL) -> None: await browser.close() +@pytest.mark.skipif( + sys.platform == 'linux' and sys.version_info < (3, 11), + reason='Hangs on Linux with Python 3.10: the pytest-asyncio backports runner can leak the fixture context ' + 'into the test server thread.', +) async def test_double_call_infinite_scroll(server_url: URL) -> None: """Checks that calling infinite_scroll twice does not load more items the second time.""" async with async_playwright() as p: diff --git a/tests/unit/request_loaders/test_sitemap_request_loader.py b/tests/unit/request_loaders/test_sitemap_request_loader.py index d88b84164d..4c75c6f562 100644 --- a/tests/unit/request_loaders/test_sitemap_request_loader.py +++ b/tests/unit/request_loaders/test_sitemap_request_loader.py @@ -1,5 +1,6 @@ import base64 import gzip +import sys from contextlib import asynccontextmanager from typing import TYPE_CHECKING, Any, cast from unittest.mock import AsyncMock, MagicMock, patch @@ -346,6 +347,11 @@ def transform_request(request_options: RequestOptions) -> RequestOptions | Reque assert extracted_urls == get_basic_results(server_url) +@pytest.mark.skipif( + sys.platform == 'linux' and sys.version_info < (3, 11), + reason='Hangs on Linux with Python 3.10: the pytest-asyncio backports runner can leak the fixture context ' + 'into the test server thread.', +) async def test_transform_request_function_with_skip(server_url: URL, http_client: HttpClient) -> None: sitemap_url = (server_url / 'sitemap.xml').with_query( base64=encode_base64(get_basic_sitemap(url=server_url).encode()) From a00bf3a5c3726f45dda2496cd0695b0ce1b2f5ac Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Tue, 13 Oct 2026 11:01:54 +0200 Subject: [PATCH 6/8] test: narrow the recurring task execution test docstring --- tests/unit/_utils/test_recurring_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/_utils/test_recurring_task.py b/tests/unit/_utils/test_recurring_task.py index 680089dc12..c9a02f2268 100644 --- a/tests/unit/_utils/test_recurring_task.py +++ b/tests/unit/_utils/test_recurring_task.py @@ -43,7 +43,7 @@ async def test_start_and_stop(function: AsyncMock, delay: timedelta) -> None: async def test_execution(function: AsyncMock, delay: timedelta) -> None: - """The recurring task keeps calling its function until stopped.""" + """The recurring task calls its function repeatedly.""" third_call_done = asyncio.Event() function.side_effect = lambda: third_call_done.set() if function.call_count >= 3 else None task = RecurringTask(function, delay) From cda3863d580a67b0ecfc7ea514f88a4d2b0ea42e Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Tue, 13 Oct 2026 14:37:28 +0200 Subject: [PATCH 7/8] test: pin pytest-asyncio below 1.1 on Linux with Python 3.10 instead of skipping hanging tests --- pyproject.toml | 5 +- tests/unit/crawlers/_playwright/test_utils.py | 7 - .../test_sitemap_request_loader.py | 6 - tests/unit/storages/test_dataset.py | 4 +- tests/unit/storages/test_key_value_store.py | 4 +- tests/unit/storages/test_request_queue.py | 4 +- uv.lock | 177 +++++++++++++----- 7 files changed, 136 insertions(+), 71 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dd45088ac4..c167dccce8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,7 +130,10 @@ dev = [ "pre-commit<5.0.0", "proxy-py<3.0.0", "pydoc-markdown<5.0.0", - "pytest-asyncio<2.0.0", + # pytest-asyncio 1.1+ runs tests through `backports.asyncio.runner` on Python 3.10, whose process-wide + # `contextvars.copy_context` patch can leak the fixture context into the test server thread and hang the test. + "pytest-asyncio<2.0.0; python_version >= '3.11' or sys_platform != 'linux'", + "pytest-asyncio>=1.0.0,<1.1.0; python_version < '3.11' and sys_platform == 'linux'", "pytest-cov<8.0.0", "pytest-rerunfailures<17.0.0", "pytest-timeout<3.0.0", diff --git a/tests/unit/crawlers/_playwright/test_utils.py b/tests/unit/crawlers/_playwright/test_utils.py index 935142dfc4..acd56cb39a 100644 --- a/tests/unit/crawlers/_playwright/test_utils.py +++ b/tests/unit/crawlers/_playwright/test_utils.py @@ -1,5 +1,3 @@ -import sys - import pytest from playwright.async_api import async_playwright from yarl import URL @@ -67,11 +65,6 @@ async def test_infinite_scroll_no_page_without_scroll(server_url: URL) -> None: await browser.close() -@pytest.mark.skipif( - sys.platform == 'linux' and sys.version_info < (3, 11), - reason='Hangs on Linux with Python 3.10: the pytest-asyncio backports runner can leak the fixture context ' - 'into the test server thread.', -) async def test_double_call_infinite_scroll(server_url: URL) -> None: """Checks that calling infinite_scroll twice does not load more items the second time.""" async with async_playwright() as p: diff --git a/tests/unit/request_loaders/test_sitemap_request_loader.py b/tests/unit/request_loaders/test_sitemap_request_loader.py index 4c75c6f562..d88b84164d 100644 --- a/tests/unit/request_loaders/test_sitemap_request_loader.py +++ b/tests/unit/request_loaders/test_sitemap_request_loader.py @@ -1,6 +1,5 @@ import base64 import gzip -import sys from contextlib import asynccontextmanager from typing import TYPE_CHECKING, Any, cast from unittest.mock import AsyncMock, MagicMock, patch @@ -347,11 +346,6 @@ def transform_request(request_options: RequestOptions) -> RequestOptions | Reque assert extracted_urls == get_basic_results(server_url) -@pytest.mark.skipif( - sys.platform == 'linux' and sys.version_info < (3, 11), - reason='Hangs on Linux with Python 3.10: the pytest-asyncio backports runner can leak the fixture context ' - 'into the test server thread.', -) async def test_transform_request_function_with_skip(server_url: URL, http_client: HttpClient) -> None: sitemap_url = (server_url / 'sitemap.xml').with_query( base64=encode_base64(get_basic_sitemap(url=server_url).encode()) diff --git a/tests/unit/storages/test_dataset.py b/tests/unit/storages/test_dataset.py index 7e0b64fbd5..0002f31cc4 100644 --- a/tests/unit/storages/test_dataset.py +++ b/tests/unit/storages/test_dataset.py @@ -930,7 +930,7 @@ async def test_purge_on_start_enabled(storage_client: StorageClient) -> None: # Skip this test for memory storage since it doesn't persist data between client instances. if isinstance(storage_client, MemoryStorageClient): - pytest.skip('Memory storage does not persist data between client instances.') + raise pytest.skip.Exception('Memory storage does not persist data between client instances.') configuration = Configuration(purge_on_start=True) @@ -1016,7 +1016,7 @@ async def test_purge_on_start_disabled(storage_client: StorageClient) -> None: # Skip this test for memory storage since it doesn't persist data between client instances. if isinstance(storage_client, MemoryStorageClient): - pytest.skip('Memory storage does not persist data between client instances.') + raise pytest.skip.Exception('Memory storage does not persist data between client instances.') configuration = Configuration(purge_on_start=False) diff --git a/tests/unit/storages/test_key_value_store.py b/tests/unit/storages/test_key_value_store.py index 1faadce748..302c8eb30e 100644 --- a/tests/unit/storages/test_key_value_store.py +++ b/tests/unit/storages/test_key_value_store.py @@ -994,7 +994,7 @@ async def test_purge_on_start_enabled(storage_client: StorageClient) -> None: # Skip this test for memory storage since it doesn't persist data between client instances. if isinstance(storage_client, MemoryStorageClient): - pytest.skip('Memory storage does not persist data between client instances.') + raise pytest.skip.Exception('Memory storage does not persist data between client instances.') configuration = Configuration(purge_on_start=True) @@ -1080,7 +1080,7 @@ async def test_purge_on_start_disabled(storage_client: StorageClient) -> None: # Skip this test for memory storage since it doesn't persist data between client instances. if isinstance(storage_client, MemoryStorageClient): - pytest.skip('Memory storage does not persist data between client instances.') + raise pytest.skip.Exception('Memory storage does not persist data between client instances.') configuration = Configuration(purge_on_start=False) diff --git a/tests/unit/storages/test_request_queue.py b/tests/unit/storages/test_request_queue.py index ebcec63a1b..112adfaa96 100644 --- a/tests/unit/storages/test_request_queue.py +++ b/tests/unit/storages/test_request_queue.py @@ -1279,7 +1279,7 @@ async def test_purge_on_start_enabled(storage_client: StorageClient) -> None: # Skip this test for memory storage since it doesn't persist data between client instances. if isinstance(storage_client, MemoryStorageClient): - pytest.skip('Memory storage does not persist data between client instances.') + raise pytest.skip.Exception('Memory storage does not persist data between client instances.') configuration = Configuration(purge_on_start=True) @@ -1405,7 +1405,7 @@ async def test_purge_on_start_disabled(storage_client: StorageClient) -> None: # Skip this test for memory storage since it doesn't persist data between client instances. if isinstance(storage_client, MemoryStorageClient): - pytest.skip('Memory storage does not persist data between client instances.') + raise pytest.skip.Exception('Memory storage does not persist data between client instances.') configuration = Configuration(purge_on_start=False) diff --git a/uv.lock b/uv.lock index 1ddc6d6d79..965a54e55b 100644 --- a/uv.lock +++ b/uv.lock @@ -3,13 +3,14 @@ revision = 5 requires-python = ">=3.10" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] [options] @@ -516,7 +517,7 @@ name = "build" version = "1.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, + { name = "colorama", marker = "(python_full_version >= '3.11' and os_name == 'nt') or (os_name == 'nt' and sys_platform != 'linux')" }, { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, { name = "packaging" }, { name = "pyproject-hooks" }, @@ -1151,8 +1152,10 @@ dev = [ { name = "pre-commit" }, { name = "proxy-py" }, { name = "pydoc-markdown" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, + { name = "pytest-asyncio", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, + { name = "pytest-asyncio", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, { name = "pytest-cov" }, { name = "pytest-rerunfailures" }, { name = "pytest-timeout" }, @@ -1240,7 +1243,8 @@ dev = [ { name = "proxy-py", specifier = "<3.0.0" }, { name = "pydoc-markdown", specifier = "<5.0.0" }, { name = "pytest", specifier = "<10.0.0" }, - { name = "pytest-asyncio", specifier = "<2.0.0" }, + { name = "pytest-asyncio", marker = "python_full_version >= '3.11' or sys_platform != 'linux'", specifier = "<2.0.0" }, + { name = "pytest-asyncio", marker = "python_full_version < '3.11' and sys_platform == 'linux'", specifier = ">=1.0.0,<1.1.0" }, { name = "pytest-cov", specifier = "<8.0.0" }, { name = "pytest-rerunfailures", specifier = "<17.0.0" }, { name = "pytest-timeout", specifier = "<3.0.0" }, @@ -1331,7 +1335,8 @@ name = "cssselect" version = "1.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/8e/5a/6d6fcf922709391fac986f0a03ad4546f4f45b94d10aeb6c1ee041599993/cssselect-1.5.0.tar.gz", hash = "sha256:3cbe82dd7acbee9ba9e5723b5f9e4749826912f1fb31cd7f92aabed5fde15b15", size = 47598, upload-time = "2026-07-27T09:17:34.189Z" } wheels = [ @@ -1344,10 +1349,10 @@ version = "1.6.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -1678,7 +1683,8 @@ name = "griffelib" version = "2.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/27/af/018c10bc9edd42b6ef6db2e96b09542050d5253f9b195e74bc910b2d13ab/griffelib-2.3.0.tar.gz", hash = "sha256:7b0952caf5bca6afa4bb5ee8c6a2d183fe3f21b62efc5f6c7243cb2b26d2d115", size = 234534, upload-time = "2026-09-04T15:08:17.472Z" } wheels = [ @@ -1691,10 +1697,10 @@ version = "2.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -2388,7 +2394,8 @@ name = "jsonpath-ng" version = "1.8.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/32/58/250751940d75c8019659e15482d548a4aa3b6ce122c515102a4bfdac50e3/jsonpath_ng-1.8.0.tar.gz", hash = "sha256:54252968134b5e549ea5b872f1df1168bd7defe1a52fed5a358c194e1943ddc3", size = 74513, upload-time = "2026-02-24T14:42:06.182Z" } wheels = [ @@ -2401,10 +2408,10 @@ version = "1.10.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -3073,7 +3080,8 @@ name = "numpy" version = "2.2.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } wheels = [ @@ -3221,10 +3229,10 @@ version = "2.5.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] sdist = { url = "https://files.pythonhosted.org/packages/95/b0/c7453d0b6e2073c3264468b106ee1563750cecc910965e67357e3698c83e/numpy-2.5.4.tar.gz", hash = "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", size = 20866315, upload-time = "2026-10-10T20:05:31.422Z" } @@ -3539,7 +3547,8 @@ name = "platformdirs" version = "4.12.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/90/a1/d5f9002a70298c64a789779077d8dd90c10aa1f47fe40c86802df874f2a6/platformdirs-4.12.4.tar.gz", hash = "sha256:63743c02414e755de4e31b8f68125c1407495b86c5a006e203c01ff8b9924250", size = 60010, upload-time = "2026-10-07T23:30:32.426Z" } wheels = [ @@ -3552,10 +3561,10 @@ version = "4.13.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -3866,7 +3875,8 @@ name = "pydantic-ai-slim" version = "2.54.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ { name = "anyio" }, @@ -3896,10 +3906,10 @@ version = "2.55.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -4076,7 +4086,8 @@ name = "pydantic-graph" version = "2.54.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ { name = "anyio" }, @@ -4095,10 +4106,10 @@ version = "2.55.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -4200,31 +4211,86 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/11/044d1ae1b4ec0d7af88ee5bc91e081be1022533032b906a7bdabdbb60977/pyproject_hooks-1.3.3-py3-none-any.whl", hash = "sha256:5fc53fdac9f7bd63fbcdc868fb5f90b4784d78a53a3d3388cd738b807441a20b", size = 10724, upload-time = "2026-09-16T08:58:02.96Z" }, ] +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'linux'", +] +dependencies = [ + { name = "exceptiongroup" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + [[package]] name = "pytest" version = "9.1.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11' and sys_platform != 'linux'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11' and sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] +[[package]] +name = "pytest-asyncio" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'linux'", +] +dependencies = [ + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" }, +] + [[package]] name = "pytest-asyncio" version = "1.4.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, - { name = "pytest" }, + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11' and sys_platform != 'linux'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" } }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } @@ -4239,7 +4305,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ @@ -4252,7 +4319,8 @@ version = "16.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/b0/6b5337f9d59b26b0069ea3d5e863c31dc04b69e51bbfb1cf2ea6328fba87/pytest_rerunfailures-16.7.tar.gz", hash = "sha256:6956ddfb65ca1d07e7e3d99e2c5359d82300f4cb062e6049563bd2c106f72d5c", size = 59666, upload-time = "2026-09-17T07:08:48.871Z" } wheels = [ @@ -4264,7 +4332,8 @@ name = "pytest-timeout" version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } wheels = [ @@ -4277,7 +4346,8 @@ version = "3.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, - { name = "pytest" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [ @@ -4437,7 +4507,8 @@ name = "rapidfuzz" version = "3.14.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/2c/21/ef6157213316e85790041254259907eb722e00b03480256c0545d98acd33/rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e", size = 57901753, upload-time = "2026-04-07T11:16:31.931Z" } wheels = [ @@ -4531,10 +4602,10 @@ version = "3.14.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -4889,7 +4960,8 @@ name = "scikit-learn" version = "1.7.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ { name = "joblib" }, @@ -4937,10 +5009,10 @@ version = "1.9.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -5004,7 +5076,8 @@ name = "scipy" version = "1.15.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, @@ -5138,10 +5211,10 @@ version = "1.18.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -5270,7 +5343,8 @@ name = "sqlalchemy" version = "2.0.54" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, @@ -5334,10 +5408,10 @@ version = "2.1.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] @@ -6028,7 +6102,8 @@ name = "websockets" version = "16.1.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/21/f7/bc3a25c5ec26ce62ce487690becc2f3710bbc7b33338f005ad390db0b986/websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57", size = 182204, upload-time = "2026-07-17T22:51:05.858Z" } wheels = [ @@ -6148,10 +6223,10 @@ version = "17.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", ] From 6e5401ef7524fd69820ce1dcb31aacdaa1dbfdd4 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Tue, 13 Oct 2026 14:44:32 +0200 Subject: [PATCH 8/8] test: pin pytest-asyncio below 1.1 on Python 3.10 on all platforms --- pyproject.toml | 4 ++-- uv.lock | 45 +++++++++++++++++---------------------------- 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c167dccce8..17a7e7bcb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,8 +132,8 @@ dev = [ "pydoc-markdown<5.0.0", # pytest-asyncio 1.1+ runs tests through `backports.asyncio.runner` on Python 3.10, whose process-wide # `contextvars.copy_context` patch can leak the fixture context into the test server thread and hang the test. - "pytest-asyncio<2.0.0; python_version >= '3.11' or sys_platform != 'linux'", - "pytest-asyncio>=1.0.0,<1.1.0; python_version < '3.11' and sys_platform == 'linux'", + "pytest-asyncio<2.0.0; python_version >= '3.11'", + "pytest-asyncio>=1.0.0,<1.1.0; python_version < '3.11'", "pytest-cov<8.0.0", "pytest-rerunfailures<17.0.0", "pytest-timeout<3.0.0", diff --git a/uv.lock b/uv.lock index 965a54e55b..a37fa0281c 100644 --- a/uv.lock +++ b/uv.lock @@ -223,15 +223,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/11/ec5f7f306dd361aa9558f002cbb6acfa1e9ba32fa59b8f53135fbdfa14f1/asyncpg-0.32.0-cp315-cp315t-win_arm64.whl", hash = "sha256:3bbf08c08e31f43be858255614518e78cdfb343571e557e818e9fe736334f4c8", size = 622767, upload-time = "2026-10-06T20:32:24.64Z" }, ] -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, -] - [[package]] name = "backports-zstd" version = "1.8.0" @@ -1152,10 +1143,10 @@ dev = [ { name = "pre-commit" }, { name = "proxy-py" }, { name = "pydoc-markdown" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, - { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, - { name = "pytest-asyncio", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, - { name = "pytest-asyncio", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pytest-asyncio", version = "1.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pytest-asyncio", version = "1.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pytest-cov" }, { name = "pytest-rerunfailures" }, { name = "pytest-timeout" }, @@ -1243,8 +1234,8 @@ dev = [ { name = "proxy-py", specifier = "<3.0.0" }, { name = "pydoc-markdown", specifier = "<5.0.0" }, { name = "pytest", specifier = "<10.0.0" }, - { name = "pytest-asyncio", marker = "python_full_version >= '3.11' or sys_platform != 'linux'", specifier = "<2.0.0" }, - { name = "pytest-asyncio", marker = "python_full_version < '3.11' and sys_platform == 'linux'", specifier = ">=1.0.0,<1.1.0" }, + { name = "pytest-asyncio", marker = "python_full_version < '3.11'", specifier = ">=1.0.0,<1.1.0" }, + { name = "pytest-asyncio", marker = "python_full_version >= '3.11'", specifier = "<2.0.0" }, { name = "pytest-cov", specifier = "<8.0.0" }, { name = "pytest-rerunfailures", specifier = "<17.0.0" }, { name = "pytest-timeout", specifier = "<3.0.0" }, @@ -4216,9 +4207,11 @@ name = "pytest" version = "8.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ + "python_full_version < '3.11' and sys_platform != 'linux'", "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup" }, { name = "iniconfig" }, { name = "packaging" }, @@ -4243,16 +4236,13 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", - "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11' and sys_platform != 'linux'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11' and sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ @@ -4264,6 +4254,7 @@ name = "pytest-asyncio" version = "1.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ + "python_full_version < '3.11' and sys_platform != 'linux'", "python_full_version < '3.11' and sys_platform == 'linux'", ] dependencies = [ @@ -4286,10 +4277,8 @@ resolution-markers = [ "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version == '3.11.*'", - "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11' and sys_platform != 'linux'" }, { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" } }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] @@ -4305,8 +4294,8 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, - { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } wheels = [ @@ -4319,8 +4308,8 @@ version = "16.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, - { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/b0/6b5337f9d59b26b0069ea3d5e863c31dc04b69e51bbfb1cf2ea6328fba87/pytest_rerunfailures-16.7.tar.gz", hash = "sha256:6956ddfb65ca1d07e7e3d99e2c5359d82300f4cb062e6049563bd2c106f72d5c", size = 59666, upload-time = "2026-09-17T07:08:48.871Z" } wheels = [ @@ -4332,8 +4321,8 @@ name = "pytest-timeout" version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, - { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } wheels = [ @@ -4346,8 +4335,8 @@ version = "3.8.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' and sys_platform == 'linux'" }, - { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or sys_platform != 'linux'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [