This option is interesting, but I don’t know if we can count and async connections being portable for every backend. Would this work on, let’s say, async SQLite?
Sorry, I suppose my actual thought was “Make sync connections just wrap an async connection for backends that support async connections.”. For backends that don’t support native async connections or portability, the existing behavior would be maintained.
I just dislike having a backend where we are opening connections through two separate APIs in the lower leve.
I did a bit more work on my branch during PyCon AU sprints implementing a variant of this idea:
def should_use_sync_fallback(async_variant):
# this checks how many "new connection" context managers deep we're in
# (tracked by an asgiref.Local of course)
return async_variant and (new_connection_block_depth.value == 0)
# ... over in the Queryset API
async def aget(self, *args, **kwargs):
if should_use_sync_fallback(ASYNC_TRUTH_MARKER):
return await sync_to_async(self.get)(*args, **kwargs)
# otherwise we're going down an async-native thing
The core point here being that there is a check for determining whether to use a sync fallback here. This provides a place for resolving the issue we’re talking about, as well as the issue of people overriding save
but not asave
in their custom Model
methods.
Anyways it sounds like here there’s good consensus on the following idea:
- We will maintain backwards compatibility with regards to existing async queryset APIs sharing the same connection context as sync queryset APIs in code that is valid in existing versions of Django.
- Creating an async connection through the
new_connection
context manager places you into new semantics regarding connection context throughout that context. Inside there, async APIs use async connections, while sync APIs will continue to use sync connections