Skip to content
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

Added coroutine detection shims for Python 3.12 #360

Merged
merged 3 commits into from Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions asgiref/compatibility.py
@@ -1,6 +1,7 @@
import asyncio
import inspect

from .sync import iscoroutinefunction


def is_double_callable(application):
"""
Expand All @@ -18,10 +19,10 @@ def is_double_callable(application):
if hasattr(application, "__call__"):
# We only check to see if its __call__ is a coroutine function -
# if it's not, it still might be a coroutine function itself.
if asyncio.iscoroutinefunction(application.__call__):
if iscoroutinefunction(application.__call__):
return False
# Non-classes we just check directly
return not asyncio.iscoroutinefunction(application)
return not iscoroutinefunction(application)


def double_to_single_callable(application):
Expand Down
28 changes: 25 additions & 3 deletions asgiref/sync.py
Expand Up @@ -26,19 +26,41 @@ def _restore_context(context):
cvar.set(context.get(cvar))


# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for
# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker.
# The latter is replaced with the inspect.markcoroutinefunction decorator.
# Until 3.12 is the minimum supported Python version, provide a shim.
# Django 4.0 only supports 3.8+, so don't concern with the _or_partial backport.

# Type hint: should be generic: whatever T it takes it returns. (Same id)
def markcoroutinefunction(func: Any) -> Any:
if hasattr(inspect, "markcoroutinefunction"):
return inspect.markcoroutinefunction(func)
else:
func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore
return func


def iscoroutinefunction(func: Any) -> bool:
if hasattr(inspect, "markcoroutinefunction"):
return inspect.iscoroutinefunction(func)
else:
return asyncio.iscoroutinefunction(func)


def _iscoroutinefunction_or_partial(func: Any) -> bool:
# Python < 3.8 does not correctly determine partially wrapped
# coroutine functions are coroutine functions, hence the need for
# this to exist. Code taken from CPython.
if sys.version_info >= (3, 8):
return asyncio.iscoroutinefunction(func)
return iscoroutinefunction(func)
else:
while inspect.ismethod(func):
func = func.__func__
while isinstance(func, functools.partial):
func = func.func

return asyncio.iscoroutinefunction(func)
return iscoroutinefunction(func)


class ThreadSensitiveContext:
Expand Down Expand Up @@ -356,7 +378,7 @@ def __init__(
self.func = func
functools.update_wrapper(self, func)
self._thread_sensitive = thread_sensitive
self._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore
markcoroutinefunction(self)
if thread_sensitive and executor is not None:
raise TypeError("executor must not be set when thread_sensitive is True")
self._executor = executor
Expand Down
11 changes: 8 additions & 3 deletions tests/test_sync.py
Expand Up @@ -10,7 +10,12 @@

import pytest

from asgiref.sync import ThreadSensitiveContext, async_to_sync, sync_to_async
from asgiref.sync import (
ThreadSensitiveContext,
async_to_sync,
iscoroutinefunction,
sync_to_async,
)
from asgiref.timeout import timeout


Expand Down Expand Up @@ -645,8 +650,8 @@ def test_sync_to_async_detected_as_coroutinefunction():
def sync_func():
return

assert not asyncio.iscoroutinefunction(sync_to_async)
assert asyncio.iscoroutinefunction(sync_to_async(sync_func))
assert not iscoroutinefunction(sync_to_async)
assert iscoroutinefunction(sync_to_async(sync_func))


async def async_process(queue):
Expand Down