1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Refs #34118 -- Adopted asgiref coroutine detection shims.

Thanks to Mariusz Felisiak for review.
This commit is contained in:
Carlton Gibson 2022-12-20 11:10:48 +01:00 committed by GitHub
parent a09d39f286
commit 32d70b2f55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 42 additions and 44 deletions

View File

@ -2,7 +2,7 @@ import asyncio
import logging
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.core.exceptions import ImproperlyConfigured, MiddlewareNotUsed
@ -119,7 +119,7 @@ class BaseHandler:
- Asynchronous methods are left alone
"""
if method_is_async is None:
method_is_async = asyncio.iscoroutinefunction(method)
method_is_async = iscoroutinefunction(method)
if debug and not name:
name = name or "method %s()" % method.__qualname__
if is_async:
@ -191,7 +191,7 @@ class BaseHandler:
if response is None:
wrapped_callback = self.make_view_atomic(callback)
# 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)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs)
@ -245,7 +245,7 @@ class BaseHandler:
if response is None:
wrapped_callback = self.make_view_atomic(callback)
# 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, thread_sensitive=True
)
@ -278,7 +278,7 @@ class BaseHandler:
% (middleware_method.__self__.__class__.__name__,),
)
try:
if asyncio.iscoroutinefunction(response.render):
if iscoroutinefunction(response.render):
response = await response.render()
else:
response = await sync_to_async(
@ -346,7 +346,7 @@ class BaseHandler:
non_atomic_requests = getattr(view, "_non_atomic_requests", set())
for alias, settings_dict in connections.settings.items():
if settings_dict["ATOMIC_REQUESTS"] and alias not in non_atomic_requests:
if asyncio.iscoroutinefunction(view):
if iscoroutinefunction(view):
raise RuntimeError(
"You cannot use ATOMIC_REQUESTS with async views."
)

View File

@ -1,9 +1,8 @@
import asyncio
import logging
import sys
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.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
can rely on getting a response instead of an exception.
"""
if asyncio.iscoroutinefunction(get_response):
if iscoroutinefunction(get_response):
@wraps(get_response)
async def inner(request):

View File

@ -1,4 +1,3 @@
import asyncio
import difflib
import inspect
import json
@ -26,7 +25,7 @@ from urllib.parse import (
)
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.conf import settings
@ -401,7 +400,7 @@ class SimpleTestCase(unittest.TestCase):
)
# Convert async test methods.
if asyncio.iscoroutinefunction(testMethod):
if iscoroutinefunction(testMethod):
setattr(self, self._testMethodName, async_to_sync(testMethod))
if not skipped:

View File

@ -1,4 +1,3 @@
import asyncio
import collections
import logging
import os
@ -14,6 +13,8 @@ from types import SimpleNamespace
from unittest import TestCase, skipIf, skipUnless
from xml.dom.minidom import Node, parseString
from asgiref.sync import iscoroutinefunction
from django.apps import apps
from django.apps.registry import Apps
from django.conf import UserSettingsHolder, settings
@ -440,7 +441,7 @@ class TestContextDecorator:
raise TypeError("Can only decorate subclasses of unittest.TestCase")
def decorate_callable(self, func):
if asyncio.iscoroutinefunction(func):
if iscoroutinefunction(func):
# If the inner function is an async function, we must execute async
# as well so that the `with` statement executes at the right time.
@wraps(func)

View File

@ -1,8 +1,7 @@
import asyncio
import inspect
import warnings
from asgiref.sync import sync_to_async
from asgiref.sync import iscoroutinefunction, markcoroutinefunction, sync_to_async
class RemovedInDjango50Warning(DeprecationWarning):
@ -120,16 +119,14 @@ class MiddlewareMixin:
If get_response is a coroutine function, turns us into async mode so
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
# inside __call__ to avoid swapping out dunder methods
self._is_coroutine = asyncio.coroutines._is_coroutine
else:
self._is_coroutine = None
markcoroutinefunction(self)
def __call__(self, request):
# Exit out to async mode, if needed
if self._is_coroutine:
if iscoroutinefunction(self):
return self.__acall__(request)
response = None
if hasattr(self, "process_request"):

