Package: asgimod
asgimodThis package includes components and utilities that makes django *usable* in async python, such as:Async model mixins(fully typed),asgimod.mixins.Async managers and querysets (fully typed),asgimod.db.Typedsync_to_asyncandasync_to_syncwrappers,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 classpropertyaobjects.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.0Python >= 3.8Installation:pipinstallasgimodThe documentation uses references from these model definitions:classTopping(AsyncMixin,models.Model):name=models.CharField(max_length=30)classBox(AsyncMixin,models.Model):name=models.CharField(max_length=50)classPrice(AsyncMixin,models.Model):amount=models.DecimalField(decimal_places=2,max_digits=10)currency=models.CharField(max_length=16,default="usd")classPizza(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)Async model mixinsThis mixin adds async capabilities to the model class and instances:aobjectsfull featured async manager.asave,adeleteasync equivalents ofsaveanddelete.a(.*)async foreign relations access.Import:fromasgimod.mixinsimportAsyncMixinUsage:classSampleModel(AsyncMixin,models.Model):sample_field=models.CharField(max_length=50)API Reference:Extends frommodels.Model, uses metaclassAsyncMixinMeta(extended frommodels.ModelBase).classpropertyaobjects->AsyncManagerReturns an instance ofAsyncManager. Async equivalent ofModel.objects.asyncmethodasave(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)->NoneAsync equivalent ofModel.saveasyncmethodadelete(using=DEFAULT_DB_ALIAS, keep_parents=False)->NoneAsync equivalent ofModel.deletegetattra(.*)->Awaitable[T] | AsyncManyToOneRelatedManager | AsyncManyToManyRelatedManagerThere 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 theaprefix to your sync access attribute. Using the models defined for this documentation, examples:price=awaitPrice.aobjects.get(id=1)pizza=awaitPizza.aobjects.get(id=1)weird_pizza=awaitPizza.aobjects.get(id=2)bacon=awaitTopping.aobjects.get(id=1)mushroom=awaitTopping.aobjects.get(id=2)medium_box=awaitBox.aobjects.get(id=1)# one to one rel & forward many to one relawaitpizza.apriceawaitprice.apizzaawaitprice.abox# reverse many to one relawaitmedium_box.apizza_set.all().get(id=1)awaitmedium_box.apizza_set.filter(id__gt=1).order_by("name").count()awaitmedium_box.apizza_set.add(weird_pizza)awaitmedium_box.apizza_set.clear()# forward many to many relawaitpizza.atoppings.all().exists()awaitpizza.atoppings.add(bacon,mushroom)awaitbacon.atoppings.filter(name__startswith="b").exists()awaitpizza.atoppings.remove(bacon)awaitpizza.atoppings.clear()# reverse many to many relawaitmushroom.apizza_set.all().exists()awaitmushroom.apizza_set.add(pizza)awaitmushroom.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:classTopping(AsyncMixin,models.Model):name=models.CharField(max_length=30)apizza_set:AsyncManyToManyRelatedManager["Pizza"]classBox(AsyncMixin,models.Model):name=models.CharField(max_length=50)apizza_set:AsyncManyToOneRelatedManager["Pizza"]classPrice(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 querysetsAsync 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, useobjectsinstead.Import:fromasgimod.dbimport(AsyncQuerySet,AsyncManyToOneRelatedQuerySet,AsyncManyToManyRelatedQuerySet,AsyncManager,AsyncManyToOneRelatedManager,AsyncManyToManyRelatedManager)API Reference:classAsyncQuerySet[T](alias:AsyncManager[T])Magic methods - Iterators & Iterables:asynciterator__aiter__->Iterable[T | Tuple | datetime | date | Any]Async iterator over anAsyncQuerySet[T]usingasync forsyntax. 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.asyncforpriceinPrice.aobjects.filter(currency="usd"):self.assertEqual(price.currency,"usd")getitem__getitem__->AsyncQuerySet[T] | Awaitable[T | Tuple | datetime | date | Any]Slicing and indexing over anAsyncQuerySet[T]using[]syntax.Slicing anAsyncQuerySet[T]will return a newAsyncQuerySet[T], slicing using steps is not allowed, as it would evaluate the internal syncQuerySetand raisesSynchronousOnlyOperation.prices=awaitPrice.aobjects.all()[:2].eval()prices=awaitPrice.aobjects.all()[1:2].eval()prices=awaitPrice.aobjects.all().order_by("-amount")[1:].eval()Indexing anAsyncQuerySet[T]will return anAwaitable[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=awaitPrice.aobjects.all()[0]price=awaitPrice.aobjects.all()[:5][0]price=awaitPrice.aobjects.filter(amount__gte=Decimal("9.99"))[0]Magic methods - Generalbuiltin__repr__Returnsf"<AsyncQuerySet [...{self._cls}]>".builtin__str__Returnsf"<AsyncQuerySet [...{self._cls}]>".builtin__len__RaisesNotImplementedError,AsyncQuerySetdoes not support__len__(), use.count()instead.builtin__bool__RaisesNotImplementedError,AsyncQuerySetdoes not support__bool__(), use.exists()instead.Magic methods - Operatorsoperator__and__(&)AsyncQuerySet[T] & AsyncQuerySet[T] -> AsyncQuerySet[T]# async qs for prices amount > 19.99qa=Price.aobjects.filter(amount__gt=Decimal("2.99"))qb=Price.aobjects.filter(amount__gt=Decimal("19.99"))qs=qa&qboperator__or__(|)AsyncQuerySet[T] | AsyncQuerySet[T] -> AsyncQuerySet[T]# async qs for prices with usd and eur currencyqa=Price.aobjects.filter(currency="usd")qb=Price.aobjects.filter(currency="eur")qs=qa|qbMethods for explicit evaluation of querysetsasyncmethoditem(val: Union[int, Any])->T | Tuple | datetime | date | AnyReturns the item on indexvalof anAsyncQuerySet[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.asyncmethodeval()->List[T | Tuple | datetime | date | Any]Returns the evaluatedAsyncQuerySet[T]in a list. Equivalent ofsync_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=awaitTopping.aobjects.all().eval()toppings_start_with_B=awaitTopping.aobjects.filter(name__startswith="B").eval()Methods that returns a newAsyncQuerySet[T]containing the new internalQuerySet[T].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.methodfilter(*args, **kwargs)Equivalent ofmodels.Manager.filterandQuerySet.filter.methodexclude(*args, **kwargs)Equivalent ofmodels.Manager.excludeandQuerySet.exclude.methodannotate(*args, **kwargs)Equivalent ofmodels.Manager.annotateandQuerySet.annotate.methodalias(*args, **kwargs)Equivalent ofmodels.Manager.aliasandQuerySet.alias.methodorder_by(*fields)Equivalent ofmodels.Manager.order_byandQuerySet.order_by.methodreverse()Equivalent ofmodels.Manager.reverseandQuerySet.reverse.methoddistinct(*fields)Equivalent ofmodels.Manager.distinctandQuerySet.distinct.methodvalues(*fields, **expressions)Equivalent ofmodels.Manager.valuesandQuerySet.values.methodvalues_list(*fields, flat=False, named=False)Equivalent ofmodels.Manager.values_listandQuerySet.values_list.methoddates(field, kind, order='ASC')Equivalent ofmodels.Manager.datesandQuerySet.dates.methoddatetimes(field_name, kind, order='ASC', tzinfo=None, is_dst=None)Equivalent ofmodels.Manager.datetimesandQuerySet.datetimes.methodnone()Equivalent ofmodels.Manager.noneandQuerySet.none.methodall()Equivalent ofmodels.Manager.allandQuerySet.all.methodunion(*other_qs: "AsyncQuerySet[T]", all=False)Equivalent ofmodels.Manager.unionandQuerySet.union.methodintersection(*other_qs: "AsyncQuerySet[T]")Equivalent ofmodels.Manager.intersectionandQuerySet.intersection.methoddifference(*other_qs: "AsyncQuerySet[T]")Equivalent ofmodels.Manager.differenceandQuerySet.difference.methodselect_related(*fields)Equivalent ofmodels.Manager.select_relatedandQuerySet.select_related.methodprefetch_related(*lookups)Equivalent ofmodels.Manager.prefetch_relatedandQuerySet.prefetch_related.methodextra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)Equivalent ofmodels.Manager.extraandQuerySet.extra.methoddefer(*fields)Equivalent ofmodels.Manager.deferandQuerySet.defer.methodonly(*fields)Equivalent ofmodels.Manager.onlyandQuerySet.only.methodusing(alias)Equivalent ofmodels.Manager.usingandQuerySet.using.methodselect_for_update(nowait=False, skip_locked=False, of=(), no_key=False)Equivalent ofmodels.Manager.select_for_updateandQuerySet.select_for_update.methodraw(raw_query, params=(), translations=None, using=None)Equivalent ofmodels.Manager.rawandQuerySet.raw.Methods that does NOT return a newAsyncQuerySet[T].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.asyncmethodget(**kwargs)Async equivalent ofmodels.Manager.getandQuerySet.get.asyncmethodcreate(**kwargs)Async equivalent ofmodels.Manager.createandQuerySet.create.asyncmethodget_or_create(**kwargs)Async equivalent ofmodels.Manager.get_or_createandQuerySet.get_or_create.asyncmethodupdate_or_create(defaults=None, **kwargs)Async equivalent ofmodels.Manager.update_or_createandQuerySet.update_or_create.asyncmethodbulk_create(objs, batch_size=None, ignore_conflicts=False)Async equivalent ofmodels.Manager.bulk_createandQuerySet.bulk_create.asyncmethodbulk_update(objs, fields, batch_size=None)Async equivalent ofmodels.Manager.bulk_updateandQuerySet.bulk_update.asyncmethodcount()Async equivalent ofmodels.Manager.countandQuerySet.count.asyncmethodin_bulk(id_list=None, *, field_name='pk')Async equivalent ofmodels.Manager.in_bulkandQuerySet.in_bulk.asyncmethoditerator(chunk_size=2000)Async equivalent ofmodels.Manager.iteratorandQuerySet.iterator.asyncmethodlatest(*fields)Async equivalent ofmodels.Manager.latestandQuerySet.latest.asyncmethodearliest(*fields)Async equivalent ofmodels.Manager.earliestandQuerySet.earliest.asyncmethodfirst()Async equivalent ofmodels.Manager.firstandQuerySet.first.asyncmethodlast()Async equivalent ofmodels.Manager.lastandQuerySet.last.asyncmethodaggregate(*args, **kwargs)Async equivalent ofmodels.Manager.aggregateandQuerySet.aggregate.asyncmethodexists()Async equivalent ofmodels.Manager.existsandQuerySet.exists.asyncmethodupdate(**kwargs)Async equivalent ofmodels.Manager.updateandQuerySet.update.asyncmethoddelete()Async equivalent ofmodels.Manager.deleteandQuerySet.delete.asyncmethodexplain(format=None, **options)Async equivalent ofmodels.Manager.explainandQuerySet.explain.classAsyncManyToOneRelatedQuerySet[T](alias:AsyncManyToOneRelatedManager[T])ExtendsAsyncQuerySet[T]. Manager returned for reverse many-to-one foreign relation access.asyncmethodadd(*objs, bulk=True)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.add.asyncmethodremove(*objs, bulk=True)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.remove.asyncmethodclear(*, bulk=True)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.clear.asyncmethodset(objs, *, bulk=True, clear=False)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_reverse_many_to_one_manager.RelatedManager.set.classAsyncManyToManyRelatedQuerySet[T](alias:AsyncManyToManyRelatedManager[T])ExtendsAsyncQuerySet[T]. Manager returned for many-to-many foreign relation access.asyncmethodadd(*objs, through_defaults=None)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.add.asyncmethodcreate(*, through_defaults=None, **kwargs)->TAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.create.asyncmethodget_or_create(*, through_defaults=None, **kwargs)->TAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.get_or_create.asyncmethodupdate_or_create(*, through_defaults=None, **kwargs)->TAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.update_or_create.asyncmethodremove(*objs)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.remove.asyncmethodclear()->NoneAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.clear.asyncmethodset(objs, *, clear=False, through_defaults=None)->NoneAsync equivalent ofmodels.fields.related_descriptors.create_forward_many_to_many_manager.RelatedManager.set.Typed async and sync wrappersAs of the release of this package thesync_to_asyncandasync_to_syncwrappers onasgiref.syncare 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:fromasgimod.syncimportsync_to_async,async_to_syncUsage: Same asasgiref.syncContribution & DevelopmentContributions are welcomed, there are uncovered test cases and probably missing features.Typing the missing things in sync DjangoDjango 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 testsA django test project was used for testing, simply runpythonmanage.pyshell