Let’s see how this Discourse thing works: This topic presents the current state of discussion on adding async support to Django’s class-based views. It’s a mix of the discussions happening at the PyCon Australia 2019 sprints, and the developments afterwards.
The goal
Our goals, in line with DEP9, are to absolutely avoid breaking any current code, and also offering users the possibility (but not the default) of writing async class-based views. Async class-based views involve being able to write an async def post(self, request)
or an async def get(self, request)
, or an async def dispatch(self, request)
without rewriting an existing project, for instance in a ListView
or a TemplateView
.
The problem
We cannot change the method signature of View.dispatch
to be a coroutine, since this would break existing user code the second anybody calls super().dispatch()
– which is an encouraged pattern. But for views to be async, the dispatch
method (which is called by the view
method returned by View.as_view()
) has to be async, to call an async get
or post
.
This means we need a way to allow the Django user to explicitly change the method signature of dispatch
. We’ll need to figure out which of the solutions we came up with is the best, going by user experience and maintenance burden.
We should also keep in mind how third-party libraries are going to provide async-capable views open for inheritance. Should they implement each view twice? Can they offer the same view as async-capable and sync in some way (without breaking super() calls)?
Solutions
Separate async classes
The maybe most clean, separated solutions would be to provide a separate amount of async classes, like AsyncTemplateView
and AsyncListView
. The upside is that the user has to change only the base class, and if we provide a somewhat clever dispatch
method, they don’t have to change much code, especially in third-party libraries.
At the same time, Django provides a large amounts of class-based views, so maintaining a separate stack for async seems like a lot of duplication and a rather large maintenance burden, especially since this would include for instance LoginRequiredMixin
, UserPassesTestMixin
, and PermissionRequiredMixin
.
We could, of course, choose to provide only an AsyncView
and let people handle our mixins like TemplateResponseMixin
themselves, but that leaves a lot of duplicated work on the users’ side, where lots of people build AsyncFormView
.
Mixins
As an interface it might instead be neat to provide an AsyncMixin
, that would make classes async capable. This mixin would need to be placed leftmost (or as left as possible, maybe) in the MRO, would play pretty horribly with subclassed views or third-party view classes. Such a mixin could make sure either all of the other parent classes are async capable, or wrap the dispatch call in sync_to_async
.
I’m not sure this would actually work in any meaningful way, though, especially while respecting/retaining the functionality of the other parent classes.
init_subclass
Another proposal is to adapt the current view class on the fly (though on Django startup, not on every request) via the __init_subclass__
method. This method would check for a flag on the child class, like async_enabled = True
(or a decorator, or a constant, or …). If this flag is present, it would verify that all parent classes are async capable, and then replace the def dispatch
method with an async def dispatch
method.
This implementation is already available as a demo WIP PR here. It has the advantage of requiring neither a lot of code duplication on the Django side, nor a lot of work on the user side, and allowing third-party packages to signal compatibility fairly easily by supplying this flag. (Although the question remains how a third-party view would be able to supply a dispatch method).
We do this by monkeypatching our own code though, and I’m not sure if this is something we want to see in Django core.
Next steps
The next steps are:
- Build working prototypes of the suggested solutions, to have a better basis for discussion.
- Optionally: Use these prototypes in a real-life project to compare the impact.
- Come up with alternatives
- Discuss & decide
We should keep in mind that we can also decide against supporting class-based views. While this would be not great, a solution that places a significant burden on users or library developers may be more harmful than refering users interested in async views to use function based views for now. (And/or see what others come up with and introduce a solution later on.)