From f99ffbb1d114db23755f3ae4b40325f8e3149ce8 Mon Sep 17 00:00:00 2001 From: Enrique Alonso Date: Tue, 22 Sep 2026 05:22:49 +0000 Subject: [PATCH 1/2] chore: only depend on async-timeout below Python 3.11 Closes #2052. asyncio.timeout is stdlib from 3.11, so on 3.11+ the package no longer needs the backport; the dependency gains a python_full_version < '3.11' marker instead of applying to everyone. SharedTimeout, the only consumer, goes through a single timeout_ctx binding picked at import time, so the call sites are unchanged. asyncio.Timeout raises TimeoutError, a subclass of asyncio.TimeoutError, which is what the existing tests already expect via pytest.raises(asyncio.TimeoutError). Verified on 3.11: tests/unit/_utils 319 passed; full unit suite 2473 passed with only 4 pre-existing failures that reproduce identically on clean master (browser/playwright environment issues, unrelated). ruff check and format clean. uv lock re-resolved: async-timeout is now 3.10-only. --- pyproject.toml | 2 +- src/crawlee/_utils/time.py | 16 ++++++++++++---- uv.lock | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 14cafefaa2..ffe634cac5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ keywords = [ "scraping", ] dependencies = [ - "async-timeout>=5.0.1", + "async-timeout>=5.0.1; python_full_version < '3.11'", "cachetools>=5.5.0", "colorama>=0.4.0", "impit>=0.13.2", diff --git a/src/crawlee/_utils/time.py b/src/crawlee/_utils/time.py index 10d6fd3d8d..9b8466fd4f 100644 --- a/src/crawlee/_utils/time.py +++ b/src/crawlee/_utils/time.py @@ -1,17 +1,25 @@ from __future__ import annotations +import asyncio +import sys import time from contextlib import contextmanager from dataclasses import dataclass from datetime import timedelta from typing import TYPE_CHECKING -from async_timeout import Timeout, timeout - if TYPE_CHECKING: from collections.abc import Iterator from types import TracebackType +if sys.version_info >= (3, 11): + timeout_ctx = asyncio.timeout +else: + # async-timeout backports asyncio.timeout for Python 3.10; drop once the + # project floor is 3.11 (its Timeout also raises TimeoutError, a subclass + # of asyncio.TimeoutError, on expiry). + from async_timeout import timeout as timeout_ctx # type: ignore[no-redef] + _SECONDS_PER_MINUTE = 60 _SECONDS_PER_HOUR = 3600 @@ -46,7 +54,7 @@ class SharedTimeout: def __init__(self, timeout: timedelta) -> None: self._remaining_timeout = timeout - self._active_timeout: Timeout | None = None + self._active_timeout: asyncio.Timeout | None = None self._activation_timestamp: float | None = None async def __aenter__(self) -> timedelta: @@ -54,7 +62,7 @@ async def __aenter__(self) -> timedelta: raise RuntimeError('A shared timeout context cannot be entered twice at the same time') self._activation_timestamp = time.monotonic() - self._active_timeout = new_timeout = timeout(self._remaining_timeout.total_seconds()) + self._active_timeout = new_timeout = timeout_ctx(self._remaining_timeout.total_seconds()) await new_timeout.__aenter__() return self._remaining_timeout diff --git a/uv.lock b/uv.lock index a02a8fe411..435b4cf983 100644 --- a/uv.lock +++ b/uv.lock @@ -980,7 +980,7 @@ name = "crawlee" version = "1.10.2" source = { editable = "." } dependencies = [ - { name = "async-timeout" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, { name = "cachetools" }, { name = "colorama" }, { name = "impit" }, @@ -1138,7 +1138,7 @@ requires-dist = [ { name = "apify-fingerprint-datapoints", marker = "extra == 'httpx'", specifier = ">=0.0.2" }, { name = "apify-fingerprint-datapoints", marker = "extra == 'playwright'", specifier = ">=0.0.2" }, { name = "apify-fingerprint-datapoints", marker = "extra == 'stagehand'", specifier = ">=0.0.2" }, - { name = "async-timeout", specifier = ">=5.0.1" }, + { name = "async-timeout", marker = "python_full_version < '3.11'", specifier = ">=5.0.1" }, { name = "asyncpg", marker = "extra == 'sql-postgres'", specifier = ">=0.24.0" }, { name = "beautifulsoup4", extras = ["lxml"], marker = "extra == 'beautifulsoup'", specifier = ">=4.12.0" }, { name = "browserforge", marker = "extra == 'adaptive-crawler'", specifier = ">=1.2.4" }, From 395fa9cc427013b7d6b83445ef2d289e4eaa6433 Mon Sep 17 00:00:00 2001 From: enriquealonso01 Date: Wed, 23 Sep 2026 13:21:37 +0000 Subject: [PATCH 2/2] docs: mark async-timeout backport as TODO linking #2052 (review feedback) --- src/crawlee/_utils/time.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/crawlee/_utils/time.py b/src/crawlee/_utils/time.py index 9b8466fd4f..c6042916a7 100644 --- a/src/crawlee/_utils/time.py +++ b/src/crawlee/_utils/time.py @@ -15,9 +15,9 @@ if sys.version_info >= (3, 11): timeout_ctx = asyncio.timeout else: - # async-timeout backports asyncio.timeout for Python 3.10; drop once the - # project floor is 3.11 (its Timeout also raises TimeoutError, a subclass - # of asyncio.TimeoutError, on expiry). + # TODO: `async-timeout` backports `asyncio.timeout` for Python 3.10; drop once the + # project floor bump to 3.11 + # https://github.com/apify/crawlee-python/issues/2052 from async_timeout import timeout as timeout_ctx # type: ignore[no-redef] _SECONDS_PER_MINUTE = 60