I’ve been working on adding async support to the caching framework. It is still a work in progress, but I think I’m at a point where I can start getting more eyes on it.
Caching
The actual work to write an async-capable cache backend is actually pretty straightforward, and there are good libraries to make the cache calls asynchronously. The hard part here is API design, how do we provide users with a clean way to access the cache in both sync and async contexts?.
I have implemented a backend for memcached in two ways: one based on the current memcached implementation using sync_to_async
and another based on aiocache (which also supports redis and others). This currently works as a proof-of-concept (the code still needs some cleanup) and allows you to call the appropriate sync or async function in the cache framework from either context.
In [1]: from django.core.cache import cache
In [2]: cache.set('from_sync', 1)
In [3]: await cache.set.as_async('from_async', 2)
In [4]: await cache.get.as_async('from_sync')
Out[4]: 1
In [5]: cache.get('from_async')
Out[5]: 2
In [6]: cache.get.as_sync('from_sync')
Out[6]: 1
In [7]: cache.get.as_async('from_sync')
Out[7]: <coroutine object SyncToAsync.__call__ at 0x1048a0950>
API Design
The suggested API design here is to provide an extension to the critical functions so that users can use .as_sync
and .as_async
on them which will provide a function (or method) that behaves the same way as the existing one, but that is adapted to the appropriate calling context.
Helper decorator
This is all implemented via a helper decorator (currently called auto_async
) which by default wraps the original function in sync_to_asyc
or async_to_sync
as needed and that will allow for custom sync or async implementations to be injected.
Using the helper to make other parts of django async-capable
The idea behind the helper is to allow us to quickly make other parts of django async-capable as needed. The process would be something like this:
- Wrap code in
auto_async
orasync_unsafe
as needed. This leaves us with a naive implementation that runs the existing code viasync_to_async
- Provide an appropriate async version of the code we want to replace. The decorator should make this customization easy (WIP)
- As the implementation of these functions evolve and gets properly tested, we can change the default behaviour to sync or async as needed, and provide deprecation or usage warnings when the functions get created or executed
The current code can be seen in this pull request: https://github.com/andrewgodwin/django/pull/6/files