summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjason kirtland <jek@discorporate.us>2015-07-23 17:18:39 +0200
committerDavid Lord <davidism@gmail.com>2022-07-17 06:53:30 -0700
commitf12e048f2a94ad15267d7c32becbb2632a6a9298 (patch)
tree6c2a3caedcd1d3c2cec7838dd16f9d32576f2802
parent399778023f3dc771329cfa068c1f7a05159d68d9 (diff)
downloadblinker-f12e048f2a94ad15267d7c32becbb2632a6a9298.tar.gz
Adds Signal.send_async for asyncio
-rw-r--r--CHANGES2
-rw-r--r--blinker/__init__.py5
-rw-r--r--blinker/_async.py28
-rw-r--r--blinker/base.py11
-rw-r--r--tests/_test_async.py43
-rw-r--r--tests/test_signals.py5
6 files changed, 94 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
index 9777302..75f790e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -10,6 +10,8 @@ Not yet released.
- Verified Python 3.7 support (no changes needed).
- Verified Python 3.6 support (no changes needed).
- Verified Python 3.5 support (no changes needed).
+- Added Signal.send_async, dispatching to an arbitrary mix of connected
+ coroutines and receiver functions.
Version 1.4
-----------
diff --git a/blinker/__init__.py b/blinker/__init__.py
index 57e1be8..a3251bd 100644
--- a/blinker/__init__.py
+++ b/blinker/__init__.py
@@ -20,3 +20,8 @@ __all__ = [
__version__ = '1.5dev'
+
+try:
+ import blinker._async
+except (ImportError, SyntaxError):
+ pass
diff --git a/blinker/_async.py b/blinker/_async.py
new file mode 100644
index 0000000..5a88f13
--- /dev/null
+++ b/blinker/_async.py
@@ -0,0 +1,28 @@
+import asyncio
+
+from blinker.base import Signal
+
+
+try:
+ schedule = asyncio.ensure_future
+except AttributeError:
+ schedule = asyncio.async
+
+
+@asyncio.coroutine
+def _wrap_plain_value(value):
+ """Pass through a coroutine *value* or wrap a plain value."""
+ if asyncio.iscoroutine(value):
+ value = yield from value
+ return value
+
+
+def send_async(self, *sender, **kwargs):
+ return [(receiver, schedule(_wrap_plain_value(value)))
+ for receiver, value
+ in self.send(*sender, **kwargs)]
+
+
+send_async.__doc__ = Signal.send_async.__doc__
+Signal.send_async = send_async
+
diff --git a/blinker/base.py b/blinker/base.py
index b68d8db..a2f22aa 100644
--- a/blinker/base.py
+++ b/blinker/base.py
@@ -271,6 +271,17 @@ class Signal(object):
return [(receiver, receiver(sender, **kwargs))
for receiver in self.receivers_for(sender)]
+ def send_async(self, *sender, **kwargs):
+ """Send and collect results from connected functions and coroutines.
+
+ As `Signal.send`, but also schedules any coroutines connected to the
+ signal, and uniformly presents all receiver return values as futures,
+ even if one or more receivers are regular functions.
+
+ Available only if asyncio and `yield from` are present.
+ """
+ raise NotImplementedError("asyncio support unavailable")
+
def has_receivers_for(self, sender):
"""True if there is probably a receiver for *sender*.
diff --git a/tests/_test_async.py b/tests/_test_async.py
new file mode 100644
index 0000000..35f3ced
--- /dev/null
+++ b/tests/_test_async.py
@@ -0,0 +1,43 @@
+import asyncio
+
+import blinker
+
+
+def test_send_async():
+ calls = []
+
+ @asyncio.coroutine
+ def receiver_a(sender):
+ calls.append(receiver_a)
+ return 'value a'
+
+ @asyncio.coroutine
+ def receiver_b(sender):
+ calls.append(receiver_b)
+ return 'value b'
+
+ def receiver_c(sender):
+ calls.append(receiver_c)
+ return 'value c'
+
+ sig = blinker.Signal()
+ sig.connect(receiver_a)
+ sig.connect(receiver_b)
+ sig.connect(receiver_c)
+
+ @asyncio.coroutine
+ def collect():
+ return sig.send_async()
+
+ loop = asyncio.get_event_loop()
+ results = loop.run_until_complete(collect())
+
+ expected = {
+ receiver_a: 'value a',
+ receiver_b: 'value b',
+ receiver_c: 'value c',
+ }
+
+ assert set(calls) == set(expected.keys())
+ collected_results = {v.result() for r, v in results}
+ assert collected_results == set(expected.values())
diff --git a/tests/test_signals.py b/tests/test_signals.py
index e74db47..ccbc35b 100644
--- a/tests/test_signals.py
+++ b/tests/test_signals.py
@@ -10,6 +10,11 @@ import pytest
jython = sys.platform.startswith('java')
pypy = hasattr(sys, 'pypy_version_info')
+try:
+ from _test_async import test_send_async
+except (SyntaxError, ImportError):
+ pass
+
def collect_acyclic_refs():
# cpython releases these immediately without a collection