Altering the Django ORM to be async will be a significant undertaking.
I had a few initial thoughts on syntax to support async ORM queries in async contexts while still allowing sync ORM queries in sync contexts (and sometimes also in async contexts when no queries are actually issued):
A. ORM queries happen explicitly when fetching specific models. For example:
question = Question.objects.first() # explicit query
question = Question.objects.get(id=...) # explicit query
question = Question.objects.prefetch_related(
'choice_set'
).get(id=...) # explicit query
I think we could make those functions also support async calls at the same time as sync calls by replacing the function object with an object that implements both __call__
and __acall__
. So in an async view you could write:
question = await Question.objects.first() # explicit query
question = await Question.objects.get(id=...) # explicit query
question = await Question.objects.prefetch_related(
'choice_set'
).get(id=...) # explicit query
B. ORM queries happen implicitly via property access when accessing related single models. For example:
choice = ...
question = choice.question # implicit query if not prefetched
Python doesnât currently support any kind of asynchronous property syntax. Perhaps we could have a different âasync namespaceâ (perhaps called something short like a
) that could used to access properties:
choice = ...
question = await choice.a.question # implicit query if not prefetched
Additionally, we could still allow the regular property syntax in the common case where a view function does an early prefetch of all models it uses and the property being accessed leads to a model that has already been fetched:
choice = ...
question = choice.question # ok only if has been prefetched
If the above syntax is used when a property has NOT been prefetched, a suitable exception can be raised:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnprefetchedRelatedModelAccessError: Related model Choice.question cannot be
fetched synchronously from within an async view function or other async context.
Consider using select_related() or prefetch_related() to prefetch the
model before accessing it.
C. ORM queries happen explicitly when accessing related model managers for relationships that have not been prefetched. For example:
question = ...
choices = question.choice_set.all() # explicit query if not prefetched
In an async context we could use the same approach as A to make methods like all()
support both __call__
and __acall__
:
question = ...
choices = await question.choice_set.all() # explicit query if not prefetched
Additionally, we could still allow the regular property syntax in the common case where a view function does an early prefetch of all models it uses and the property being accessed leads to a collection that has already been fetched:
question = ...
choices = question.choice_set.all() # ok only if has been prefetched
If the above syntax is used when a collection has NOT been prefetched with prefetch_related()
, a suitable exception can be raised:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnprefetchedRelatedModelAccessError: Related models in Question.choice_set cannot be
fetched synchronously from within an async view function or other async context.
Consider using prefetch_related() to prefetch the models before accessing them.
D. ORM queries can happen in view function decorators:
@user_passes_test(lambda user:
user.inschooluser.type == 'teacher' # explicit query, if not prefetched
)
def gradebook(request: HttpRequest) -> HttpResponse:
...
General decorators should be able to detect whether the view function they are wrapping is async or sync and thus should be able to support async view functions:
async def _user_is_teacher(user):
return (await user.a.inschooluser).type == 'teacher'
@user_passes_test(_user_is_teacher)
async def gradebook(request: HttpRequest) -> HttpResponse:
...
E. ORM queries can happen in middleware. And async middleware has already been implemented.
F. ORM queries can happen in management commands. Iâm not sure whether async management commands have been implemented or not, but I imagine theyâd be straightforward.
G. ORM queries can happen in an interactive shell prompt. I donât know too much about interactive async REPL prompts.
It should be possible to rewrite existing sync views as async views without significant effort, with the availability of syntaxes A-C in particular:
-
Small view functions are easy to rewrite no matter what the syntax.
-
Large view functions, which are probably already prefetching all models early anyway, will need to add some
await
expressions to the leading prefetch calls, but all other functions that the view function calls can continue to use the existing sync model access syntax without modification.- In situations where a dynamic model fetch after the initial prefetch is potentially needed,
await
expressions and intermediateasync
functions can be introduced only where necessary.
- In situations where a dynamic model fetch after the initial prefetch is potentially needed,
Comments?