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.
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 : from django.core.cache import cache In : cache.set('from_sync', 1) In : await cache.set.as_async('from_async', 2) In : await cache.get.as_async('from_sync') Out: 1 In : cache.get('from_async') Out: 2 In : cache.get.as_sync('from_sync') Out: 1 In : cache.get.as_async('from_sync') Out: <coroutine object SyncToAsync.__call__ at 0x1048a0950>
The suggested API design here is to provide an extension to the critical functions so that users can use
.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.
This is all implemented via a helper decorator (currently called
auto_async) which by default wraps the original function in
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
async_unsafeas needed. This leaves us with a naive implementation that runs the existing code via
- 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