# Asyncer: four functions for mixing sync and async Python

> Asyncer is a small AnyIO-based utility library from the author of FastAPI. It is aimed at developers who want async and blocking code to coexist without hand-rolling thread offloads, and it is honest about being a beta with a tiny surface area.

**fastapi/asyncer** — Asyncer, async and await, focused on developer experience.

- Repository: https://github.com/fastapi/asyncer
- Website: https://asyncer.tiangolo.com/
- Stars: 2,499 · Forks: 90
- Language: Python
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/fastapi-asyncer

## The sync-async boundary is where the bugs live

Most Python codebases that go async do not go async all at once. A request handler becomes async, but the database driver, the image library, the internal SDK someone wrote in 2019, and the file parser all stay synchronous. The moment those two worlds meet, you have a decision to make: call the blocking function directly and stall the event loop, or push it to a thread and pay the ceremony of getting a result back, propagating exceptions, and keeping the type checker happy.

Asyncer targets that second option. The README describes it as "a small library built on top of AnyIO" with "a small number of utility functions that allow working with async, await, and concurrent code in a more convenient way." The audience is narrow and specific: developers who already understand async Python and are tired of the boilerplate at the boundary. The stated goal is developer experience, meaning editor autocompletion, inline errors, and type checking with tools like mypy, rather than new concurrency primitives. If you have never written an async function, this library will not teach you, and it is not meant to.

## What asyncify actually does under the hood

The README's own sneak preview is the clearest statement of the mechanism. You take an ordinary synchronous function, wrap it with asyncify(), and call the result with await. The README says asyncify "will use AnyIO underneath to do the smart thing, avoid blocking the main async event loop, and run the sync/blocking function in a worker thread." So the data flow is: your coroutine awaits the wrapper, AnyIO dispatches the blocking call to a worker thread, the event loop stays free to run other tasks, and the return value comes back as an awaitable result.

The example in the README is a function that calls time.sleep(1) and returns a greeting. Because the sleep happens in a worker thread rather than on the loop, other coroutines can make progress during that second. That is the entire value proposition in one function, and it is worth being precise about the trade-off: you have not made the blocking code faster, you have made it non-blocking from the loop's perspective, at the cost of a thread. The README also notes that the library is built on AnyIO, which means the same code runs on asyncio and on Trio, a detail the project's classifiers confirm with Framework :: AsyncIO and Framework :: Trio entries. The repository lists anyio >=3.4.0,<5.0 and sniffio >=1.1 as runtime dependencies, so AnyIO arrives with the install rather than as an optional extra.

## Installing Asyncer and calling sync code from async code

The README's installation section assumes uv. It says to install uv first, then add Asyncer to your project:

```bash
uv add asyncer
```

The README shows the command completing with a progress indicator, and notes that pip users should install asyncer inside a virtual environment, pointing to the installation guide in the docs for the alternative steps. Because asyncer depends on anyio, you do not need to add AnyIO yourself; it comes along.

For a first real use, the README's preview is the shortest path. Create a file, import anyio and asyncify, define a blocking function, and await the wrapped version inside a coroutine run by anyio.run:

```python
import time

import anyio
from asyncer import asyncify


def do_sync_work(name: str):
    time.sleep(1)
    return f"Hello, {name}"


async def main():
    message = await asyncify(do_sync_work)(name="World")
    print(message)


anyio.run(main)
```

Running this prints Hello, World after roughly a second. The important part is not the output but where the sleep happened: in a worker thread, not on the event loop. Note the call shape. asyncify(do_sync_work) produces a callable, and you invoke it with keyword arguments, then await it. The README highlights autocompletion for both arguments and return values as a design goal, so in a supported editor you should see name suggested as you type the keyword and the return type surfaced on the await. The README says the library is "just 4 functions," so asyncify is one of a very small set; the tutorial at asyncer.tiangolo.com is where the project documents the rest, and the README does not enumerate them all inline.

## The beta label and the pinning advice are not decoration

The README answers its own "Can I Use It?" question with a yes, followed by a paragraph of caution that is unusual to see stated so plainly. It calls Asyncer "a very small library" with "things that can change and improve in the future," and then gives direct instructions: pin the exact Asyncer version for your project, have tests, and upgrade the version once you know the new version continues to work. The pyproject.toml classifier Development Status :: 4 - Beta backs that up.

There is a second limitation that is easy to miss. The README says that if you do not want asyncer as a dependency, you can copy the main file and use those functions directly, but warns that in that case you will not get updates easily. That is a real fork in the road, not a marketing aside: vendoring gives you zero dependency risk and zero upgrade path. The library is also opinionated by its author's own admission, described as reflecting a "very opinionated and subjective point of view." If your team's conventions differ from those opinions, the four functions will not bend to accommodate you. And if what you actually need is task groups, cancellation scopes or structured concurrency, you want AnyIO itself, not a convenience layer over it. Asyncer does not claim to be that layer.

