summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2020-02-12 15:15:00 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-03-18 19:59:12 +0100
commitfc0fa72ff4cdbf5861a366e31cb8bbacd44da22d (patch)
treed419ce531586808b0a111664907b859cb6d22862 /docs
parent3f7e4b16bf58f99c71570ba75dc97db8265071be (diff)
Fixed #31224 -- Added support for asynchronous views and middleware.
This implements support for asynchronous views, asynchronous tests, asynchronous middleware, and an asynchronous test client.
Diffstat (limited to 'docs')
-rw-r--r--docs/index.txt6
-rw-r--r--docs/ref/utils.txt25
-rw-r--r--docs/releases/3.1.txt37
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--docs/topics/async.txt105
-rw-r--r--docs/topics/http/middleware.txt87
-rw-r--r--docs/topics/http/views.txt25
-rw-r--r--docs/topics/testing/advanced.txt11
-rw-r--r--docs/topics/testing/tools.txt56
9 files changed, 342 insertions, 11 deletions
diff --git a/docs/index.txt b/docs/index.txt
index 3b33478f14..f82e3d85c1 100644
--- a/docs/index.txt
+++ b/docs/index.txt
@@ -110,8 +110,7 @@ manipulating the data of your Web application. Learn more about it below:
:doc:`Custom lookups <howto/custom-lookups>` |
:doc:`Query Expressions <ref/models/expressions>` |
:doc:`Conditional Expressions <ref/models/conditional-expressions>` |
- :doc:`Database Functions <ref/models/database-functions>` |
- :doc:`Asynchronous Support <topics/async>`
+ :doc:`Database Functions <ref/models/database-functions>`
* **Other:**
:doc:`Supported databases <ref/databases>` |
@@ -131,7 +130,8 @@ to know about views via the links below:
:doc:`URLconfs <topics/http/urls>` |
:doc:`View functions <topics/http/views>` |
:doc:`Shortcuts <topics/http/shortcuts>` |
- :doc:`Decorators <topics/http/decorators>`
+ :doc:`Decorators <topics/http/decorators>` |
+ :doc:`Asynchronous Support <topics/async>`
* **Reference:**
:doc:`Built-in Views <ref/views>` |
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index 741ed585f2..0375b2e63b 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -210,6 +210,31 @@ The functions defined in this module share the following properties:
def my_view(request):
pass
+.. function:: sync_only_middleware(middleware)
+
+ .. versionadded:: 3.1
+
+ Marks a middleware as :ref:`synchronous-only <async-middleware>`. (The
+ default in Django, but this allows you to future-proof if the default ever
+ changes in a future release.)
+
+.. function:: async_only_middleware(middleware)
+
+ .. versionadded:: 3.1
+
+ Marks a middleware as :ref:`asynchronous-only <async-middleware>`. Django
+ will wrap it in an asynchronous event loop when it is called from the WSGI
+ request path.
+
+.. function:: sync_and_async_middleware(middleware)
+
+ .. versionadded:: 3.1
+
+ Marks a middleware as :ref:`sync and async compatible <async-middleware>`,
+ this allows to avoid converting requests. You must implement detection of
+ the current request type to use this decorator. See :ref:`asynchronous
+ middleware documentation <async-middleware>` for details.
+
``django.utils.encoding``
=========================
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index 642575d023..efc57e8d01 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -27,6 +27,43 @@ officially support the latest release of each series.
What's new in Django 3.1
========================
+Asynchronous views and middleware support
+-----------------------------------------
+
+Django now supports a fully asynchronous request path, including:
+
+* :ref:`Asynchronous views <async-views>`
+* :ref:`Asynchronous middleware <async-middleware>`
+* :ref:`Asynchronous tests and test client <async-tests>`
+
+To get started with async views, you need to declare a view using
+``async def``::
+
+ 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 :doc:`/topics/async`
+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.
+
Minor features
--------------
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index 99bdba17b3..3d2d214409 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -144,6 +144,7 @@ databrowse
datafile
dataset
datasets
+datastores
datatype
datetimes
Debian
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index b502e6b65b..2ca76c972b 100644
--- a/docs/topics/async.txt
+++ b/docs/topics/async.txt
@@ -6,13 +6,106 @@ Asynchronous support
.. currentmodule:: asgiref.sync
-Django has developing support for asynchronous ("async") Python, but does not
-yet support asynchronous views or middleware; they will be coming in a future
-release.
+Django has support for writing asynchronous ("async") views, along with an
+entirely async-enabled request stack if you are running under
+:doc:`ASGI </howto/deployment/asgi/index>` rather than WSGI. Async views will
+still work under WSGI, but with performance penalties, and without the ability
+to have efficient long-running requests.
-There is limited support for other parts of the async ecosystem; namely, Django
-can natively talk :doc:`ASGI </howto/deployment/asgi/index>`, and some async
-safety support.
+We're still working on asynchronous support for the ORM and other parts of
+Django; you can expect to see these in future releases. For now, you can use
+the :func:`sync_to_async` adapter to interact with normal Django, as well as
+use a whole range of Python asyncio libraries natively. See below for more
+details.
+
+.. versionchanged:: 3.1
+
+ Support for async views was added.
+
+Async views
+===========
+
+.. versionadded:: 3.1
+
+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, asynchronous views will run in their own, one-off event
+loop. This means that you can do things like parallel, async HTTP calls to APIs
+without any issues, but you will not get the benefits of an asynchronous
+request stack.
+
+If you want these benefits - which are mostly around the ability to service
+hundreds of connections without using any Python threads (enabling slow
+streaming, long-polling, and other exciting response types) - you will need to
+deploy Django using :doc:`ASGI </howto/deployment/asgi/index>` instead.
+
+.. warning::
+
+ You will only get the benefits of a fully-asynchronous request stack if you
+ have *no synchronous middleware* loaded into your site; if there is a piece
+ of synchronous middleware, then Django must use a thread per request to
+ safely emulate a synchronous environment for it.
+
+ Middleware can be built to support :ref:`both sync and async
+ <async-middleware>` contexts. Some of Django's middleware is built like
+ this, but not all. To see what middleware Django has to adapt, you can turn
+ on debug logging for the ``django.request`` logger and look for log
+ messages about *`"Synchronous middleware ... adapted"*.
+
+In either ASGI or WSGI mode, though, you can safely use asynchronous support to
+run code in parallel rather than serially, which is especially handy when
+dealing with external APIs or datastores.
+
+If you want to call a part of Django that is still synchronous (like the ORM)
+you will need to wrap it in a :func:`sync_to_async` call, like this::
+
+ from asgiref.sync import sync_to_async
+
+ results = sync_to_async(MyModel.objects.get)(pk=123)
+
+You may find it easier to move any ORM code into its own function and call that
+entire function using :func:`sync_to_async`. If you accidentally try to call
+part of Django that is still synchronous-only from an async view, you will
+trigger Django's :ref:`asynchronous safety protection <async-safety>` 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 true of middleware as well, however. Django will attempt to minimize
+the number of context-switches. 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.
+
+If, however, 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 asynchronous mode for the view, holding the synchronous thread
+open for middleware exception propagation. This may not be noticeable, but bear
+in mind that even adding a single piece of synchronous middleware can drag your
+whole async project down to running with one thread per request, and the
+associated performance penalties.
+
+You should do your own performance testing to see what effect ASGI vs. WSGI has
+on your code. In some cases, there may be a performance increase even for
+purely-synchronous codebase under ASGI because the request-handling code is
+still all running asynchronously. In general, though, you will only want to
+enable ASGI mode if you have asynchronous code in your site.
.. _async-safety:
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt
index d72d39de5e..3fe00b947f 100644
--- a/docs/topics/http/middleware.txt
+++ b/docs/topics/http/middleware.txt
@@ -71,6 +71,10 @@ method from the handler which takes care of applying :ref:`view middleware
applying :ref:`template-response <template-response-middleware>` and
:ref:`exception <exception-middleware>` middleware.
+Middleware can either support only synchronous Python (the default), only
+asynchronous Python, or both. See :ref:`async-middleware` for details of how to
+advertise what you support, and know what kind of request you are getting.
+
Middleware can live anywhere on your Python path.
``__init__(get_response)``
@@ -282,6 +286,81 @@ if the very next middleware in the chain raises an
that exception; instead it will get an :class:`~django.http.HttpResponse`
object with a :attr:`~django.http.HttpResponse.status_code` of 404.
+.. _async-middleware:
+
+Asynchronous support
+====================
+
+.. versionadded:: 3.1
+
+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 in whatever form
+it is currently in. You can work out what type of request you have by seeing
+if the ``get_response`` object you are passed is a coroutine function or not
+(using :py:func:`asyncio.iscoroutinefunction`).
+
+The ``django.utils.decorators`` module contains
+:func:`~django.utils.decorators.sync_only_middleware`,
+:func:`~django.utils.decorators.async_only_middleware`, and
+:func:`~django.utils.decorators.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 detect and adapt your middleware if it supports
+both::
+
+ 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.
+
.. _upgrading-middleware:
Upgrading pre-Django 1.10-style middleware
@@ -292,8 +371,8 @@ Upgrading pre-Django 1.10-style middleware
Django provides ``django.utils.deprecation.MiddlewareMixin`` to ease creating
middleware classes that are compatible with both :setting:`MIDDLEWARE` and the
-old ``MIDDLEWARE_CLASSES``. All middleware classes included with Django
-are compatible with both settings.
+old ``MIDDLEWARE_CLASSES``, and support synchronous and asynchronous requests.
+All middleware classes included with Django are compatible with both settings.
The mixin provides an ``__init__()`` method that requires a ``get_response``
argument and stores it in ``self.get_response``.
@@ -345,3 +424,7 @@ These are the behavioral differences between using :setting:`MIDDLEWARE` and
HTTP response, and then the next middleware in line will see that
response. Middleware are never skipped due to a middleware raising an
exception.
+
+.. versionchanged:: 3.1
+
+ Support for asynchronous requests was added to the ``MiddlewareMixin``.
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt
index 3076192676..1d3f9b5a11 100644
--- a/docs/topics/http/views.txt
+++ b/docs/topics/http/views.txt
@@ -202,3 +202,28 @@ in a test view. For example::
response = self.client.get('/403/')
# Make assertions on the response here. For example:
self.assertContains(response, 'Error handler content', status_code=403)
+
+.. _async-views:
+
+Asynchronous views
+==================
+
+.. versionadded:: 3.1
+
+As well as being synchronous functions, views can also be asynchronous
+functions (``async def``). Django will automatically detect these and run them
+in an asynchronous context. You will need to be using an asynchronous (ASGI)
+server to get the full power of them, however.
+
+Here's an example of an asynchronous view::
+
+ from django.http import HttpResponse
+ import datetime
+
+ async def current_datetime(request):
+ now = datetime.datetime.now()
+ html = '<html><body>It is now %s.</body></html>' % now
+ return HttpResponse(html)
+
+You can read more about Django's asynchronous support, and how to best use
+asynchronous views, in :doc:`/topics/async`.
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index a909c5f3d8..8aca92ea36 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -67,6 +67,17 @@ The following is a unit test using the request factory::
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
+AsyncRequestFactory
+-------------------
+
+``RequestFactory`` creates WSGI-like requests. If you want to create ASGI-like
+requests, including having a correct ASGI ``scope``, you can instead use
+``django.test.AsyncRequestFactory``.
+
+This class is directly API-compatible with ``RequestFactory``, with the only
+difference being that it returns ``ASGIRequest`` instances rather than
+``WSGIRequest`` instances. All of its methods are still synchronous callables.
+
Testing class-based views
=========================
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 02433bbf5e..64fee656bb 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -1755,6 +1755,62 @@ You can also exclude tests by tag. To run core tests if they are not slow:
test has two tags and you select one of them and exclude the other, the test
won't be run.
+.. _async-tests:
+
+Testing asynchronous code
+=========================
+
+.. versionadded:: 3.1
+
+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::
+
+ 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 :doc:`asynchronous request path </topics/async>`, 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.
+
+.. warning::
+
+ If you are using test decorators, they must be async-compatible to ensure
+ they work correctly. Django's built-in decorators will behave correctly, but
+ third-party ones may appear to not execute (they will "wrap" the wrong part
+ of the execution flow and not your test).
+
+ If you need to use these decorators, then you should decorate your test
+ methods with :func:`~asgiref.sync.async_to_sync` *inside* of them instead::
+
+ from asgiref.sync import async_to_sync
+ from django.test import TestCase
+
+ class MyTests(TestCase):
+
+ @mock.patch(...)
+ @async_to_sync
+ def test_my_thing(self):
+ ...
+
.. _topics-testing-email:
Email services