# AnyIO: one async API that runs on either asyncio or Trio

> AnyIO sits between your code and the event loop, adding Trio style structured concurrency to asyncio while staying interchangeable with it. What that buys you, and what it costs.

**agronholm/anyio** — High level asynchronous concurrency and networking framework that works on top of either Trio or asyncio

- Repository: https://github.com/agronholm/anyio
- Stars: 2,550 · Forks: 283
- Language: Python
- License: MIT
- Published: 2026-10-06 · Updated: 2026-10-06 · Language: en
- Canonical page: https://hysenlabs.com/projects/agronholm-anyio

## The abstraction is the product, not the networking layer

AnyIO describes itself as an asynchronous networking and concurrency library that works on top of either asyncio or Trio. Read that as a deliberate constraint. The project does not ship its own event loop and it does not ask you to run its runtime. It implements Trio-style structured concurrency on top of asyncio and works alongside Trio's native structured concurrency, then presents one API for both.

The payoff the README describes is portability: applications and libraries written against AnyIO's API run unmodified on either backend. The second claim is the more interesting one for existing codebases, which is incremental adoption. You can bring AnyIO into a library or application bit by bit without a full refactoring, and it blends with the native libraries of whichever backend you chose.

What you get in exchange for the indirection is a feature list the README enumerates in full: task groups, described in Trio terminology as nurseries, high level networking over TCP, UDP and UNIX sockets, a versatile API for byte streams and object streams, inter-task synchronization covering locks, conditions, events, semaphores, object streams and futures, worker threads, subprocesses, signal handling, and asynchronous versions of the functools and itertools modules. Asynchronous file I/O is there too, implemented with worker threads, and Python 3.13 and later get subinterpreter support for code parallelization.

Two networking details are worth pulling out. TCP connections use the Happy Eyeballs algorithm, which the README notes is more reliable than the asyncio implementation on Python 3.8, and UDP sockets get async and await style APIs where asyncio still leaves you with Transports and Protocols.

## Installing AnyIO and choosing a backend

AnyIO is a pure Python package on PyPI, so the install is unremarkable. Trio is an optional extra, which is the correct shape for a library that defaults to asyncio:

```bash
pip install anyio
```

The packaging metadata in pyproject.toml shows what that buys you. The project requires Python 3.10 or newer and classifies itself as Development Status 5 - Production/Stable, with typing marked as Typed. Three runtime dependencies are declared: `exceptiongroup` on Python versions below 3.11 to backport the standard library group type, `idna` at version 2.8 or newer, and `typing_extensions` below Python 3.15.

```toml
[project.optional-dependencies]
trio = ["trio >= 0.32.0"]
```

The classifiers also cover Python 3.10 through 3.15 and carry a beta marker for free-threaded builds, so the project is tracking the interpreter's parallel work rather than waiting on it. Version numbers come from setuptools_scm, which means the installed version tracks the repository tags rather than a hand-edited field.

Nothing in the README shows an application skeleton, and that is not an oversight. AnyIO is a library you call from inside an event loop that something else started, so the entry point belongs to your program or to the web framework you are already using.

## The pytest plugin is the part most projects meet first

AnyIO ships a pytest plugin of its own, and it registers itself through the standard entry point mechanism rather than asking you to add a conftest hook. The declaration is one line in pyproject.toml:

```toml
[project.entry-points]
pytest11 = {anyio = "anyio.pytest_plugin"}
```

Because it is an entry point, installing the package is enough to get async test support, and the plugin also supports asynchronous fixtures. The README notes that it works with the Hypothesis property testing library, and the project's own test dependencies show how the maintainer tests it: blockbuster for call detection, coverage, pytest at version 9 or newer, pytest-mock, pytest-timeout, trustme for generating certificates and psutil for process inspection.

Version 4.15.0 added a `--anyio-mode` command line option as an alternative to the `anyio_mode` ini setting, and fixed auto mode detection so the mode is recognised whether it is set through AnyIO's own mechanism or through `pytest_asyncio`. That is a small change and it points at the real friction in this library: pytest has several competing async plugins, and a project in the middle can end up with more than one driving the loop. The fix means an explicit flag wins regardless of which plugin set it.

## What structured concurrency gives you on asyncio

The core difference between AnyIO and asyncio is scope discipline. Asyncio tasks are created individually and cancelled individually, so it is possible to spawn work, lose the handle to it, and discover at shutdown that something is still running. AnyIO's task groups make the lifetime of child tasks a property of a lexical block, which is the guarantee Trio calls a nursery.

The synchronization primitives follow from that. Locks, conditions, events, semaphores, object streams and futures are all part of the API, and version 4.15.0 added `anyio.Future` specifically to fill a hole: it behaves like `asyncio.Future`, letting a task wait for a value or an exception produced by another task, without that primitive being tied to the asyncio name.

