Skip to main content

asgire

License PyPI PyPI - Downloads PyPI - Python Version CI Codecov

The revamped and modernized drop-in replacement for asgiref.

Same license, same API, but with better code, comprehensive coverage, and active maintenance.

Installation

pip install asgire

The import stays import asgiref — no code changes needed.

Migration

pip uninstall asgiref
pip install asgire

If you need to force Django or other libraries to depend on asgire instead of asgiref with uv, add the following to your pyproject.toml:

[tool.uv]
override-dependencies = ["asgiref ; python_version == '0'"]

This will eliminate all transitive dependencies on asgiref in uv.lock to ensure asgire is the only import asgiref provider.

Performance tips

The cost of sync_to_async / async_to_sync is paid per boundary crossing (a thread hop through the event loop, tens of microseconds), not per line — so the order below is roughly the order of impact.

Django's a-prefixed methods (e.g. aget, acreate) also carry the implicit cost of calling sync_to_async on the ORM call, so they are not free. Each one is a boundary crossing. Consider implementing the DB logic in a sync function, then wrapping it once with sync_to_async (two crossings collapse into one, ~2x faster for cheap queries):

# Slower — every a* method is its own sync_to_async crossing
async def log_login(user_id: int):
    user = await User.objects.aget(id=user_id)
    await AuthLog.objects.acreate(user=user, action="logged in")

# Faster — implement the logic in sync, then wrap it once
@sync_to_async
def log_login(user_id: int):
    user = User.objects.get(id=user_id)
    AuthLog.objects.create(user=user, action="logged in")

The sync body cannot await, so this applies when the block is pure sync work — and as a bonus, transaction.atomic works naturally inside it, which it does not across await boundaries.

Minimize boundary crossings

Cross as few times as possible. The biggest wins come from restructuring, not from micro-optimizing each call.

Batch sync work into one crossing instead of awaiting in a loop:

# N crossings
for row in rows:
    await sync_to_async(row.save)()

# One crossing
@sync_to_async
def save_all(rows):
    for row in rows:
        row.save()

await save_all(rows)

Resolve sync data while you are still in sync context. A common Django antipattern bridges into async and then immediately bounces back to sync for ORM access. Signals run in sync context where the DB connection is already available, so read what you need there and pass plain objects to the async layer:

# Bounces sync -> async -> sync -> async (three+ crossings)
@receiver(post_save, sender=IPAddress)
def on_saved(sender, instance, **kwargs):
    async_to_sync(notify)(instance)

async def notify(instance):
    user = await sync_to_async(lambda: instance.user)()
    organization = await sync_to_async(lambda: instance.organization)()
    await send_email(user, organization)

# Read the ORM data in the sync signal; hand plain objects to async (one crossing)
@receiver(post_save, sender=IPAddress)
def on_saved(sender, instance, **kwargs):
    user = instance.user
    organization = instance.organization
    async_to_sync(notify)(user, organization)

async def notify(user, organization):
    await send_email(user, organization)

Defer I/O triggered by signals. Sending email or calling an external API inside a signal runs it inside the transaction (it can fire even if the transaction later rolls back) and on the request's hot path. Prefer enqueuing the work to run after commit:

@receiver(post_save, sender=IPAddress)
def on_saved(sender, instance, **kwargs):
    user, organization = instance.user, instance.organization
    transaction.on_commit(lambda: notify_task.delay(user.id, organization.id))

Reuse wrappers instead of building them inline

sync_to_async(func) / async_to_sync(func) validate and wrap the callable on construction (~2 µs). Calling them inline rebuilds the wrapper on every call:

# Slower — a fresh wrapper is constructed on every request
async def view():
    return await sync_to_async(do_work)()

# Faster — build the wrapper once and reuse it
do_work_async = sync_to_async(do_work)

async def view():
    return await do_work_async()

Choose thread_sensitive deliberately

sync_to_async defaults to thread_sensitive=True, which runs every call on a single shared thread. That is required for thread-affine code (e.g. the Django ORM or anything using thread-locals), but it serializes calls. For sync work that is independent and thread-safe, pass thread_sensitive=False so calls run concurrently in a thread pool:

# Serialized on one shared thread (safe for thread-affine code)
await sync_to_async(orm_call)()

# Runs concurrently in a pool (use only for independent, thread-safe work)
await sync_to_async(cpu_bound, thread_sensitive=False)()

Development

uv sync
uv run pytest -v
uv run ruff check --fix
uv run ruff format
uv run ty check

Metadata

Release files for asgire 3.13.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for asgire 3.13.0
File Size Uploaded Provenance Actions
asgire-3.13.0.tar.gz 24.1 kB Without provenance Details for asgire-3.13.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for asgire 3.13.0
File Interpreter ABI Platform Actions
asgire-3.13.0-py3-none-any.whl Python 3 none any Details for asgire-3.13.0-py3-none-any.whl

Total release size: 49.6 kB

Release files / asgire-3.13.0.tar.gz

Download URL asgire-3.13.0.tar.gz
Size 24.1 kB
Tags Source
SHA-256 checksum
How to use checksums
9b113a428a6bc75e854e30a247045e3a6ab0ab64d7de79ee8e2a85e30bfb8bc8
BLAKE2b-256 checksum
How to use checksums
1ed0a4afd323123dbde39a201e92fbeed362a17c2ae74fc34583cbab0655f7c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / asgire-3.13.0-py3-none-any.whl

Download URL asgire-3.13.0-py3-none-any.whl
Size 25.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a52dcd5c0e9855755e0b8b61e86e6ed66c1d1d567c504733cbcc2fc49ddd1f5
BLAKE2b-256 checksum
How to use checksums
a62e62e62245299f5b75f10e5e3bac5d6d418c99cb08e36de1bed74922949838
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for asgire 3.13.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page