summaryrefslogtreecommitdiff
path: root/src/blinker/_utilities.py
diff options
context:
space:
mode:
authorpgjones <philip.graham.jones@googlemail.com>2022-07-23 11:56:12 +0100
committerPhil Jones <philip.graham.jones@googlemail.com>2023-01-24 20:19:57 +0000
commit5ed9c956aaf68a8e2defa9722109aa2c7bcf7ae1 (patch)
treef103622c530a5a4fa121319e4b35d040ebcf80dc /src/blinker/_utilities.py
parent0d4ca6e72c155e30aedd4315e8678ee9cada32b4 (diff)
downloadblinker-5ed9c956aaf68a8e2defa9722109aa2c7bcf7ae1.tar.gz
Add a send_async method to the Signal
This allows for signals to send to coroutine receivers by awaiting them. The _async_wrapper and _sync_wrapper allows for conversion of sync and async receivers as required if defined. If not defined a runtime error is raised. The wrappers are used to avoid any direct tie into asyncio, trio, greenbacks, asgiref, or other specific async implementation.
Diffstat (limited to 'src/blinker/_utilities.py')
-rw-r--r--src/blinker/_utilities.py37
1 files changed, 37 insertions, 0 deletions
diff --git a/src/blinker/_utilities.py b/src/blinker/_utilities.py
index 68e8422..22beb81 100644
--- a/src/blinker/_utilities.py
+++ b/src/blinker/_utilities.py
@@ -1,3 +1,8 @@
+import asyncio
+import inspect
+import sys
+from functools import partial
+from typing import Any
from weakref import ref
from blinker._saferef import BoundMethodWeakref
@@ -93,3 +98,35 @@ class lazy_property:
value = self._deferred(obj)
setattr(obj, self._deferred.__name__, value)
return value
+
+
+def is_coroutine_function(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)
+ else:
+ # Note that there is something special about the AsyncMock
+ # such that it isn't determined as a coroutine function
+ # without an explicit check.
+ try:
+ from unittest.mock import AsyncMock
+
+ if isinstance(func, AsyncMock):
+ return True
+ except ImportError:
+ # Not testing, no asynctest to import
+ pass
+
+ while inspect.ismethod(func):
+ func = func.__func__
+ while isinstance(func, partial):
+ func = func.func
+ if not inspect.isfunction(func):
+ return False
+ result = bool(func.__code__.co_flags & inspect.CO_COROUTINE)
+ return (
+ result
+ or getattr(func, "_is_coroutine", None) is asyncio.coroutines._is_coroutine
+ )