The streams layer is the other structural piece. Byte streams and object streams give you a transport abstraction that both backends implement, which matters if you want a library that can offer a clean protocol type to its callers rather than an asyncio stream protocol. On top of that, 4.15.0 added `amap`, `gather` and `as_completed` helpers for the common fan-out and fan-in patterns, and aligned `anyio.Path` with the standard library `pathlib.Path` by accepting the newer keyword arguments such as `follow_symlinks` on `exists()`, `is_dir()`, `is_file()`, `owner()` and `group()`, plus `newline` on `read_text()`.

This is where the abstraction has a cost worth naming: each of these conveniences has a backend-specific equivalent that may behave differently, and the documentation has to say which. AnyIO's `why` page is the README's own pointer for readers asking why they would use these APIs instead of asyncio's.

## Bug fixes that describe the sharp edges

The 4.14.2 release is worth reading as a list of failure modes rather than a changelog. `ByteReceiveStream.receive()` now raises a `ValueError` when `max_bytes` is not a positive integer. `CapacityLimiter.total_tokens` rejected `float("inf")` outside an event loop because the adapter setter checked for infinity by identity with `math.inf` while every backend setter used `math.isinf()`, so only the exact singleton worked. Both are the shape of bug you get when one API is implemented twice, once as an adapter and once per backend.

The other fixes are more consequential. `to_process.run_sync()` could deadlock when the worker function wrote enough to `sys.stderr` to fill an undrained pipe buffer, and the worker now redirects stderr to `os.devnull` to match the documented behaviour. `TLSStream.wrap()` matched internationalised host names against peer certificates using IDNA 2003 from the standard library rather than IDNA 2008, which could match a name against the wrong certificate. Both are cases where the abstraction was doing security-adjacent work and the fix had to correct it.

The 4.15.1 patch that followed on 2026-09-05 is a compatibility fix allowing direct access to `anyio.*` submodules from the main package even when those submodules were not imported first. That is the sort of thing that breaks an application after a patch release for no visible reason, which is worth knowing about before you go looking for it.

Repository activity is steady rather than dramatic. There are no GitHub releases beyond this 4.x line in recent history, the last push to master was on 2026-09-27, and the open issue count sits above a hundred on a repository this size. Version history is documented separately at anyio.readthedocs.io.

## Where the abstraction shows through

AnyIO covers the intersection of two APIs, so code that reaches past it lands back in backend-specific territory. Loop internals, transports, protocols, scheduler configuration and anything touching `asyncio.all_tasks()` or Trio's low-level nursery internals are outside what a portable program can assume. The README does not pretend otherwise. It says AnyIO can be adopted incrementally and that it blends with the native libraries of your backend, which is a promise about coexistence rather than about erasure.

There is also a single-maintainer reality behind a library that sits underneath much of the modern Python web stack. FastAPI, Starlette and httpx all depend on AnyIO, so its API stability is a dependency risk for projects that did not choose it directly. The mitigation is ordinary: pin the version, read the version history before upgrading a patch, and remember that the project is MIT licensed, which is about as permissive as it gets.

Security reporting goes through Tidelift, which coordinates the fix and disclosure. That is a small operational detail, but it is the difference between a project with a disclosure process and one without, and for a library this widely depended on it is the one that matters most.

## Conclusion

AnyIO earns its place in a library that wants to be neutral about the event loop, or in an application already using FastAPI, Starlette or httpx where it is a transitive dependency you did not choose. It does not replace asyncio inside code that needs loop-specific APIs, and its own documentation, at anyio.readthedocs.io, has to be read alongside the backend docs because AnyIO deliberately documents the intersection rather than the whole surface. Start with a single task group around one piece of your codebase, confirm the pytest plugin handles your fixtures, and pin to a 4.x release: 4.15.1 shipped on 2026-09-05 and the last push to master was on 2026-09-27.

## FAQ

### What are the key differences between AnyIO and asyncio?

AnyIO is a library layered on top of an event loop, not a replacement for one. Where asyncio gives you tasks and primitives individually, AnyIO adds Trio style structured concurrency through task groups, a byte and object stream abstraction, worker threads, subprocess and signal handling, and it runs the same code on either asyncio or Trio.

### What is anyio in Python?

AnyIO is a pure Python library for asynchronous networking and concurrency that supports Python 3.10 and newer under the MIT licence. It ships its own pytest plugin for asynchronous tests and fixtures, so installing the package is enough to run async test suites.

### Do I need to install Trio separately to use AnyIO?

No. AnyIO defaults to asyncio, and Trio is an optional extra declared as `trio >= 0.32.0`. Install the trio extra only if you intend to run your application on the Trio backend.

## Sources

- [agronholm/anyio on GitHub](https://github.com/agronholm/anyio)
- [Issues](https://github.com/agronholm/anyio/issues)
- [License: MIT](https://github.com/agronholm/anyio/blob/master/LICENSE)
- [README](https://github.com/agronholm/anyio/blob/master/README.md)
- [Releases](https://github.com/agronholm/anyio/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/agronholm-anyio
