asgimod
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
.
Package FAQ:
- 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.
Requirements:
- Django >= 3.0
- Python >= 3.8
Installation:
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)
s
Async model mixinThis 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)
API Reference:
Extends from models.Model
, uses metaclass AsyncMixinMeta
(extended from models.ModelBase
).
aobjects
-> AsyncManager
classproperty Returns an instance of AsyncManager
. Async equivalent of Model.objects
.
asave(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)
-> None
asyncmethod Async equivalent of Model.save
adelete(using=DEFAULT_DB_ALIAS, keep_parents=False)
-> None
asyncmethod Async equivalent of Model.delete
a(.*)
-> Awaitable[T] | AsyncManyToOneRelatedManager | AsyncManyToManyRelatedManager
getattr 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 managers and querysets
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
)
API Reference:
AsyncQuerySet[T]
(alias: AsyncManager[T]
)
class Magic methods - Iterators & Iterables:
__aiter__
-> Iterable[T | Tuple | datetime | date | Any]
asynciterator 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")
__getitem__
-> AsyncQuerySet[T] | Awaitable[T | Tuple | datetime | date | Any]
getitem 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]
Magic methods - General
__repr__
builtin Returns f"<AsyncQuerySet [...{self._cls}]>"
.
__str__
builtin Returns f"<AsyncQuerySet [...{self._cls}]>"
.
__len__
builtin Raises NotImplementedError
, AsyncQuerySet
does not support __len__()
, use .count()
instead.
__bool__
builtin Raises NotImplementedError
, AsyncQuerySet
does not support __bool__()
, use .exists()
instead.
Magic methods - Operators
__and__
(&
)
operator 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
__or__
(|
)
operator 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
Methods for explicit evaluation of querysets
item(val: Union[int, Any])
-> T | Tuple | datetime | date | Any
asyncmethod 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.
eval()
-> List[T | Tuple | datetime | date | Any]
asyncmethod 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()
AsyncQuerySet[T]
containing the new internal QuerySet[T]
.
Methods that returns a new 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.
filter(*args, **kwargs)
method Equivalent of models.Manager.filter
and QuerySet.filter
.
exclude(*args, **kwargs)
method Equivalent of models.Manager.exclude
and QuerySet.exclude
.
annotate(*args, **kwargs)
method Equivalent of models.Manager.annotate
and QuerySet.annotate
.
alias(*args, **kwargs)
method Equivalent of models.Manager.alias
and QuerySet.alias
.
order_by(*fields)
method Equivalent of models.Manager.order_by
and QuerySet.order_by
.
reverse()
method Equivalent of models.Manager.reverse
and QuerySet.reverse
.
distinct(*fields)
method Equivalent of models.Manager.distinct
and QuerySet.distinct
.
values(*fields, **expressions)
method Equivalent of models.Manager.values
and QuerySet.values
.
values_list(*fields, flat=False, named=False)
method Equivalent of models.Manager.values_list
and QuerySet.values_list
.
dates(field, kind, order='ASC')
method Equivalent of models.Manager.dates
and QuerySet.dates
.
datetimes(field_name, kind, order='ASC', tzinfo=None, is_dst=None)
method Equivalent of models.Manager.datetimes
and QuerySet.datetimes
.
none()
method Equivalent of models.Manager.none
and QuerySet.none
.
all()
method Equivalent of models.Manager.all
and QuerySet.all
.
union(*other_qs: "AsyncQuerySet[T]", all=False)
method Equivalent of models.Manager.union
and QuerySet.union
.
intersection(*other_qs: "AsyncQuerySet[T]")
method Equivalent of models.Manager.intersection
and QuerySet.intersection
.
difference(*other_qs: "AsyncQuerySet[T]")
method Equivalent of models.Manager.difference
and QuerySet.difference
.
select_related(*fields)
method Equivalent of models.Manager.select_related
and QuerySet.select_related
.
prefetch_related(*lookups)
method Equivalent of models.Manager.prefetch_related
and QuerySet.prefetch_related
.
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
method Equivalent of models.Manager.extra
and QuerySet.extra
.
defer(*fields)
method Equivalent of models.Manager.defer
and QuerySet.defer
.
only(*fields)
method Equivalent of models.Manager.only
and QuerySet.only
.
using(alias)
method Equivalent of models.Manager.using
and QuerySet.using
.
select_for_update(nowait=False, skip_locked=False, of=(), no_key=False)
method Equivalent of models.Manager.select_for_update
and QuerySet.select_for_update
.
raw(raw_query, params=(), translations=None, using=None)
method Equivalent of models.Manager.raw
and QuerySet.raw
.
AsyncQuerySet[T]
.
Methods that does NOT return a new 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.
get(**kwargs)
asyncmethod Async equivalent of models.Manager.get
and QuerySet.get
.
create(**kwargs)
asyncmethod Async equivalent of models.Manager.create
and QuerySet.create
.
get_or_create(**kwargs)
asyncmethod Async equivalent of models.Manager.get_or_create
and QuerySet.get_or_create
.
update_or_create(defaults=None, **kwargs)
asyncmethod Async equivalent of models.Manager.update_or_create
and QuerySet.update_or_create
.
bulk_create(objs, batch_size=None, ignore_conflicts=False)
asyncmethod Async equivalent of models.Manager.bulk_create
and QuerySet.bulk_create
.
bulk_update(objs, fields, batch_size=None)
asyncmethod Async equivalent of models.Manager.bulk_update
and QuerySet.bulk_update
.
count()
asyncmethod Async equivalent of models.Manager.count
and QuerySet.count
.
in_bulk(id_list=None, *, field_name='pk')
asyncmethod Async equivalent of models.Manager.in_bulk
and QuerySet.in_bulk
.
iterator(chunk_size=2000)
asyncmethod Async equivalent of models.Manager.iterator
and QuerySet.iterator
.
latest(*fields)
asyncmethod Async equivalent of models.Manager.latest
and QuerySet.latest
.
earliest(*fields)
asyncmethod Async equivalent of models.Manager.earliest
and QuerySet.earliest
.
first()
asyncmethod Async equivalent of models.Manager.first
and QuerySet.first
.
last()
asyncmethod Async equivalent of models.Manager.last
and QuerySet.last
.
aggregate(*args, **kwargs)
asyncmethod Async equivalent of models.Manager.aggregate
and QuerySet.aggregate
.
exists()
asyncmethod Async equivalent of models.Manager.exists
and QuerySet.exists
.
update(**kwargs)
asyncmethod Async equivalent of models.Manager.update
and QuerySet.update
.
delete()
asyncmethod Async equivalent of models.Manager.delete
and QuerySet.delete
.
explain(format=None, **options)
asyncmethod Async equivalent of models.Manager.explain
and QuerySet.explain
.
AsyncManyToOneRelatedQuerySet[T]
(alias: AsyncManyToOneRelatedManager[T]
)
class Extends AsyncQuerySet[T]
. Manager returned for reverse many-to-one foreign relation access.
add(*objs, bulk=True)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.add
.
remove(*objs, bulk=True)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.remove
.
clear(*, bulk=True)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.clear
.
set(objs, *, bulk=True, clear=False)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.set
.
AsyncManyToManyRelatedQuerySet[T]
(alias: AsyncManyToManyRelatedManager[T]
)
class Extends AsyncQuerySet[T]
. Manager returned for many-to-many foreign relation access.
add(*objs, through_defaults=None)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.add
.
create(*, through_defaults=None, **kwargs)
-> T
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.create
.
get_or_create(*, through_defaults=None, **kwargs)
-> T
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.get_or_create
.
update_or_create(*, through_defaults=None, **kwargs)
-> T
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.update_or_create
.
remove(*objs)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.remove
.
clear()
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.clear
.
set(objs, *, clear=False, through_defaults=None)
-> None
asyncmethod Async equivalent of models.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.set
.
Typed async and sync wrappers
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
Contribution & Development
Contributions are welcomed, there are uncovered test cases and probably missing features.
Typing the missing things in sync Django
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.
Running the tests
A django test project was used for testing, simply run
python manage.py shell