async-timeout

An asyncio-compatible timeout context manager for cancelling slow Python coroutines without asyncio.wait_for().

Library
PyPI
v5.0.1
572stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
47/100Fair
Development Activity16
Maintenance20
Community72
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture78
Code Quality82
Innovation55
Learning Curve85

async-timeout is a small, focused library from the aio-libs organization that provides timeout() and timeout_at() context managers for bounding how long a block of asyncio code is allowed to run. Instead of wrapping a coroutine call in asyncio.wait_for(), you wrap the code directly in async with timeout(delay):, which schedules a cancellation at the deadline and raises asyncio.TimeoutError once the block exits if the deadline was reached. Because it doesn’t spin up a new task the way wait_for() does, it is noticeably faster and composes more naturally around multi-statement blocks of async code rather than a single awaitable.

The library predates Python’s own asyncio.timeout() (added in 3.11) and was in fact the design that inspired it. As of version 5.0, async-timeout detects the running Python version: on 3.11+ its Timeout class subclasses asyncio.Timeout directly and layers on the older async-timeout-specific method names (shift(), update(), reject(), .expired as both property and callable) for backward compatibility, while on older Pythons it falls back to its original from-scratch implementation. The maintainers now explicitly mark the project deprecated in favor of the standard library’s asyncio.timeout(), recommending it purely as a compatibility shim for code that needs to run on both old and new Python versions.

Despite the deprecation notice, it remains one of the most widely depended-on packages in the Python async ecosystem — it’s a transitive dependency of aiohttp and dozens of other async libraries — which keeps its download numbers high even as direct new usage declines in favor of the stdlib equivalent.

What You Get

  • timeout(delay) - a context manager that cancels the wrapped block after delay seconds (or never, if delay is None)
  • timeout_at(deadline) - schedules cancellation at an absolute point on the event loop’s clock instead of a relative delay
  • Rescheduling API - shift(), update(), and reschedule() let you extend or change a pending deadline while the block is still running
  • Dual-mode implementation on Python 3.11+ - subclasses the stdlib’s own asyncio.Timeout and adds the legacy method names on top, so old and new code paths share one engine
  • Zero-task overhead - avoids the extra asyncio.Task that wait_for() creates internally, making it measurably cheaper for high-frequency timeout use

Common Use Cases

  • Bounding outbound HTTP calls - wrapping an aiohttp or httpx request in a hard deadline so a stalled upstream service can’t hang a request handler indefinitely
  • Guarding multi-step async blocks - applying a single timeout across several sequential await calls that together must finish within a budget, which wait_for() can’t do cleanly for more than one awaitable
  • Cross-version compatibility shims - libraries that must support both pre-3.11 and 3.11+ Python import async_timeout conditionally so they get one consistent API on every supported interpreter
  • Rescheduling deadlines mid-operation - long-running jobs that extend their own timeout via shift()/update() once they’ve made verified progress, rather than restarting the whole timeout window

Under The Hood

Architecture The entire implementation lives in a single module, async_timeout/__init__.py, exposing two factory functions (timeout(), timeout_at()) that both construct and return a Timeout instance holding a deadline and an event-loop reference. On Python 3.11+, Timeout subclasses asyncio.Timeout directly and layers legacy method names (shift, update, reject, a callable-and-truthy expired) on top of the stdlib’s own scheduling logic via a small _Expired wrapper class. On older Pythons, Timeout is a self-contained state machine (INIT -> ENTER -> TIMEOUT/EXIT) that schedules a loop.call_at() callback on entry, cancels the current task if the callback fires before exit, and converts the resulting CancelledError into asyncio.TimeoutError inside __aexit__. There is no external dependency graph to speak of — the only import is the standard asyncio module itself.

Tech Stack Pure-Python, zero runtime dependencies, requiring Python 3.8+. Packaging is classic setuptools (setup.cfg + pyproject.toml build-system declaration), versioned via attr: async_timeout.__version__. Type hints are exhaustive and a py.typed marker file is shipped so consumers get typed inference; mypy is configured via .mypy.ini and run in CI. Release notes are managed with towncrier reading fragment files from CHANGES/.

Code Quality Tests live in a single tests/test_timeout.py file using pytest with pytest-asyncio in strict mode, and CI runs a coverage-branch report (--cov=async_timeout --cov-branch) alongside Codecov upload. The GitHub Actions workflow runs a separate lint job (pre-commit hooks), the test suite across supported Python versions, and a CodeQL security scan; Dependabot is configured with auto-merge for its own PRs. The Timeout class uses __slots__ on the pre-3.11 code path and explicit _State enum transitions rather than ad hoc booleans, and every public method raises RuntimeError with a descriptive message when called in an invalid state (e.g. rescheduling after exit) rather than failing silently.

What Makes It Unique Unlike most libraries, async-timeout’s own README leads with a deprecation notice: it explicitly says it has been “upstreamed” into Python 3.11’s asyncio.timeout(), and its own current implementation reuses that stdlib class under the hood on new Python versions rather than maintaining a parallel logic path. Its lasting relevance is less about novel functionality and more about serving as a drop-in compatibility layer that lets libraries and applications write one code path that behaves identically whether the interpreter has native asyncio.timeout() or not — a role that has kept it as a transitive dependency of a large share of the async Python ecosystem (notably aiohttp) even after its functionality moved into the language itself.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search