8000 Refs #34118 -- Adopted asgiref coroutine detection shims. by carltongibson · Pull Request #16380 · django/django · GitHub
[go: up one dir, main page]

Skip to content

Refs #34118 -- Adopted asgiref coroutine detection shims. #16380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Refs #34118 -- Adopted asgiref coroutine detection shims.
  • Loading branch information
carltongibson committed Dec 20, 2022
commit f24b783b3bd6f3cd48204d784ab381ae536c8b1f
12 changes: 6 additions & 6 deletions django/core/handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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
Expand Down Expand Up @@ -119,7 +119,7 @@ def adapt_method_mode(
- 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:
Expand Down Expand Up @@ -191,7 +191,7 @@ def _get_response(self, request):
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)
Expand Down Expand Up @@ -245,7 +245,7 @@ async def _get_response_async(self, request):
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
)
Expand Down Expand Up @@ -278,7 +278,7 @@ async def _get_response_async(self, request):
% (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(
Expand Down Expand Up @@ -346,7 +346,7 @@ def make_view_atomic(self, view):
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):
10000 raise RuntimeError(
"You cannot use ATOMIC_REQUESTS with async views."
)
Expand Down
5 changes: 2 additions & 3 deletions django/core/handlers/exception.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand Down
5 changes: 2 additions & 3 deletions django/test/testcases.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
import difflib
import inspect
import json
Expand Down Expand Up @@ -26,7 +25,7 @@
)
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
Expand Down Expand Up @@ -401,7 +400,7 @@ def _setup_and_call(self, result, debug=False):
)

# Convert async test methods.
if asyncio.iscoroutinefunction(testMethod):
if iscoroutinefunction(testMethod):
setattr(self, self._testMethodName, async_to_sync(testMethod))

if not skipped:
Expand Down
5 changes: 3 additions & 2 deletions django/test/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import asyncio
import collections
import logging
import os
Expand All @@ -14,6 +13,8 @@
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
Expand Down Expand Up @@ -440,7 +441,7 @@ def setUp(inner_self):
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)
Expand Down
11 changes: 4 additions & 7 deletions django/utils/deprecation.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -120,16 +119,14 @@ def _async_check(self):
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"):
Expand Down
9 changes: 5 additions & 4 deletions django/views/generic/base.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -68,8 +69,8 @@ def view_is_async(cls):
]
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."
Expand Down Expand Up @@ -117,7 +118,7 @@ def view(request, *args, **kwargs):

# 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

Expand Down
6 changes: 3 additions & 3 deletions docs/topics/async.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions docs/topics/http/middleware.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions tests/async/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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
Expand Down Expand Up @@ -84,7 +84,7 @@ def test_views_are_correctly_marked(self):
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):
Expand Down
7 changes: 3 additions & 4 deletions tests/deprecation/test_middleware_mixin.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -101,11 +100,11 @@ def sync_get_response(request):
# 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):
"""
Expand Down
7 changes: 3 additions & 4 deletions tests/middleware_exceptions/middleware.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import asyncio
from asgiref.sync import iscoroutinefunction, markcoroutinefunction

from django.http import Http404, HttpResponse
from django.template import engines
Expand All @@ -15,9 +15,8 @@
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)
Expand Down
0