.. index:: pair: ASGI; servers .. _django_3_1_async_topics: ======================================================================================= Asynchronous topics by **Django documentation** ======================================================================================= .. seealso:: - https://docs.djangoproject.com/en/3.1/topics/async/ - https://docs.djangoproject.com/en/dev/releases/3.1/#asynchronous-views-and-middleware-support - https://docs.djangoproject.com/en/3.1/topics/async/#async-safety .. contents:: :depth: 3 Django 3.1 =============== .. seealso:: - https://docs.djangoproject.com/en/dev/releases/3.1/#asynchronous-views-and-middleware-support Asynchronous views and middleware support ------------------------------------------ Django now supports a fully asynchronous request path, including: - :ref:`Asynchronous views ` - :ref:`Asynchronous middleware ` - :ref:`Asynchronous tests and test client ` To get started with async views, you need to declare a view using **async def**: .. code-block:: python :linenos: async def my_view(request): await asyncio.sleep(0.5) return HttpResponse('Hello, async world!') All asynchronous features are supported whether you are running under WSGI or ASGI mode. However, there will be performance penalties using async code in WSGI mode. You can read more about the specifics in Asynchronous support documentation. You are free to mix async and sync views, middleware, and tests as much as you want. Django will ensure that you always end up with the right execution context. We expect most projects will keep the majority of their views synchronous, and only have a select few running in async mode - but it is entirely your choice. Django’s ORM, cache layer, and other pieces of code that do long-running network calls do not yet support async access. We expect to add support for them in upcoming releases. Async views are ideal, however, if you are doing a lot of API or HTTP calls inside your view, you can now natively do all those HTTP calls in parallel to considerably speed up your view’s execution. Asynchronous support should be entirely backwards-compatible and we have tried to ensure that it has no speed regressions for your existing, synchronous code. It should have no noticeable effect on any existing Django projects. .. _asynchronous_views_django_3_1: topics/http/views **Async views** ========================================= .. seealso:: - https://docs.djangoproject.com/en/dev/topics/http/views/#async-views As well as being synchronous functions, views can also be asynchronous (“async”) functions, normally defined using Python’s **async def** syntax. Django will automatically detect these and run them in an async context. **However, you will need to use an async server based on ASGI to get their performance benefits**. Here’s an example of an async view: .. code-block:: python :linenos: import datetime # https://docs.python.org/3.9/library/zoneinfo.html from zoneinfo import ZoneInfo from django.http import HttpResponse async def current_datetime(request): now = datetime.datetime.now(tz=ZoneInfo("Europe/Paris")) html = 'It is now %s.' % now return HttpResponse(html) You can read more about Django’s async support, and how to best use async views, in Asynchronous support. .. _asynchronous_middleware_django_3_1: topics/http/middleware **async-middleware Asynchronous support** ==================================================================== .. seealso:: - https://docs.djangoproject.com/en/dev/topics/http/middleware/#async-middleware Middleware can support any combination of synchronous and asynchronous requests. Django will adapt requests to fit the middleware’s requirements if it cannot support both, but at a performance penalty. By default, Django assumes that your middleware is capable of handling only synchronous requests. To change these assumptions, set the following attributes on your middleware factory function or class: - **sync_capable** is a boolean indicating if the middleware can handle synchronous requests. **Defaults to True**. - **async_capable** is a boolean indicating if the middleware can handle asynchronous requests. **Defaults to False**. If your middleware has both sync_capable = True and async_capable = True, then Django will pass it the request without converting it. In this case, you can work out if your middleware will receive async requests by checking if the get_response object you are passed is a coroutine function, using **asyncio.iscoroutinefunction()**. The **django.utils.decorators** module contains: - **sync_only_middleware()**, - async_only_middleware(), - and sync_and_async_middleware() decorators that allow you to apply these flags to middleware factory functions. The returned callable must match the sync or async nature of the get_response method. If you have an asynchronous get_response, you must return a coroutine function (async def). process_view, process_template_response and process_exception methods, if they are provided, should also be adapted to match the sync/async mode. However, Django will individually adapt them as required if you do not, at an additional performance penalty. Here’s an example of how to create a middleware function that supports both: .. code-block:: python :linenos: import asyncio from django.utils.decorators import sync_and_async_middleware @sync_and_async_middleware def simple_middleware(get_response): # One-time configuration and initialization goes here. if asyncio.iscoroutinefunction(get_response): async def middleware(request): # Do something here! response = await get_response(request) return response else: def middleware(request): # Do something here! response = get_response(request) return response return middleware .. note:: If you declare a hybrid middleware that supports both synchronous and asynchronous calls, the kind of call you get may not match the underlying view. Django will optimize the middleware call stack to have as few sync/async transitions as possible. Thus, even if you are wrapping an async view, you may be called in sync mode if there is other, synchronous middleware between you and the view. .. _asynchronous_test_django_3_1: topics/testing/tools/#async-tests **Testing asynchronous code** ================================================================ .. seealso:: - https://docs.djangoproject.com/en/dev/topics/testing/tools/#async-tests If you merely want to test the output of your asynchronous views, the standard test client will run them inside their own asynchronous loop without any extra work needed on your part. However, if you want to write fully-asynchronous tests for a Django project, you will need to take several things into account. Firstly, **your tests must be async def methods** on the test class (in order to give them an asynchronous context). Django will automatically detect any async def tests and wrap them so they run in their own event loop. If you are testing from an asynchronous function, you must also use the asynchronous test client. This is available as django.test.AsyncClient, or as self.async_client on any test. With the exception of the follow parameter, which is not supported, AsyncClient has the same methods and signatures as the synchronous (normal) test client, but any method that makes a request must be awaited: .. code-block:: python :linenos: async def test_my_thing(self): response = await self.async_client.get('/some-url/') self.assertEqual(response.status_code, 200) The asynchronous client can also call synchronous views; it runs through `Django’s asynchronous request path `_, which supports both. Any view called through the AsyncClient will get an ASGIRequest object for its request rather than the WSGIRequest that the normal client creates. topics/async/ Introduction =========================== .. seealso:: - https://docs.djangoproject.com/en/3.1/topics/async/ Django has support for writing asynchronous (“async”) views, along with an entirely async-enabled request stack if you are running under ASGI. Async views will still work under WSGI, but with **performance penalties**, and without the ability to have efficient long-running requests. We’re still working on async support for the ORM and other parts of Django. You can expect to see this in future releases. For now, you can use the `sync_to_async() adapter `_ to interact with the sync parts of Django. There is also a whole range of async-native Python libraries that you can integrate with. **Async views** ================= Any view can be declared async by making the callable part of it return a coroutine, commonly, this is done using **async def**. For a function-based view, this means declaring the whole view using async def. For a class-based view, this means making its __call__() method an async def (not its __init__() or as_view()). .. note:: Django uses asyncio.iscoroutinefunction to test if your view is asynchronous or not. If you implement your own method of returning a coroutine, ensure you set the _is_coroutine attribute of the view to asyncio.coroutines._is_coroutine so this function returns True. Under a WSGI server, async views will run in their own, one-off event loop. This means you can use async features, like concurrent async HTTP requests, without any issues, but you **will not get the benefits of an async stack**. The main benefits are the ability to service hundreds of connections **without using Python threads**. This allows you to use **slow streaming, long-polling**, and other exciting response types. If you want to use these, you will need to deploy Django using `ASGI `_ instead. In both ASGI and WSGI mode, you can still safely use asynchronous support to run code concurrently rather than serially. This is especially handy when dealing with external APIs or data stores. If you want to call a part of Django that is still synchronous, like the ORM, you will need to wrap it in a sync_to_async() call. For example (to be completed, see :ref:`example_3_async_django`) .. code-block:: python :linenos: # https://docs.python.org/3/library/asyncio-task.html import asyncio # https://docs.djangoproject.com/en/3.1/topics/async/#async-to-sync from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async results = sync_to_async(Blog.objects.get)(pk=123) loop = asyncio.get_event_loop() loop.create_task(get_blog)(pk) **You may find it easier to move any ORM code into its own function and call that entire function using sync_to_async()**. For example (to be completed, see :ref:`example_3_async_django`) .. code-block:: python :linenos: # https://docs.python.org/3/library/asyncio-task.html import asyncio # https://docs.djangoproject.com/en/3.1/topics/async/#async-to-sync from asgiref.sync import sync_to_async @sync_to_async def get_blog(pk): return Blog.objects.select_related('author').get(pk=pk) loop = asyncio.get_event_loop() loop.create_task(get_blog)(pk) If you accidentally try to call a part of Django that is still synchronous-only from an async view, you will trigger Django’s `asynchronous safety protection `_ to protect your data from corruption. Performance =============== When running in a mode that does not match the view (e.g. an async view under WSGI, or a traditional sync view under ASGI), Django must emulate the other call style to allow your code to run. This context-switch causes a small performance penalty of around a millisecond. This is also true of middleware. Django will attempt to minimize the number of context-switches between sync and async. If you have an ASGI server, but all your middleware and views are synchronous, it will switch just once, before it enters the middleware stack. However, if you put synchronous middleware between an ASGI server and an asynchronous view, it will have to switch into sync mode for the middleware and then back to async mode for the view. Django will also hold the sync thread open for middleware exception propagation. This may not be noticeable at first, but adding this penalty of one thread per request can remove any async performance advantage. You should do your own performance testing to see what effect ASGI versus WSGI has on your code. In some cases, there may be a performance increase even for a purely synchronous codebase under ASGI because the request-handling code is still all running asynchronously. In general you will only want to enable ASGI mode if you have asynchronous code in your project. django.core.handlers.asgi.py ============================ .. seealso:: - https://github.com/django/django/blob/master/django/core/handlers/asgi.py .. literalinclude:: asgi.py :linenos: