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_asyncandasync_to_syncwrappers,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 asgimodThe 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:
aobjectsfull featured async manager.asave,adeleteasync equivalents ofsaveanddelete.a(.*)async foreign relations access.
Import:
from asgimod.mixins import AsyncMixinUsage:
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 withTreturn (Tbeing 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 & qbAsyncQuerySet[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 | qbReturns 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_syncUsage: 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