-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #562 from KeepSafe/signals-v2
Signals v2
- Loading branch information
Showing
11 changed files
with
288 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import asyncio | ||
from itertools import count | ||
|
||
|
||
class BaseSignal(list): | ||
|
||
@asyncio.coroutine | ||
def _send(self, *args, **kwargs): | ||
for receiver in self: | ||
res = receiver(*args, **kwargs) | ||
if asyncio.iscoroutine(res) or isinstance(res, asyncio.Future): | ||
yield from res | ||
|
||
def copy(self): | ||
raise NotImplementedError("copy() is forbidden") | ||
|
||
def sort(self): | ||
raise NotImplementedError("sort() is forbidden") | ||
|
||
|
||
class Signal(BaseSignal): | ||
"""Coroutine-based signal implementation. | ||
To connect a callback to a signal, use any list method. | ||
Signals are fired using the :meth:`send` coroutine, which takes named | ||
arguments. | ||
""" | ||
|
||
def __init__(self, app): | ||
super().__init__() | ||
self._app = app | ||
klass = self.__class__ | ||
self._name = klass.__module__ + ':' + klass.__qualname__ | ||
self._pre = app.on_pre_signal | ||
self._post = app.on_post_signal | ||
|
||
@asyncio.coroutine | ||
def send(self, *args, **kwargs): | ||
""" | ||
Sends data to all registered receivers. | ||
""" | ||
ordinal = None | ||
debug = self._app._debug | ||
if debug: | ||
ordinal = self._pre.ordinal() | ||
yield from self._pre.send(ordinal, self._name, *args, **kwargs) | ||
yield from self._send(*args, **kwargs) | ||
if debug: | ||
yield from self._post.send(ordinal, self._name, *args, **kwargs) | ||
|
||
|
||
class DebugSignal(BaseSignal): | ||
|
||
@asyncio.coroutine | ||
def send(self, ordinal, name, *args, **kwargs): | ||
yield from self._send(ordinal, name, *args, **kwargs) | ||
|
||
|
||
class PreSignal(DebugSignal): | ||
|
||
def __init__(self): | ||
super().__init__() | ||
self._counter = count(1) | ||
|
||
def ordinal(self): | ||
return next(self._counter) | ||
|
||
|
||
class PostSignal(DebugSignal): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import asyncio | ||
from unittest import mock | ||
from aiohttp.multidict import CIMultiDict | ||
from aiohttp.signals import Signal | ||
from aiohttp.web import Application | ||
from aiohttp.web import Request, Response | ||
from aiohttp.protocol import HttpVersion11 | ||
from aiohttp.protocol import RawRequestMessage | ||
|
||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def app(loop): | ||
return Application(loop=loop) | ||
|
||
|
||
@pytest.fixture | ||
def debug_app(loop): | ||
return Application(loop=loop, debug=True) | ||
|
||
|
||
def make_request(app, method, path, headers=CIMultiDict()): | ||
message = RawRequestMessage(method, path, HttpVersion11, headers, | ||
False, False) | ||
return request_from_message(message, app) | ||
|
||
|
||
def request_from_message(message, app): | ||
payload = mock.Mock() | ||
transport = mock.Mock() | ||
reader = mock.Mock() | ||
writer = mock.Mock() | ||
req = Request(app, message, payload, | ||
transport, reader, writer) | ||
return req | ||
|
||
|
||
def test_add_response_prepare_signal_handler(loop, app): | ||
callback = asyncio.coroutine(lambda request, response: None) | ||
app.on_response_prepare.append(callback) | ||
|
||
|
||
def test_add_signal_handler_not_a_callable(loop, app): | ||
callback = True | ||
app.on_response_prepare.append(callback) | ||
with pytest.raises(TypeError): | ||
app.on_response_prepare(None, None) | ||
|
||
|
||
def test_function_signal_dispatch(loop, app): | ||
signal = Signal(app) | ||
kwargs = {'foo': 1, 'bar': 2} | ||
|
||
callback_mock = mock.Mock() | ||
|
||
@asyncio.coroutine | ||
def callback(**kwargs): | ||
callback_mock(**kwargs) | ||
|
||
signal.append(callback) | ||
|
||
loop.run_until_complete(signal.send(**kwargs)) | ||
callback_mock.assert_called_once_with(**kwargs) | ||
|
||
|
||
def test_function_signal_dispatch2(loop, app): | ||
signal = Signal(app) | ||
args = {'a', 'b'} | ||
kwargs = {'foo': 1, 'bar': 2} | ||
|
||
callback_mock = mock.Mock() | ||
|
||
@asyncio.coroutine | ||
def callback(*args, **kwargs): | ||
callback_mock(*args, **kwargs) | ||
|
||
signal.append(callback) | ||
|
||
loop.run_until_complete(signal.send(*args, **kwargs)) | ||
callback_mock.assert_called_once_with(*args, **kwargs) | ||
|
||
|
||
def test_response_prepare(loop, app): | ||
callback = mock.Mock() | ||
|
||
@asyncio.coroutine | ||
def cb(*args, **kwargs): | ||
callback(*args, **kwargs) | ||
|
||
app.on_response_prepare.append(cb) | ||
|
||
request = make_request(app, 'GET', '/') | ||
response = Response(body=b'') | ||
loop.run_until_complete(response.prepare(request)) | ||
|
||
callback.assert_called_once_with(request=request, | ||
response=response) | ||
|
||
|
||
def test_non_coroutine(loop, app): | ||
signal = Signal(app) | ||
kwargs = {'foo': 1, 'bar': 2} | ||
|
||
callback = mock.Mock() | ||
|
||
signal.append(callback) | ||
|
||
loop.run_until_complete(signal.send(**kwargs)) | ||
callback.assert_called_once_with(**kwargs) | ||
|
||
|
||
def test_copy_forbidden(app): | ||
signal = Signal(app) | ||
with pytest.raises(NotImplementedError): | ||
signal.copy() | ||
|
||
|
||
def test_sort_forbidden(app): | ||
l1 = lambda: None | ||
l2 = lambda: None | ||
l3 = lambda: None | ||
signal = Signal(app) | ||
signal.extend([l1, l2, l3]) | ||
with pytest.raises(NotImplementedError): | ||
signal.sort() | ||
assert signal == [l1, l2, l3] | ||
|
||
|
||
def test_debug_signal(loop, debug_app): | ||
assert debug_app.debug, "Should be True" | ||
signal = Signal(debug_app) | ||
|
||
callback = mock.Mock() | ||
pre = mock.Mock() | ||
post = mock.Mock() | ||
|
||
signal.append(callback) | ||
debug_app.on_pre_signal.append(pre) | ||
debug_app.on_post_signal.append(post) | ||
|
||
loop.run_until_complete(signal.send(1, a=2)) | ||
callback.assert_called_once_with(1, a=2) | ||
pre.assert_called_once_with(1, 'aiohttp.signals:Signal', 1, a=2) | ||
post.assert_called_once_with(1, 'aiohttp.signals:Signal', 1, a=2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.