Warning
Since Django 4.1 (2022), built-in async support was added. This package is now archived and redundant, please use the officially maintained APIs for async Django projects. Relevant docs:
This package includes components and utilities that makes django *usable* in async python, such as:
- Async model mixin
s(fully typed),asgimod.mixins
. - Async managers and querysets (fully typed),
asgimod.db
. - Typed
sync_to_async
andasync_to_sync
wrappers,asgimod.sync
.
- Does this support foreign relation access: YES.
- Does this allow queryset chaining: YES.
- Does this allow queryset iterating, slicing and indexing: YES.
- Does this affect default model manager functionality: NO, because it’s on another classproperty
aobjects
. - Is everything TYPED: YES, with the only exception of function parameters specification on Python<3.10 since PEP 612 is being released on 3.10.
- Django >= 3.0
- Python >= 3.8
pip install asgimod
The documentation uses references from these model definitions:
class Topping(AsyncMixin, models.Model):
name = models.CharField(max_length=30)
class Box(AsyncMixin, models.Model):
name = models.CharField(max_length=50)
class Price(AsyncMixin, models.Model):
amount = models.DecimalField(decimal_places=2, max_digits=10)
currency = models.CharField(max_length=16, default="usd")
class Pizza(AsyncMixin, models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
box = models.ForeignKey(Box, null=True, on_delete=models.SET_NULL)
price = models.OneToOneField(Price, on_delete=models.CASCADE)
and the following TypeVar:
T = TypeVar("T", bound=models.Model)
This mixin adds async capabilities to the model class and instances:
aobjects
full featured async manager.asave
,adelete
async equivalents ofsave
anddelete
.a(.*)
async foreign relations access.
Import:
from asgimod.mixins import AsyncMixin
Usage:
class SampleModel(AsyncMixin, models.Model):
sample_field = models.CharField(max_length=50)
Extends from models.Model
, uses metaclass AsyncMixinMeta
(extended from models.ModelBase
).
Returns an instance of AsyncManager
. Async equivalent of Model.objects
.
asyncmethod asave(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)
-> None
Async equivalent of Model.save
Async equivalent of Model.delete
There are 3 possible returns from an async foreign relation access.
AsyncManyToOneRelatedManager
: Result of a reverse many to one relation access.AsyncManyToManyRelatedManager
: Result of a many to many relation access (both forward and reverse access).Awaitable[T]
: Result of a one to one relation access or a forward many to one relation access. Returns an awaitable withT
return (T
being the type of the foreign object).
To access a foreign relation in async mode, add the a
prefix to your sync access attribute. Using the models defined for this documentation, examples:
price = await Price.aobjects.get(id=1)
pizza = await Pizza.aobjects.get(id=1)
weird_pizza = await Pizza.aobjects.get(id=2)
bacon = await Topping.aobjects.get(id=1)
mushroom = await Topping.aobjects.get(id=2)
medium_box = await Box.aobjects.get(id=1)
# one to one rel & forward many to one rel
await pizza.aprice
await price.apizza
await price.abox
# reverse many to one rel
await medium_box.apizza_set.all().get(id=1)
await medium_box.apizza_set.filter(id__gt=1).order_by("name").count()
await medium_box.apizza_set.add(weird_pizza)
await medium_box.apizza_set.clear()
# forward many to many rel
await pizza.atoppings.all().exists()
await pizza.atoppings.add(bacon, mushroom)
await bacon.atoppings.filter(name__startswith="b").exists()
await pizza.atoppings.remove(bacon)
await pizza.atoppings.clear()
# reverse many to many rel
await mushroom.apizza_set.all().exists()
await mushroom.apizza_set.add(pizza)
await mushroom.apizza_set.set([pizza, weird_pizza])
As you have guessed, these attributes are not defined in code, and thus they are not typed, well, here's the fix:
class Topping(AsyncMixin, models.Model):
name = models.CharField(max_length=30)
apizza_set: AsyncManyToManyRelatedManager["Pizza"]
class Box(AsyncMixin, models.Model):
name = models.CharField(max_length=50)
apizza_set: AsyncManyToOneRelatedManager["Pizza"]
class Price(AsyncMixin, models.Model):
amount = models.DecimalField(decimal_places=2, max_digits=10)
currency = models.CharField(max_length=16, default="usd")
apizza: "Pizza"
Async equivalent managers and querysets. All async managers classes are only alias to their respective querysets classes. Such alias exists for user friendliness and better field typings. If you need other methods and attributes unique to managers, use objects
instead.
Import:
from asgimod.db import (
AsyncQuerySet,
AsyncManyToOneRelatedQuerySet,
AsyncManyToManyRelatedQuerySet,
AsyncManager,
AsyncManyToOneRelatedManager,
AsyncManyToManyRelatedManager
)
Async iterator over an AsyncQuerySet[T]
using async for
syntax. The type of the item evaluated queryset depends on the query made, for return type of each query please refer to the official Django QuerySet API references.
async for price in Price.aobjects.filter(currency="usd"):
self.assertEqual(price.currency, "usd")
Slicing and indexing over an AsyncQuerySet[T]
using []
syntax.
Slicing an AsyncQuerySet[T]
will return a new AsyncQuerySet[T]
, slicing using steps is not allowed, as it would evaluate the internal sync QuerySet
and raises SynchronousOnlyOperation
.
prices = await Price.aobjects.all()[:2].eval()
prices = await Price.aobjects.all()[1:2].eval()
prices = await Price.aobjects.all().order_by("-amount")[1:].eval()
Indexing an AsyncQuerySet[T]
will return an Awaitable[T | Tuple | datetime | date | Any]
(return of the awaitable depends on the query, for return type of each query please refer to the official Django QuerySet API references).
price = await Price.aobjects.all()[0]
price = await Price.aobjects.all()[:5][0]
price = await Price.aobjects.filter(amount__gte=Decimal("9.99"))[0]
Returns f"<AsyncQuerySet [...{self._cls}]>"
.
Returns f"<AsyncQuerySet [...{self._cls}]>"
.
Raises NotImplementedError
, AsyncQuerySet
does not support __len__()
, use .count()
instead.
Raises NotImplementedError
, AsyncQuerySet
does not support __bool__()
, use .exists()
instead.
AsyncQuerySet[T] & AsyncQuerySet[T] -> AsyncQuerySet[T]
# async qs for prices amount > 19.99
qa = Price.aobjects.filter(amount__gt=Decimal("2.99"))
qb = Price.aobjects.filter(amount__gt=Decimal("19.99"))
qs = qa & qb
AsyncQuerySet[T] | AsyncQuerySet[T] -> AsyncQuerySet[T]
# async qs for prices with usd and eur currency
qa = Price.aobjects.filter(currency="usd")
qb = Price.aobjects.filter(currency="eur")
qs = qa | qb
Returns the item on index val
of an AsyncQuerySet[T]
. This method is used by __getitem__
internally. The return type depends on the query, for return type of each query please refer to the official Django QuerySet API references.
Returns the evaluated AsyncQuerySet[T]
in a list. Equivalent of sync_to_async(list)(qs: QuerySet[T])
. The item type of the list depends on the query, for return type of each query please refer to the official Django QuerySet API references.
toppings = await Topping.aobjects.all().eval()
toppings_start_with_B = await Topping.aobjects.filter(name__startswith="B").eval()
Used for building queries. These methods are NOT async, it will not connect to the database unless evaluated by other methods or iterations. For return type and in-depth info of each method please refer to the official Django QuerySet API references.
Equivalent of models.Manager.filter
and QuerySet.filter
.
Equivalent of models.Manager.exclude
and QuerySet.exclude
.
Equivalent of models.Manager.annotate
and QuerySet.annotate
.
Equivalent of models.Manager.alias
and QuerySet.alias
.
Equivalent of models.Manager.order_by
and QuerySet.order_by
.
Equivalent of models.Manager.reverse
and QuerySet.reverse
.
Equivalent of models.Manager.distinct
and QuerySet.distinct
.
Equivalent of models.Manager.values
and QuerySet.values
.
Equivalent of models.Manager.values_list
and QuerySet.values_list
.
Equivalent of models.Manager.dates
and QuerySet.dates
.
Equivalent of models.Manager.datetimes
and QuerySet.datetimes
.
Equivalent of models.Manager.none
and QuerySet.none
.
Equivalent of models.Manager.all
and QuerySet.all
.
Equivalent of models.Manager.union
and QuerySet.union
.
Equivalent of models.Manager.intersection
and QuerySet.intersection
.
Equivalent of models.Manager.difference
and QuerySet.difference
.
Equivalent of models.Manager.select_related
and QuerySet.select_related
.
Equivalent of models.Manager.prefetch_related
and QuerySet.prefetch_related
.
Equivalent of models.Manager.extra
and QuerySet.extra
.
Equivalent of models.Manager.defer
and QuerySet.defer
.
Equivalent of models.Manager.only
and QuerySet.only
.
Equivalent of models.Manager.using
and QuerySet.using
.
Equivalent of models.Manager.select_for_update
and QuerySet.select_for_update
.
Equivalent of models.Manager.raw
and QuerySet.raw
.
These methods are async and will connect to the database. For return type and in-depth info of each method please refer to the official Django QuerySet API references.
Async equivalent of models.Manager.get
and QuerySet.get
.
Async equivalent of models.Manager.create
and QuerySet.create
.
Async equivalent of models.Manager.get_or_create
and QuerySet.get_or_create
.
Async equivalent of models.Manager.update_or_create
and QuerySet.update_or_create
.
Async equivalent of models.Manager.bulk_create
and QuerySet.bulk_create
.
Async equivalent of models.Manager.bulk_update
and QuerySet.bulk_update
.
Async equivalent of models.Manager.count
and QuerySet.count
.
Async equivalent of models.Manager.in_bulk
and QuerySet.in_bulk
.
Async equivalent of models.Manager.iterator
and QuerySet.iterator
.
Async equivalent of models.Manager.latest
and QuerySet.latest
.
Async equivalent of models.Manager.earliest
and QuerySet.earliest
.
Async equivalent of models.Manager.first
and QuerySet.first
.
Async equivalent of models.Manager.last
and QuerySet.last
.
Async equivalent of models.Manager.aggregate
and QuerySet.aggregate
.
Async equivalent of models.Manager.exists
and QuerySet.exists
.
Async equivalent of models.Manager.update
and QuerySet.update
.
Async equivalent of models.Manager.delete
and QuerySet.delete
.
Async equivalent of models.Manager.explain
and QuerySet.explain
.
Extends AsyncQuerySet[T]
. Manager returned for reverse many-to-one foreign relation access.
Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.add
.
Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.remove
.
Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.clear
.
Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.set
.
Extends AsyncQuerySet[T]
. Manager returned for many-to-many foreign relation access.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.add
.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.create
.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.get_or_create
.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.update_or_create
.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.remove
.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.clear
.
Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.set
.
As of the release of this package the sync_to_async
and async_to_sync
wrappers on asgiref.sync
are not typed, this package provides the typed equivalent of these wrappers:
- If project is on python<3.10, only the return type will be typed.
- If project is on python>=3.10, both the return type and param specs will be typed (PEP 612).
Import:
from asgimod.sync import sync_to_async, async_to_sync
Usage: Same as asgiref.sync
Contributions are welcomed, there are uncovered test cases and probably missing features.
Django itself is not doing well at typing, for example the sync managers are not typed, but please keep those out of the scope of this project as it's not related to async and asgi.
A django test project was used for testing, simply run
python manage.py shell