Refs #34118 -- Adopted asgiref coroutine detection shims.
Thanks to Mariusz Felisiak for review.
This commit is contained in:
parent
a09d39f286
commit
32d70b2f55
@ -2,7 +2,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync, sync_to_async
|
from asgiref.sync import async_to_sync, iscoroutinefunction, sync_to_async
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
|
from django.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
|
||||||
@ -119,7 +119,7 @@ class BaseHandler:
|
|||||||
- Asynchronous methods are left alone
|
- Asynchronous methods are left alone
|
||||||
"""
|
"""
|
||||||
if method_is_async is None:
|
if method_is_async is None:
|
||||||
method_is_async = asyncio.iscoroutinefunction(method)
|
method_is_async = iscoroutinefunction(method)
|
||||||
if debug and not name:
|
if debug and not name:
|
||||||
name = name or "method %s()" % method.__qualname__
|
name = name or "method %s()" % method.__qualname__
|
||||||
if is_async:
|
if is_async:
|
||||||
@ -191,7 +191,7 @@ class BaseHandler:
|
|||||||
if response is None:
|
if response is None:
|
||||||
wrapped_callback = self.make_view_atomic(callback)
|
wrapped_callback = self.make_view_atomic(callback)
|
||||||
# If it is an asynchronous view, run it in a subthread.
|
# If it is an asynchronous view, run it in a subthread.
|
||||||
if asyncio.iscoroutinefunction(wrapped_callback):
|
if iscoroutinefunction(wrapped_callback):
|
||||||
wrapped_callback = async_to_sync(wrapped_callback)
|
wrapped_callback = async_to_sync(wrapped_callback)
|
||||||
try:
|
try:
|
||||||
response = wrapped_callback(request, *callback_args, **callback_kwargs)
|
response = wrapped_callback(request, *callback_args, **callback_kwargs)
|
||||||
@ -245,7 +245,7 @@ class BaseHandler:
|
|||||||
if response is None:
|
if response is None:
|
||||||
wrapped_callback = self.make_view_atomic(callback)
|
wrapped_callback = self.make_view_atomic(callback)
|
||||||
# If it is a synchronous view, run it in a subthread
|
# If it is a synchronous view, run it in a subthread
|
||||||
if not asyncio.iscoroutinefunction(wrapped_callback):
|
if not iscoroutinefunction(wrapped_callback):
|
||||||
wrapped_callback = sync_to_async(
|
wrapped_callback = sync_to_async(
|
||||||
wrapped_callback, thread_sensitive=True
|
wrapped_callback, thread_sensitive=True
|
||||||
)
|
)
|
||||||
@ -278,7 +278,7 @@ class BaseHandler:
|
|||||||
% (middleware_method.__self__.__class__.__name__,),
|
% (middleware_method.__self__.__class__.__name__,),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
if asyncio.iscoroutinefunction(response.render):
|
if iscoroutinefunction(response.render):
|
||||||
response = await response.render()
|
response = await response.render()
|
||||||
else:
|
else:
|
||||||
response = await sync_to_async(
|
response = await sync_to_async(
|
||||||
@ -346,7 +346,7 @@ class BaseHandler:
|
|||||||
non_atomic_requests = getattr(view, "_non_atomic_requests", set())
|
non_atomic_requests = getattr(view, "_non_atomic_requests", set())
|
||||||
for alias, settings_dict in connections.settings.items():
|
for alias, settings_dict in connections.settings.items():
|
||||||
if settings_dict["ATOMIC_REQUESTS"] and alias not in non_atomic_requests:
|
if settings_dict["ATOMIC_REQUESTS"] and alias not in non_atomic_requests:
|
||||||
if asyncio.iscoroutinefunction(view):
|
if iscoroutinefunction(view):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"You cannot use ATOMIC_REQUESTS with async views."
|
"You cannot use ATOMIC_REQUESTS with async views."
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import iscoroutinefunction, sync_to_async
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
@ -34,7 +33,7 @@ def convert_exception_to_response(get_response):
|
|||||||
no middleware leaks an exception and that the next middleware in the stack
|
no middleware leaks an exception and that the next middleware in the stack
|
||||||
can rely on getting a response instead of an exception.
|
can rely on getting a response instead of an exception.
|
||||||
"""
|
"""
|
||||||
if asyncio.iscoroutinefunction(get_response):
|
if iscoroutinefunction(get_response):
|
||||||
|
|
||||||
@wraps(get_response)
|
@wraps(get_response)
|
||||||
async def inner(request):
|
async def inner(request):
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import asyncio
|
|
||||||
import difflib
|
import difflib
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
@ -26,7 +25,7 @@ from urllib.parse import (
|
|||||||
)
|
)
|
||||||
from urllib.request import url2pathname
|
from urllib.request import url2pathname
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync, iscoroutinefunction
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -401,7 +400,7 @@ class SimpleTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Convert async test methods.
|
# Convert async test methods.
|
||||||
if asyncio.iscoroutinefunction(testMethod):
|
if iscoroutinefunction(testMethod):
|
||||||
setattr(self, self._testMethodName, async_to_sync(testMethod))
|
setattr(self, self._testMethodName, async_to_sync(testMethod))
|
||||||
|
|
||||||
if not skipped:
|
if not skipped:
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import asyncio
|
|
||||||
import collections
|
import collections
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -14,6 +13,8 @@ from types import SimpleNamespace
|
|||||||
from unittest import TestCase, skipIf, skipUnless
|
from unittest import TestCase, skipIf, skipUnless
|
||||||
from xml.dom.minidom import Node, parseString
|
from xml.dom.minidom import Node, parseString
|
||||||
|
|
||||||
|
from asgiref.sync import iscoroutinefunction
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.apps.registry import Apps
|
from django.apps.registry import Apps
|
||||||
from django.conf import UserSettingsHolder, settings
|
from django.conf import UserSettingsHolder, settings
|
||||||
@ -440,7 +441,7 @@ class TestContextDecorator:
|
|||||||
raise TypeError("Can only decorate subclasses of unittest.TestCase")
|
raise TypeError("Can only decorate subclasses of unittest.TestCase")
|
||||||
|
|
||||||
def decorate_callable(self, func):
|
def decorate_callable(self, func):
|
||||||
if asyncio.iscoroutinefunction(func):
|
if iscoroutinefunction(func):
|
||||||
# If the inner function is an async function, we must execute async
|
# If the inner function is an async function, we must execute async
|
||||||
# as well so that the `with` statement executes at the right time.
|
# as well so that the `with` statement executes at the right time.
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import asyncio
|
|
||||||
import inspect
|
import inspect
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
from asgiref.sync import iscoroutinefunction, markcoroutinefunction, sync_to_async
|
||||||
|
|
||||||
|
|
||||||
class RemovedInDjango50Warning(DeprecationWarning):
|
class RemovedInDjango50Warning(DeprecationWarning):
|
||||||
@ -120,16 +119,14 @@ class MiddlewareMixin:
|
|||||||
If get_response is a coroutine function, turns us into async mode so
|
If get_response is a coroutine function, turns us into async mode so
|
||||||
a thread is not consumed during a whole request.
|
a thread is not consumed during a whole request.
|
||||||
"""
|
"""
|
||||||
if asyncio.iscoroutinefunction(self.get_response):
|
if iscoroutinefunction(self.get_response):
|
||||||
# Mark the class as async-capable, but do the actual switch
|
# Mark the class as async-capable, but do the actual switch
|
||||||
# inside __call__ to avoid swapping out dunder methods
|
# inside __call__ to avoid swapping out dunder methods
|
||||||
self._is_coroutine = asyncio.coroutines._is_coroutine
|
markcoroutinefunction(self)
|
||||||
else:
|
|
||||||
self._is_coroutine = None
|
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
# Exit out to async mode, if needed
|
# Exit out to async mode, if needed
|
||||||
if self._is_coroutine:
|
if iscoroutinefunction(self):
|
||||||
return self.__acall__(request)
|
return self.__acall__(request)
|
||||||
response = None
|
response = None
|
||||||
if hasattr(self, "process_request"):
|
if hasattr(self, "process_request"):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import (
|
from django.http import (
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
@ -68,8 +69,8 @@ class View:
|
|||||||
]
|
]
|
||||||
if not handlers:
|
if not handlers:
|
||||||
return False
|
return False
|
||||||
is_async = asyncio.iscoroutinefunction(handlers[0])
|
is_async = iscoroutinefunction(handlers[0])
|
||||||
if not all(asyncio.iscoroutinefunction(h) == is_async for h in handlers[1:]):
|
if not all(iscoroutinefunction(h) == is_async for h in handlers[1:]):
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
f"{cls.__qualname__} HTTP handlers must either be all sync or all "
|
f"{cls.__qualname__} HTTP handlers must either be all sync or all "
|
||||||
"async."
|
"async."
|
||||||
@ -117,7 +118,7 @@ class View:
|
|||||||
|
|
||||||
# Mark the callback if the view class is async.
|
# Mark the callback if the view class is async.
|
||||||
if cls.view_is_async:
|
if cls.view_is_async:
|
||||||
view._is_coroutine = asyncio.coroutines._is_coroutine
|
markcoroutinefunction(view)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ dependencies:
|
|||||||
|
|
||||||
* aiosmtpd_
|
* aiosmtpd_
|
||||||
* argon2-cffi_ 19.1.0+
|
* argon2-cffi_ 19.1.0+
|
||||||
* asgiref_ 3.5.2+ (required)
|
* asgiref_ 3.6.0+ (required)
|
||||||
* bcrypt_
|
* bcrypt_
|
||||||
* colorama_
|
* colorama_
|
||||||
* docutils_
|
* docutils_
|
||||||
|
@ -438,6 +438,9 @@ Miscellaneous
|
|||||||
``DatabaseIntrospection.get_table_description()`` rather than
|
``DatabaseIntrospection.get_table_description()`` rather than
|
||||||
``internal_size`` for ``CharField``.
|
``internal_size`` for ``CharField``.
|
||||||
|
|
||||||
|
* The minimum supported version of ``asgiref`` is increased from 3.5.2 to
|
||||||
|
3.6.0.
|
||||||
|
|
||||||
.. _deprecated-features-4.2:
|
.. _deprecated-features-4.2:
|
||||||
|
|
||||||
Features deprecated in 4.2
|
Features deprecated in 4.2
|
||||||
|
@ -28,10 +28,10 @@ class-based view, this means declaring the HTTP method handlers, such as
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Django uses ``asyncio.iscoroutinefunction`` to test if your view is
|
Django uses ``asgiref.sync.iscoroutinefunction`` to test if your view is
|
||||||
asynchronous or not. If you implement your own method of returning a
|
asynchronous or not. If you implement your own method of returning a
|
||||||
coroutine, ensure you set the ``_is_coroutine`` attribute of the view
|
coroutine, ensure you use ``asgiref.sync.markcoroutinefunction`` so this
|
||||||
to ``asyncio.coroutines._is_coroutine`` so this function returns ``True``.
|
function returns ``True``.
|
||||||
|
|
||||||
Under a WSGI server, async views will run in their own, one-off event loop.
|
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,
|
This means you can use async features, like concurrent async HTTP requests,
|
||||||
|
@ -312,7 +312,7 @@ If your middleware has both ``sync_capable = True`` and
|
|||||||
``async_capable = True``, then Django will pass it the request without
|
``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
|
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
|
async requests by checking if the ``get_response`` object you are passed is a
|
||||||
coroutine function, using ``asyncio.iscoroutinefunction``.
|
coroutine function, using ``asgiref.sync.iscoroutinefunction``.
|
||||||
|
|
||||||
The ``django.utils.decorators`` module contains
|
The ``django.utils.decorators`` module contains
|
||||||
:func:`~django.utils.decorators.sync_only_middleware`,
|
:func:`~django.utils.decorators.sync_only_middleware`,
|
||||||
@ -331,13 +331,13 @@ at an additional performance penalty.
|
|||||||
|
|
||||||
Here's an example of how to create a middleware function that supports both::
|
Here's an example of how to create a middleware function that supports both::
|
||||||
|
|
||||||
import asyncio
|
from asgiref.sync import iscoroutinefunction
|
||||||
from django.utils.decorators import sync_and_async_middleware
|
from django.utils.decorators import sync_and_async_middleware
|
||||||
|
|
||||||
@sync_and_async_middleware
|
@sync_and_async_middleware
|
||||||
def simple_middleware(get_response):
|
def simple_middleware(get_response):
|
||||||
# One-time configuration and initialization goes here.
|
# One-time configuration and initialization goes here.
|
||||||
if asyncio.iscoroutinefunction(get_response):
|
if iscoroutinefunction(get_response):
|
||||||
async def middleware(request):
|
async def middleware(request):
|
||||||
# Do something here!
|
# Do something here!
|
||||||
response = await get_response(request)
|
response = await get_response(request)
|
||||||
|
@ -39,7 +39,7 @@ packages = find:
|
|||||||
include_package_data = true
|
include_package_data = true
|
||||||
zip_safe = false
|
zip_safe = false
|
||||||
install_requires =
|
install_requires =
|
||||||
asgiref >= 3.5.2
|
asgiref >= 3.6.0
|
||||||
backports.zoneinfo; python_version<"3.9"
|
backports.zoneinfo; python_version<"3.9"
|
||||||
sqlparse >= 0.2.2
|
sqlparse >= 0.2.2
|
||||||
tzdata; sys_platform == 'win32'
|
tzdata; sys_platform == 'win32'
|
||||||
|
@ -2,7 +2,7 @@ import asyncio
|
|||||||
import os
|
import os
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync, iscoroutinefunction
|
||||||
|
|
||||||
from django.core.cache import DEFAULT_CACHE_ALIAS, caches
|
from django.core.cache import DEFAULT_CACHE_ALIAS, caches
|
||||||
from django.core.exceptions import ImproperlyConfigured, SynchronousOnlyOperation
|
from django.core.exceptions import ImproperlyConfigured, SynchronousOnlyOperation
|
||||||
@ -84,7 +84,7 @@ class ViewTests(SimpleTestCase):
|
|||||||
with self.subTest(view_cls=view_cls, is_async=is_async):
|
with self.subTest(view_cls=view_cls, is_async=is_async):
|
||||||
self.assertIs(view_cls.view_is_async, is_async)
|
self.assertIs(view_cls.view_is_async, is_async)
|
||||||
callback = view_cls.as_view()
|
callback = view_cls.as_view()
|
||||||
self.assertIs(asyncio.iscoroutinefunction(callback), is_async)
|
self.assertIs(iscoroutinefunction(callback), is_async)
|
||||||
|
|
||||||
def test_mixed_views_raise_error(self):
|
def test_mixed_views_raise_error(self):
|
||||||
class MixedView(View):
|
class MixedView(View):
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import asyncio
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync, iscoroutinefunction
|
||||||
|
|
||||||
from django.contrib.admindocs.middleware import XViewMiddleware
|
from django.contrib.admindocs.middleware import XViewMiddleware
|
||||||
from django.contrib.auth.middleware import (
|
from django.contrib.auth.middleware import (
|
||||||
@ -101,11 +100,11 @@ class MiddlewareMixinTests(SimpleTestCase):
|
|||||||
# Middleware appears as coroutine if get_function is
|
# Middleware appears as coroutine if get_function is
|
||||||
# a coroutine.
|
# a coroutine.
|
||||||
middleware_instance = middleware(async_get_response)
|
middleware_instance = middleware(async_get_response)
|
||||||
self.assertIs(asyncio.iscoroutinefunction(middleware_instance), True)
|
self.assertIs(iscoroutinefunction(middleware_instance), True)
|
||||||
# Middleware doesn't appear as coroutine if get_function is not
|
# Middleware doesn't appear as coroutine if get_function is not
|
||||||
# a coroutine.
|
# a coroutine.
|
||||||
middleware_instance = middleware(sync_get_response)
|
middleware_instance = middleware(sync_get_response)
|
||||||
self.assertIs(asyncio.iscoroutinefunction(middleware_instance), False)
|
self.assertIs(iscoroutinefunction(middleware_instance), False)
|
||||||
|
|
||||||
def test_sync_to_async_uses_base_thread_and_connection(self):
|
def test_sync_to_async_uses_base_thread_and_connection(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import asyncio
|
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
|
||||||
|
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.template import engines
|
from django.template import engines
|
||||||
@ -15,9 +15,8 @@ log = []
|
|||||||
class BaseMiddleware:
|
class BaseMiddleware:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
if asyncio.iscoroutinefunction(self.get_response):
|
if iscoroutinefunction(self.get_response):
|
||||||
# Mark the class as async-capable.
|
markcoroutinefunction(self)
|
||||||
self._is_coroutine = asyncio.coroutines._is_coroutine
|
|
||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
aiosmtpd
|
aiosmtpd
|
||||||
asgiref >= 3.5.2
|
asgiref >= 3.6.0
|
||||||
argon2-cffi >= 16.1.0
|
argon2-cffi >= 16.1.0
|
||||||
backports.zoneinfo; python_version < '3.9'
|
backports.zoneinfo; python_version < '3.9'
|
||||||
bcrypt
|
bcrypt
|
||||||
|
Loading…
x
Reference in New Issue
Block a user