View File

@ -1,6 +1,7 @@
import asyncio
import logging
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
from django.core.exceptions import ImproperlyConfigured
from django.http import (
HttpResponse,
@ -68,8 +69,8 @@ class View:
]
if not handlers:
return False
is_async = asyncio.iscoroutinefunction(handlers[0])
if not all(asyncio.iscoroutinefunction(h) == is_async for h in handlers[1:]):
is_async = iscoroutinefunction(handlers[0])
if not all(iscoroutinefunction(h) == is_async for h in handlers[1:]):
raise ImproperlyConfigured(
f"{cls.__qualname__} HTTP handlers must either be all sync or all "
"async."
@ -117,7 +118,7 @@ class View:
# Mark the callback if the view class is async.
if cls.view_is_async:
view._is_coroutine = asyncio.coroutines._is_coroutine
markcoroutinefunction(view)
return view

View File

@ -278,7 +278,7 @@ dependencies:
* aiosmtpd_
* argon2-cffi_ 19.1.0+
* asgiref_ 3.5.2+ (required)
* asgiref_ 3.6.0+ (required)
* bcrypt_
* colorama_
* docutils_

View File

@ -438,6 +438,9 @@ Miscellaneous
``DatabaseIntrospection.get_table_description()`` rather than
``internal_size`` for ``CharField``.
* The minimum supported version of ``asgiref`` is increased from 3.5.2 to
3.6.0.
.. _deprecated-features-4.2:
Features deprecated in 4.2

View File

@ -28,10 +28,10 @@ class-based view, this means declaring the HTTP method handlers, such as
.. 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
coroutine, ensure you set the ``_is_coroutine`` attribute of the view
to ``asyncio.coroutines._is_coroutine`` so this function returns ``True``.
coroutine, ensure you use ``asgiref.sync.markcoroutinefunction`` 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,

View File

@ -312,7 +312,7 @@ 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``.
coroutine function, using ``asgiref.sync.iscoroutinefunction``.
The ``django.utils.decorators`` module contains
: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::
import asyncio
from asgiref.sync import iscoroutinefunction
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):
if iscoroutinefunction(get_response):
async def middleware(request):
# Do something here!
response = await get_response(request)

View File

@ -39,7 +39,7 @@ packages = find:
include_package_data = true
zip_safe = false
install_requires =
asgiref >= 3.5.2
asgiref >= 3.6.0
backports.zoneinfo; python_version<"3.9"
sqlparse >= 0.2.2
tzdata; sys_platform == 'win32'

View File

@ -2,7 +2,7 @@ import asyncio
import os
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.exceptions import ImproperlyConfigured, SynchronousOnlyOperation
@ -84,7 +84,7 @@ class ViewTests(SimpleTestCase):
with self.subTest(view_cls=view_cls, is_async=is_async):
self.assertIs(view_cls.view_is_async, is_async)
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):
class MixedView(View):

View File

@ -1,7 +1,6 @@
import asyncio
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.auth.middleware import (
@ -101,11 +100,11 @@ class MiddlewareMixinTests(SimpleTestCase):
# Middleware appears as coroutine if get_function is
# a coroutine.
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
# a coroutine.
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):
"""

View File

@ -1,4 +1,4 @@
import asyncio
from asgiref.sync import iscoroutinefunction, markcoroutinefunction
from django.http import Http404, HttpResponse
from django.template import engines
@ -15,9 +15,8 @@ log = []
class BaseMiddleware:
def __init__(self, get_response):
self.get_response = get_response
if asyncio.iscoroutinefunction(self.get_response):
# Mark the class as async-capable.
self._is_coroutine = asyncio.coroutines._is_coroutine
if iscoroutinefunction(self.get_response):
markcoroutinefunction(self)
def __call__(self, request):
return self.get_response(request)

View File

@ -1,5 +1,5 @@
aiosmtpd
asgiref >= 3.5.2
asgiref >= 3.6.0
argon2-cffi >= 16.1.0
backports.zoneinfo; python_version < '3.9'
bcrypt