From a6637778c5a860c5f5bba00fe3d7df580af44cb1 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 23 Nov 2022 16:36:23 +0100 Subject: [PATCH 1/3] Initial sketch of iscoroutinefunction shim for PY312 Refs: https://github.com/python/cpython/pull/99247 This would be needed for Django 4.2, which would support PY312 on release of that. --- asgiref/sync.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/asgiref/sync.py b/asgiref/sync.py index 6a9bd1d6..e0c10704 100644 --- a/asgiref/sync.py +++ b/asgiref/sync.py @@ -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 + 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: From ae3cfbad28856773cac52f18048a6f003af03ec1 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 13 Dec 2022 11:32:17 +0100 Subject: [PATCH 2/3] Make use of shim internally. --- asgiref/compatibility.py | 7 ++++--- asgiref/sync.py | 2 +- tests/test_sync.py | 11 ++++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/asgiref/compatibility.py b/asgiref/compatibility.py index eccaee0d..3a2a63e6 100644 --- a/asgiref/compatibility.py +++ b/asgiref/compatibility.py @@ -1,6 +1,7 @@ -import asyncio import inspect +from .sync import iscoroutinefunction + def is_double_callable(application): """ @@ -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): diff --git a/asgiref/sync.py b/asgiref/sync.py index e0c10704..9b5a959f 100644 --- a/asgiref/sync.py +++ b/asgiref/sync.py @@ -378,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 diff --git a/tests/test_sync.py b/tests/test_sync.py index 022a1743..c46e9be8 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -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 @@ -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): From 1689063c9ab45b98b57b73708ae965245ffaaa0b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 13 Dec 2022 11:49:50 +0100 Subject: [PATCH 3/3] Fix mypy error. --- asgiref/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asgiref/sync.py b/asgiref/sync.py index 9b5a959f..6f9c7cc5 100644 --- a/asgiref/sync.py +++ b/asgiref/sync.py @@ -37,7 +37,7 @@ def markcoroutinefunction(func: Any) -> Any: if hasattr(inspect, "markcoroutinefunction"): return inspect.markcoroutinefunction(func) else: - func._is_coroutine = asyncio.coroutines._is_coroutine + func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore return func