## Asyncer versus using AnyIO directly

The honest alternative is AnyIO, which is not a competitor so much as the substrate. Asyncer depends on anyio >=3.4.0,<5.0, and the README states plainly that the library is built on top of it. AnyIO's own to_thread.run_sync is the mechanism that asyncify wraps, and AnyIO also gives you task groups, timeouts, memory object streams, capacity limiters and the full structured concurrency toolkit.

The difference in approach is one of scope and ergonomics. AnyIO is a complete async abstraction layer that runs on both asyncio and Trio and expects you to learn its idioms. Asyncer is a thin convenience layer that trades breadth for a smaller API and, per its stated goal, better editor support and type checking around the sync-async boundary. If you need to offload one blocking call in an existing asyncio application, reaching for AnyIO means adopting a broader framework for a narrow problem. If you are already all-in on AnyIO and want its task groups, Asyncer adds little beyond the wrapper. The two are not mutually exclusive, and the dependency direction makes that clear: installing Asyncer gives you AnyIO either way.

## Maintenance, releases and the MIT licence

The repository's last push was on 2026-09-02, and the most recent release listed is 0.0.18 from 2026-06-25, following 0.0.17 and 0.0.16 in February 2026. The version numbers are still in the 0.0.x range, which is consistent with the beta classifier and the README's warning that things can change. The changelog lives at asyncer.tiangolo.com/release-notes/, linked from the project's pyproject.toml under [project.urls], so upgrade decisions have a documented place to look.

The licence is MIT, declared both in the README's License section and in pyproject.toml with license = "MIT" and license-files = ["LICENSE"]. MIT is permissive and imposes essentially no conditions beyond preserving the notice, which matters if you are considering the README's suggestion to copy the main file into your own project. That said, this is not legal advice, and if you vendor the code you should read the LICENSE file in the repository yourself. On upgrade cost, the README's own framing is the most useful data point: it is just four functions, so even a breaking refactor would be small. The recommended practice is to pin the exact version, which means upgrades are deliberate rather than automatic, and the project expects you to run your tests before moving forward. The declared floor is requires-python >=3.10, with classifiers through Python 3.14, and typing_extensions is pulled in conditionally for Python versions below 3.15 to provide Unpack.

## Conclusion

Adopt Asyncer if you already build on AnyIO or asyncio and keep tripping over the boundary between blocking calls and the event loop; the four functions are small enough to read in one sitting and the README explicitly tells you to pin the exact version and keep tests around upgrades. Do not adopt it as a general concurrency framework, and do not expect it to replace task orchestration, cancellation design or structured concurrency patterns you would get from AnyIO directly. Before committing, verify two things: that your Python version satisfies requires-python >=3.10, and that the specific function you need behaves as the tutorial describes against your installed AnyIO version, since anyio is the only runtime dependency that matters.

## FAQ

### What is Asyncer and who is it for?

Asyncer is a small Python library built on top of AnyIO that provides a handful of utility functions for working with async, await and concurrent code. It is aimed at developers who already work with async Python and want a more convenient way to mix synchronous blocking code with async code. The README describes the goal as improving developer experience through better autocompletion, inline errors and type checking support.

### How do I install Asyncer?

The README says to install uv first and then run uv add asyncer in your project. If you prefer pip, it says to install asyncer inside a virtual environment and points to the installation guide in the documentation for the alternative steps.

### What does asyncify do in Asyncer?

asyncify wraps a synchronous function so you can await it from async code. According to the README, it uses AnyIO underneath to run the blocking function in a worker thread, which avoids blocking the main async event loop.

### Is Asyncer stable enough for production?

The README answers yes but advises caution: it calls Asyncer a very small library where things can change, and tells you to pin the exact Asyncer version, keep tests, and upgrade only once you know the new version works. The package classifier is Development Status :: 4 - Beta and versions are still in the 0.0.x range.

### Can I use Asyncer without adding it as a dependency?

Yes. The README says you can copy the main file and use those functions directly since it is quite small, but warns that in that case you will not get updates easily. The project is MIT licensed, so read the LICENSE file in the repository if you go that route.

## Sources

- [fastapi/asyncer on GitHub](https://github.com/fastapi/asyncer)
- [License: MIT](https://github.com/fastapi/asyncer/blob/main/LICENSE)
- [Project website](https://asyncer.tiangolo.com/)
- [README](https://github.com/fastapi/asyncer/blob/main/README.md)
- [Releases](https://github.com/fastapi/asyncer/releases)

---

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