Skip to main content

The revamped and modernized drop-in replacement for asgiref

Project description

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.

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

asgire-3.12.2.tar.gz (21.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

asgire-3.12.2-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file asgire-3.12.2.tar.gz.

File metadata

  • Download URL: asgire-3.12.2.tar.gz
  • Upload date:
  • Size: 21.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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}

File hashes

Hashes for asgire-3.12.2.tar.gz
Algorithm Hash digest
SHA256 a6c6d057f16fde3896dd3ce916307e0f1925eaf1666d096b7066b906bf3d1bb4
MD5 8d719c015e016a949300717660b81123
BLAKE2b-256 58e3d71ebb66cdc0bc9836bb8ad556dd9ef69eefb7e9a497fda9794ec8d28dbf

See more details on using hashes here.

File details

Details for the file asgire-3.12.2-py3-none-any.whl.

File metadata

  • Download URL: asgire-3.12.2-py3-none-any.whl
  • Upload date:
  • Size: 23.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","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}

File hashes

Hashes for asgire-3.12.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9911f8ae323210a1719e76730b2a4d1806bf81bb79613222849a0260a710a27c
MD5 b2628b66299ecca8de8db36ac50ed162
BLAKE2b-256 461598c12309038e66ee5a51ebca2737678e5a6ed853466960d3bde03e4e92b2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page