asgiref is the layer where synchronous and asynchronous Python have to agree
ASGI specification and utilities
At a glance
- What is it?
- asgiref is the package that carries the ASGI specification and the utilities that make mixed sync and async code workable, including AsyncToSync and SyncToAsync, a thread-local replacement that also understands asyncio tasks, a stateless server base class and a WSGI-to-ASGI adapter. It requires Python 3.11 or newer, and its thread rules are specific enough that reading the implementation details matters.
- Who is it for?
- asgiref is the right dependency for any project that has to run synchronous code inside an async server, and its thread-sensitivity defaults are the safe choice rather than a performance mistake, so leave them alone unless you have measured a problem and know your code holds nothing bound to a thread.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- django/asgiref1,634 ★ · +1
| Repository | 2026-10-16 | 2026-10-23 |
|---|---|---|
| django/asgiref | 1.6k | 1.6k |
1 Stars in the last 7 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
ASGI is a specification first, a library second
asgiref carries the ASGI standard itself, the description says it is the ASGI specification and utilities, and it is positioned as the asynchronous successor to WSGI. The standard is how an asynchronous Python web app and an asynchronous Python server talk to each other, and the package ships the base libraries that make that possible. The practical reason a specification needs a reference implementation is that the awkward parts of async Python are in the seams. An application that was written synchronously has to run inside an event loop, a database driver has to be called without blocking the loop, and code that assumed one thread now runs in several. asgiref is the reference implementation of those answers, and it is what Channels builds on, so its behaviour is de facto part of how the wider Python web stack behaves. The audience is framework and library authors, and application developers who inherit asgiref through their framework and need to understand why their sync code runs where it does.
AsyncToSync and SyncToAsync, and the thread each one chooses
The two wrappers are the heart of the package, and the README describes them precisely. AsyncToSync lets a synchronous subthread stop and wait while the async function is called on the main thread's event loop, and then returns control to the thread when the async function is finished. SyncToAsync does the opposite, letting async code call a synchronous function that is run in a threadpool, with control returned to the async coroutine when the synchronous function completes. The stated purpose is to make it easier to call synchronous APIs from async code and asynchronous APIs from synchronous code, so that a codebase can move from one style to the other gradually. The canonical example is Channels, which wraps the synchronous Django view system with SyncToAsync so it can run inside the asynchronous ASGI server. The wrappers are thin in appearance and thick in behaviour, because the thread that a piece of code ends up on is not something the wrapper can decide arbitrarily.
thread_sensitive defaults to on, and turning it off moves the risk
There is one setting that matters more than the others. By default, sync_to_async will run all synchronous code in the program in the same thread, for safety reasons. You can disable this for more performance with @sync_to_async(thread_sensitive=False), but the README warns to make sure your code does not rely on anything bound to threads, like database connections, when you do. The reasoning is in the implementation details section, where the restrictions are spelled out. All synchronous code called through SyncToAsync and marked with thread_sensitive should run in the same thread as each other, and if the outer layer of the program is synchronous, the main thread. And if a thread already has a running async loop, AsyncToSync cannot run things on that loop if it is blocked on synchronous code that is above you in the call stack. The first compromise, running thread_sensitive code in the same thread rather than in a sub-thread, immediately collides with the second restriction, so the real solution is a variant of ThreadPoolExecutor that executes thread_sensitive code on the outermost synchronous thread, either the main thread or a single spawned sub-thread. Turning the flag off is a performance decision with a correctness cost, and the default is the right default.
A Local that survives the move between threads and tasks
The package also replaces threading.local. The replacement works with both threads and asyncio Tasks, and it does something the standard library class cannot, since it proxies values through from a task-local context to a thread-local context when you use sync_to_async to run things in a threadpool, and the other way round for async_to_sync. That proxying is the whole point, because the same logical request can be in a task in one place and a thread in another, and a value stored on a plain threading.local would simply vanish. If you want true thread and task safety instead of the proxying behaviour, the documentation says you can set thread_critical on the Local object to ensure this. So there are two modes, a convenient one and a strict one, and the strict one is opt-in. The package keeps a micro-benchmark suite for this module in tests/test_benchmark_local.py, which is skipped unless pytest-benchmark or pytest-codspeed is installed and is not part of the regular CI run, and the documentation is careful to say that benchmark numbers only make sense when compared on the same machine.
WsgiToAsgi makes an old app look like a new one
The adapter lets you wrap a WSGI application so that it appears as a valid ASGI application, and the usage is a single line:
asgi_application = WsgiToAsgi(wsgi_application)The WSGI application is run in a synchronous threadpool, and the wrapped ASGI application accepts http class messages. That is the whole mechanism, and it is what allows a legacy application to be served by an async server during a migration rather than after one. The caveat is stated in the same breath. Not all extended features of WSGI may be supported, and the example given is file handles for incoming POST bodies. That is the difference between this adapter and a native async rewrite. The adapter preserves the semantics of the old application, including the parts of WSGI that are awkward to emulate, and the price is that those semantics are preserved rather than improved. If your application streams request bodies or relies on other extensions, that price is worth measuring before you rely on the adapter.
StatelessServer and the connection scope idea
The server base class is the least known part of the package. StatelessServer provides all the hard work of writing a stateless server, which the README defines as one that does not handle direct incoming sockets but instead consumes external streams or sockets to work out what is happening. The example given is a chatbot server that connects out to a central chat server and provides a connection scope per user chatting to it. There is only one actual connection, but the server has to separate things into several scopes to make writing the code easier. The repository points to frequensgi as an example of this pattern in use. The idea is a separation of concerns that is familiar from other asynchronous systems, where the transport is a single stream and the logical sessions multiplexed over it are scoped explicitly. It matters for anyone writing a client of a message bus or a chat protocol, because the alternative is inventing a scope system and getting the cancellation semantics wrong, and the server base class ships that part already solved.
Testing, benchmarking, releasing, and the Python floor
The contributor workflow is spelled out in the README. To run the tests you install the tests extra and run pytest:
cd asgiref/
pip install -e .[tests]
pytestThe benchmark suite for asgiref.local is separate, skipped by default and run through tox with tox -e benchmark, saving a baseline with --benchmark-save=before and comparing later with --benchmark-compare. The documentation is built with Sphinx, and there is a sphinx-autobuild option for a live reload server. Releasing means adding details to CHANGELOG.txt, updating the version number in asgiref/__init__.py, and then:
python -m build
twine upload dist/*
rm -r asgiref.egg-info distThe project requires Python 3.11 or higher, which is a real constraint for projects still supporting older interpreters. The repository is not archived, the last push was on 2026-10-14, and it has no GitHub releases, so the PyPI version and the changelog are the version markers. The licence is BSD 3-Clause, which is permissive and allows use inside a closed-source product provided the copyright notice and disclaimer are kept. The repository also carries a specs/ directory, which is where the ASGI specification itself lives, and the contributing docs point at the Channels repository.
Editorial conclusion
asgiref is the right dependency for any project that has to run synchronous code inside an async server, and its thread-sensitivity defaults are the safe choice rather than a performance mistake, so leave them alone unless you have measured a problem and know your code holds nothing bound to a thread. It is the wrong tool for a project that has committed fully to async, since then you pay for the compatibility layer without needing it, and it is the wrong tool on Python 3.10 or older, which it no longer supports. Before adopting it, read the Synchronous code and threads section rather than the summary, and if you use the WSGI adapter, verify that the WSGI features you rely on survive the conversion, since the documentation is explicit that not all of them are supported.
Frequently asked questions
What does asgiref provide?
It includes the ASGI base libraries, the sync and async function wrappers in asgiref.sync, the server base classes in asgiref.server, and a WSGI-to-ASGI adapter in asgiref.wsgi. It also ships a drop-in replacement for threading.local that works with threads and asyncio Tasks.
What does thread_sensitive do in asgiref.sync?
By default sync_to_async runs all synchronous code in the same thread, for safety reasons, so that thread_sensitive code can rely on things bound to a thread such as database connections. Setting thread_sensitive=False gives more performance by allowing a threadpool, at the cost of that guarantee.
How do I run a WSGI application under an ASGI server?
Wrap it with the adapter, asgi_application = WsgiToAsgi(wsgi_application). The WSGI application is run in a synchronous threadpool and the result accepts http class messages, but the documentation warns that not all extended WSGI features are supported, with file handles for incoming POST bodies given as an example.
Which Python versions does asgiref support?
asgiref requires Python 3.11 or higher. The package is published on PyPI, the project has no GitHub releases, and the version number is kept in asgiref/__init__.py with details in CHANGELOG.txt.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/django-asgiref)Star history chart for your README
[](https://hysenlabs.com/projects/django-asgiref)Was this useful?