#36872 closed New feature (wontfix)
Django's template engine cannot handle asynchronous methods
| Reported by: | Ricardo Robles | Owned by: | Ricardo Robles |
|---|---|---|---|
| Component: | Template system | Version: | 6.0 |
| Severity: | Normal | Keywords: | Template, Async |
| Cc: | Carlton Gibson | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | yes |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Currently, the Django template is not designed to execute asynchronous methods; it is only designed to execute synchronous methods.
Here's an example of how to reproduce the error:
from django.template import engines
django_engine = engines['django']
class Example:
def sync_method(self):
return "Synchronous Method Result"
async def async_method(self):
return "Asynchronous Method Result"
html_string = """
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>sync: {{ example.sync_method }}!</h1>
<p>async: {{ example.async_method }}</p>
</body>
</html>
"""
template = django_engine.from_string(html_string)
rendered_html = template.render({'example': Example()})
print(rendered_html)
This will return this error:
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>sync: Synchronous Method Result!</h1>
<p>async: <coroutine object Example.async_method at 0x7bdeeb9aa980></p>
</body>
</html>
I had thought that a solution to this error might be to modify the resolve method of the FilterExpression class
https://github.com/django/django/blob/main/django/template/base.py#L785
If we add this:
class FilterExpression:
...
def resolve(self, context, ignore_failures=False):
if self.is_var:
try:
obj = self.var.resolve(context)
# My proposal begins
if asyncio.iscoroutine(obj):
obj = async_to_sync(lambda: obj)()
# My proposal ends
except VariableDoesNotExist:
...
Now it renders correctly:
<!DOCTYPE html>
<html>
<head>
<title>Example</title>
</head>
<body>
<h1>sync: Synchronous Method Result!</h1>
<p>async: Asynchronous Method Result</p>
</body>
</html>
I use Django ASGI a lot at work, and there are many features like this that would be very useful. I look forward to your feedback.
Attachments (2)
Change History (17)
follow-up: 2 comment:1 by , 3 weeks ago
comment:2 by , 3 weeks ago
Replying to Zachary W:
Could be related. There is an async_to_sync function in asgiref: https://github.com/django/asgiref/blob/2b28409ab83b3e4cf6fed9019403b71f8d7d1c51/asgiref/sync.py#L585
You're correct, the most elegant and simple way to do it would be:
def resolve(self, context, ignore_failures=False):
if self.is_var:
try:
obj = self.var.resolve(context)
if asyncio.iscoroutine(obj):
obj = async_to_sync(lambda: obj)()
...
comment:3 by , 3 weeks ago
| Description: | modified (diff) |
|---|
comment:4 by , 3 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
by , 3 weeks ago
| Attachment: | proposal.diff added |
|---|
comment:5 by , 3 weeks ago
| Owner: | changed from to |
|---|
comment:6 by , 3 weeks ago
| Triage Stage: | Unreviewed → Accepted |
|---|---|
| UI/UX: | unset |
Thanks, I think it makes sense to have this. The provided patch is non-invasive, and I don't think anyone wants the current behavior with the memory address of the coroutine rendered into the template (and RuntimeWarning at shutdown for a coroutine not awaited).
comment:7 by , 3 weeks ago
| Has patch: | set |
|---|
follow-up: 9 comment:8 by , 3 weeks ago
| Needs documentation: | set |
|---|---|
| Type: | Bug → New feature |
I think this will need a sliver of documentation. For instance where we say:
If the resulting value is callable, it is called with no arguments.
We could augment it with:
If the resulting value is a coroutine, it is awaited via
async_to_sync().
or similar.
Although this is sort of a bug since elsewhere the docs say "variables are evaluated", I think we should let folks know we added this with a release note and versionchanged annotation.
by , 3 weeks ago
| Attachment: | proposal_2.diff added |
|---|
comment:9 by , 3 weeks ago
Replying to Jacob Walls:
I think this will need a sliver of documentation. For instance where we say:
If the resulting value is callable, it is called with no arguments.
We could augment it with:
If the resulting value is a coroutine, it is awaited via
async_to_sync().
or similar.
Although this is sort of a bug since elsewhere the docs say "variables are evaluated", I think we should let folks know we added this with a release note and versionchanged annotation.
Good afternoon,
Okay, I've just updated the documentation in both the diff and the PR to include this information.
comment:10 by , 3 weeks ago
| Patch needs improvement: | set |
|---|
comment:11 by , 3 weeks ago
| Cc: | added |
|---|
follow-up: 15 comment:12 by , 3 weeks ago
As small as the change is here, I can't help but thinking it makes little sense. Async functions just aren't supported in the DTL... and what purpose do they serve? Adding this auto-wrap in async_to_sync is a road to where exactly? Nowhere. It's a very short cul-de-sac.
I don't think anyone wants the current behavior with the memory address of the coroutine rendered into the template (and RuntimeWarning at shutdown for a coroutine not awaited).
If this is a genuine problem (which I'm not sure it is: "Just don't do that" is often enough) raising an error here would be better. It's not making a misleading promise that we won't later keep.
🤷
comment:13 by , 3 weeks ago
Thanks all for your comments and views! I agree with Carlton in that there is no async support nowhere in the DTL so adding this specific bit feels disconnected and strange.
comment:14 by , 3 weeks ago
| Resolution: | → wontfix |
|---|---|
| Status: | assigned → closed |
I'm convinced. Thanks for helping me keep the bigger picture in mind. Ricardo, I hope that makes some sense about why we're not acting on this.
comment:15 by , 3 weeks ago
Replying to Carlton Gibson:
As small as the change is here, I can't help but thinking it makes little sense. Async functions just aren't supported in the DTL... and what purpose do they serve? Adding this auto-wrap in async_to_sync is a road to where exactly? Nowhere. It's a very short cul-de-sac.
I don't think anyone wants the current behavior with the memory address of the coroutine rendered into the template (and RuntimeWarning at shutdown for a coroutine not awaited).
If this is a genuine problem (which I'm not sure it is: "Just don't do that" is often enough) raising an error here would be better. It's not making a misleading promise that we won't later keep.
🤷
Good morning, the inability to use asynchronous methods in DTL is a real problem.
There are increasingly more asynchronous libraries that don't require additional code to convert them to synchronous functionality for use in a template.
For example, Django 6.0 introduced AsyncPaginator, which cannot be used in DTL. Why did you add AsyncPaginator if you're not going to allow its use in DTL?
Could be related. There is an async_to_sync function in asgiref: https://github.com/django/asgiref/blob/2b28409ab83b3e4cf6fed9019403b71f8d7d1c51/asgiref/sync.py#L585