If you ever tried to call coroutines in Django views, you probably know that it returns a coroutine object. It is related to the fact that Django views are sequential code. When you call async functions in sequential code, the return value is a coroutine object.
Unfortunately, you can not simply call the async function with the await construct. If you try this, you get a SyntaxError: ‘await’ outside async function. In this tutorial, we learn how to call async functions in sequential code and apply this technique to call coroutines in Django views.
Call coroutines in django views
Let see the errors you get when you call async functions in sequential code. The sequential code we demonstrate the problem is Django views. Suppose you have coroutines declared with async syntax, and you want to call coroutines in Django views; that is, you want to call coroutines in sequential code.
For the sake of simplicity, suppose the coroutine with async syntax return a string. In the Django view, you want to call this coroutine, find the generated string and respond with text file whose content is this value.
Call async functions in sequential code – Always return coroutine object
It seems straightforward task. You naively try to write the following code:
data.py – The module with coroutine declared with async syntax
import asyncio
async def msg():
await asyncio.sleep(2)
return 'hello'
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('hello', views.hello, name='hello')
]
views.py
from . import data
def hello(request):
ss = data.hello()
return HttpResponse(ss,content_type='text/plain')
However when you test the URL you get the following text file:
Text File at /hello
<coroutine object msg at 0x7f8504696d48>
This example shows that you will always get a coroutine object when you call async functions in sequential code. It is nice, but it not what you want to do. You want to return the value generated from the async function.
‘await’ outside async function
Fortunately, You remember that you can use the await statement to get the generated from the async function and decide to modify the code to use await construct.
views.py
from . import data
def hello(request):
ss = await data.hello()
return HttpResponse(ss,content_type='text/plain')
When test the code by accessing the hello
url, you get:
SyntaxError: ‘await’ outside async function
$ ./manage.py runserver 0:8000
Performing system checks...
Unhandled exception in thread ...
ss = await data.msg()
^
SyntaxError: 'await' outside async function
It does not solve the problem. Now, the code raises an exception. You get SyntaxError
exception with a clear message 'await' outside async function
.
async functions always return coroutine object
Well, You may think it straightforward to fix this issue. You decide to modify the code and convert the view function to coroutine with the async construct.
views.py
from . import data
async def hello(request):
ss = await data.hello()
return HttpResponse(ss,content_type='text/plain')
When test the code by accessing the hello
url, you get, An exception is raised when accessing the url:
exception at /hello : exception ‘coroutine’ object has no attribute ‘get’
AttributeError at /hello
'coroutine' object has no attribute 'get'
Oops! As we see earlier, a function with async syntax will always return coroutine object. However, sometimes your API require that you provide a regular function and not a coroutine.
In this example, Django (at least, Django 2.15) expect you return a HttpResponse
object. However, when it try to call the method get
method of the HttpResponse
object, It can not find this methode and raises this exception.
How to Call async functions in sequential code?
Let’s see how to call async functions in sequential code. we will use the asyncio module
First, we create a new event loop with asyncio.new_event_loop
loop = asyncio.new_event_loop()
The loop will be responsible for executing coroutine or the async functions we provide it. We can tell the loop to wait until the coroutine is completed and return the generated value using run_until_complete method.
ss = loop.run_until_complete( data.msg() )
We need to close the loop when we do not need the loop anymore, so the loop releases the unused resources.
loop.close()
Putting it all together, we get the following code:
Call async functions in sequential code
from . import data
def hello(request):
loop = asyncio.new_event_loop()
ss = loop.run_until_complete( data.msg() )
loop.close()
return HttpResponse(ss,content_type='text/plain')
Now, When test the code by accessing the hello
url, you get, An exception is raised when accessing the URL, we get the expected contents.
Text File at /hello
hello
We see the problems you get when you call async functions in sequential code and how to overcome the issue. What do you think? Do you have a better solution?