Update app.event() to return context dict (#2088), and only fire for the specific requested event (#2826).
Also added support for passing conditions= and exclusive= arguments to app.event().
This commit is contained in:
parent
a5a9658896
commit
4d0a0a3570
23
sanic/app.py
23
sanic/app.py
|
@ -17,6 +17,7 @@ from asyncio import (
|
||||||
from asyncio.futures import Future
|
from asyncio.futures import Future
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from contextlib import contextmanager, suppress
|
from contextlib import contextmanager, suppress
|
||||||
|
from enum import Enum
|
||||||
from functools import partial, wraps
|
from functools import partial, wraps
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from os import environ
|
from os import environ
|
||||||
|
@ -663,7 +664,12 @@ class Sanic(
|
||||||
)
|
)
|
||||||
|
|
||||||
async def event(
|
async def event(
|
||||||
self, event: str, timeout: Optional[Union[int, float]] = None
|
self,
|
||||||
|
event: Union[str, Enum],
|
||||||
|
timeout: Optional[Union[int, float]] = None,
|
||||||
|
*,
|
||||||
|
condition: Optional[Dict[str, Any]] = None,
|
||||||
|
exclusive: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Wait for a specific event to be triggered.
|
"""Wait for a specific event to be triggered.
|
||||||
|
|
||||||
|
@ -686,13 +692,18 @@ class Sanic(
|
||||||
timeout (Optional[Union[int, float]]): An optional timeout value
|
timeout (Optional[Union[int, float]]): An optional timeout value
|
||||||
in seconds. If provided, the wait will be terminated if the
|
in seconds. If provided, the wait will be terminated if the
|
||||||
timeout is reached. Defaults to `None`, meaning no timeout.
|
timeout is reached. Defaults to `None`, meaning no timeout.
|
||||||
|
condition: If provided, method will only return when the signal
|
||||||
|
is dispatched with the given condition.
|
||||||
|
exclusive: When true (default), the signal can only be dispatched
|
||||||
|
when the condition has been met. When ``False``, the signal can
|
||||||
|
be dispatched either with or without it.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
NotFound: If the event is not found and auto-registration of
|
NotFound: If the event is not found and auto-registration of
|
||||||
events is not enabled.
|
events is not enabled.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
The context dict of the dispatched signal.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```python
|
```python
|
||||||
|
@ -708,16 +719,16 @@ class Sanic(
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
signal = self.signal_router.name_index.get(event)
|
waiter = self.signal_router.get_waiter(event, condition, exclusive)
|
||||||
if not signal:
|
if not waiter:
|
||||||
if self.config.EVENT_AUTOREGISTER:
|
if self.config.EVENT_AUTOREGISTER:
|
||||||
self.signal_router.reset()
|
self.signal_router.reset()
|
||||||
self.add_signal(None, event)
|
self.add_signal(None, event)
|
||||||
signal = self.signal_router.name_index[event]
|
waiter = self.signal_router.get_waiter(event, condition, exclusive)
|
||||||
self.signal_router.finalize()
|
self.signal_router.finalize()
|
||||||
else:
|
else:
|
||||||
raise NotFound("Could not find signal %s" % event)
|
raise NotFound("Could not find signal %s" % event)
|
||||||
return await wait_for(signal.ctx.event.wait(), timeout=timeout)
|
return await wait_for(waiter.wait(), timeout=timeout)
|
||||||
|
|
||||||
def report_exception(
|
def report_exception(
|
||||||
self, handler: Callable[[Sanic, Exception], Coroutine[Any, Any, None]]
|
self, handler: Callable[[Sanic, Exception], Coroutine[Any, Any, None]]
|
||||||
|
|
|
@ -512,33 +512,54 @@ class Blueprint(BaseSanic):
|
||||||
condition = kwargs.pop("condition", {})
|
condition = kwargs.pop("condition", {})
|
||||||
condition.update({"__blueprint__": self.name})
|
condition.update({"__blueprint__": self.name})
|
||||||
kwargs["condition"] = condition
|
kwargs["condition"] = condition
|
||||||
await asyncio.gather(
|
return await asyncio.gather(
|
||||||
*[app.dispatch(*args, **kwargs) for app in self.apps]
|
*[app.dispatch(*args, **kwargs) for app in self.apps]
|
||||||
)
|
)
|
||||||
|
|
||||||
def event(self, event: str, timeout: Optional[Union[int, float]] = None):
|
def event(
|
||||||
|
self,
|
||||||
|
event: str,
|
||||||
|
timeout: Optional[Union[int, float]] = None,
|
||||||
|
*,
|
||||||
|
condition: Optional[Dict[str, Any]] = None,
|
||||||
|
):
|
||||||
"""Wait for a signal event to be dispatched.
|
"""Wait for a signal event to be dispatched.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (str): Name of the signal event.
|
event (str): Name of the signal event.
|
||||||
timeout (Optional[Union[int, float]]): Timeout for the event to be
|
timeout (Optional[Union[int, float]]): Timeout for the event to be
|
||||||
dispatched.
|
dispatched.
|
||||||
|
condition: If provided, method will only return when the signal
|
||||||
|
is dispatched with the given condition.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Awaitable: Awaitable for the event to be dispatched.
|
Awaitable: Awaitable for the event to be dispatched.
|
||||||
"""
|
"""
|
||||||
events = set()
|
if condition is None:
|
||||||
for app in self.apps:
|
condition = {}
|
||||||
signal = app.signal_router.name_index.get(event)
|
condition.update({"__blueprint__": self.name})
|
||||||
if not signal:
|
|
||||||
raise NotFound("Could not find signal %s" % event)
|
|
||||||
events.add(signal.ctx.event)
|
|
||||||
|
|
||||||
return asyncio.wait(
|
waiters = []
|
||||||
[asyncio.create_task(event.wait()) for event in events],
|
for app in self.apps:
|
||||||
|
waiter = app.signal_router.get_waiter(event, condition, exclusive=False)
|
||||||
|
if not waiter:
|
||||||
|
raise NotFound("Could not find signal %s" % event)
|
||||||
|
waiters.append(waiter)
|
||||||
|
|
||||||
|
return self._event(waiters, timeout)
|
||||||
|
|
||||||
|
async def _event(self, waiters, timeout):
|
||||||
|
done, pending = await asyncio.wait(
|
||||||
|
[asyncio.create_task(waiter.wait()) for waiter in waiters],
|
||||||
return_when=asyncio.FIRST_COMPLETED,
|
return_when=asyncio.FIRST_COMPLETED,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
|
for task in pending:
|
||||||
|
task.cancel()
|
||||||
|
if not done:
|
||||||
|
raise TimeoutError()
|
||||||
|
finished_task, = done
|
||||||
|
return finished_task.result()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_value(*values):
|
def _extract_value(*values):
|
||||||
|
|
|
@ -88,7 +88,7 @@ class SignalMixin(metaclass=SanicMeta):
|
||||||
"""
|
"""
|
||||||
if not handler:
|
if not handler:
|
||||||
|
|
||||||
async def noop():
|
async def noop(**context):
|
||||||
...
|
...
|
||||||
|
|
||||||
handler = noop
|
handler = noop
|
||||||
|
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from inspect import isawaitable
|
from inspect import isawaitable
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
||||||
|
@ -76,6 +78,38 @@ class Signal(Route):
|
||||||
"""A `Route` that is used to dispatch signals to handlers"""
|
"""A `Route` that is used to dispatch signals to handlers"""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SignalWaiter:
|
||||||
|
"""A record representing a future waiting for a signal"""
|
||||||
|
|
||||||
|
signal: Signal
|
||||||
|
event_definition: str
|
||||||
|
trigger: str = ""
|
||||||
|
requirements: Optional[Dict[str, str]] = None
|
||||||
|
exclusive: bool = True
|
||||||
|
|
||||||
|
future: Optional[asyncio.Future] = None
|
||||||
|
|
||||||
|
async def wait(self):
|
||||||
|
"""Block until the signal is next dispatched.
|
||||||
|
|
||||||
|
Return the context of the signal dispatch, if any.
|
||||||
|
"""
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
self.future = loop.create_future()
|
||||||
|
self.signal.ctx.waiters.append(self)
|
||||||
|
try:
|
||||||
|
return await self.future
|
||||||
|
finally:
|
||||||
|
self.signal.ctx.waiters.remove(self)
|
||||||
|
|
||||||
|
def matches(self, event, condition):
|
||||||
|
return ((condition is None and not self.exclusive)
|
||||||
|
or (condition is None and not self.requirements)
|
||||||
|
or condition == self.requirements
|
||||||
|
) and (self.trigger or event == self.event_definition)
|
||||||
|
|
||||||
|
|
||||||
class SignalGroup(RouteGroup):
|
class SignalGroup(RouteGroup):
|
||||||
"""A `RouteGroup` that is used to dispatch signals to handlers"""
|
"""A `RouteGroup` that is used to dispatch signals to handlers"""
|
||||||
|
|
||||||
|
@ -160,18 +194,20 @@ class SignalRouter(BaseRouter):
|
||||||
error_logger.warning(str(e))
|
error_logger.warning(str(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
events = [signal.ctx.event for signal in group]
|
|
||||||
for signal_event in events:
|
|
||||||
signal_event.set()
|
|
||||||
if context:
|
if context:
|
||||||
params.update(context)
|
params.update(context)
|
||||||
|
params.pop("__trigger__", None)
|
||||||
|
|
||||||
signals = group.routes
|
signals = group.routes
|
||||||
if not reverse:
|
if not reverse:
|
||||||
signals = signals[::-1]
|
signals = signals[::-1]
|
||||||
try:
|
try:
|
||||||
for signal in signals:
|
for signal in signals:
|
||||||
params.pop("__trigger__", None)
|
for waiter in signal.ctx.waiters:
|
||||||
|
if waiter.matches(event, condition):
|
||||||
|
waiter.future.set_result(dict(params))
|
||||||
|
|
||||||
|
for signal in signals:
|
||||||
requirements = signal.extra.requirements
|
requirements = signal.extra.requirements
|
||||||
if (
|
if (
|
||||||
(condition is None and signal.ctx.exclusive is False)
|
(condition is None and signal.ctx.exclusive is False)
|
||||||
|
@ -197,9 +233,6 @@ class SignalRouter(BaseRouter):
|
||||||
)
|
)
|
||||||
setattr(e, "__dispatched__", True)
|
setattr(e, "__dispatched__", True)
|
||||||
raise e
|
raise e
|
||||||
finally:
|
|
||||||
for signal_event in events:
|
|
||||||
signal_event.clear()
|
|
||||||
|
|
||||||
async def dispatch(
|
async def dispatch(
|
||||||
self,
|
self,
|
||||||
|
@ -244,14 +277,29 @@ class SignalRouter(BaseRouter):
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
return task
|
return task
|
||||||
|
|
||||||
def add( # type: ignore
|
def get_waiter(
|
||||||
self,
|
self,
|
||||||
handler: SignalHandler,
|
event: Union[str, Enum],
|
||||||
event: str,
|
condition: Optional[Dict[str, Any]],
|
||||||
condition: Optional[Dict[str, Any]] = None,
|
exclusive: bool,
|
||||||
exclusive: bool = True,
|
):
|
||||||
) -> Signal:
|
event_definition = str(event.value) if isinstance(event, Enum) else event
|
||||||
event_definition = event
|
name, trigger, _ = self._get_event_parts(event_definition)
|
||||||
|
signal = cast(Signal, self.name_index.get(name))
|
||||||
|
if not signal:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if event_definition.endswith(".*") and not trigger:
|
||||||
|
trigger = "*"
|
||||||
|
return SignalWaiter(
|
||||||
|
signal=signal,
|
||||||
|
event_definition=event_definition,
|
||||||
|
trigger=trigger,
|
||||||
|
requirements=condition,
|
||||||
|
exclusive=bool(exclusive),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_event_parts(self, event):
|
||||||
parts = self._build_event_parts(event)
|
parts = self._build_event_parts(event)
|
||||||
if parts[2].startswith("<"):
|
if parts[2].startswith("<"):
|
||||||
name = ".".join([*parts[:-1], "*"])
|
name = ".".join([*parts[:-1], "*"])
|
||||||
|
@ -263,6 +311,18 @@ class SignalRouter(BaseRouter):
|
||||||
if not trigger:
|
if not trigger:
|
||||||
event = ".".join([*parts[:2], "<__trigger__>"])
|
event = ".".join([*parts[:2], "<__trigger__>"])
|
||||||
|
|
||||||
|
return name, trigger, event
|
||||||
|
|
||||||
|
def add( # type: ignore
|
||||||
|
self,
|
||||||
|
handler: SignalHandler,
|
||||||
|
event: str,
|
||||||
|
condition: Optional[Dict[str, Any]] = None,
|
||||||
|
exclusive: bool = True,
|
||||||
|
) -> Signal:
|
||||||
|
event_definition = event
|
||||||
|
name, trigger, event = self._get_event_parts(event)
|
||||||
|
|
||||||
signal = super().add(
|
signal = super().add(
|
||||||
event,
|
event,
|
||||||
handler,
|
handler,
|
||||||
|
@ -298,7 +358,7 @@ class SignalRouter(BaseRouter):
|
||||||
raise RuntimeError("Cannot finalize signals outside of event loop")
|
raise RuntimeError("Cannot finalize signals outside of event loop")
|
||||||
|
|
||||||
for signal in self.routes:
|
for signal in self.routes:
|
||||||
signal.ctx.event = asyncio.Event()
|
signal.ctx.waiters = deque()
|
||||||
|
|
||||||
return super().finalize(do_compile=do_compile, do_optimize=do_optimize)
|
return super().finalize(do_compile=do_compile, do_optimize=do_optimize)
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,49 @@ def test_invalid_signal(app, signal):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_triggers_event(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
def sync_signal(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_triggers_correct_event(app):
|
||||||
|
# Check for https://github.com/sanic-org/sanic/issues/2826
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
def sync_signal(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.signal("foo.bar.spam")
|
||||||
|
def sync_signal(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
baz_task = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
spam_task = asyncio.create_task(app.event("foo.bar.spam"))
|
||||||
|
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert baz_task.done()
|
||||||
|
assert not spam_task.done()
|
||||||
|
baz_task.result()
|
||||||
|
spam_task.cancel()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_with_enum_event(app):
|
async def test_dispatch_signal_with_enum_event(app):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
@ -97,6 +140,26 @@ async def test_dispatch_signal_with_enum_event(app):
|
||||||
assert counter == 1
|
assert counter == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_with_enum_event_to_event(app):
|
||||||
|
|
||||||
|
class FooEnum(Enum):
|
||||||
|
FOO_BAR_BAZ = "foo.bar.baz"
|
||||||
|
|
||||||
|
@app.signal(FooEnum.FOO_BAR_BAZ)
|
||||||
|
def sync_signal(*args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event(FooEnum.FOO_BAR_BAZ))
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_multiple_handlers(app):
|
async def test_dispatch_signal_triggers_multiple_handlers(app):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
@ -121,22 +184,45 @@ async def test_dispatch_signal_triggers_multiple_handlers(app):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_triggers_event(app):
|
async def test_dispatch_signal_triggers_multiple_events(app):
|
||||||
counter = 0
|
|
||||||
|
|
||||||
@app.signal("foo.bar.baz")
|
@app.signal("foo.bar.baz")
|
||||||
def sync_signal(*args):
|
def sync_signal(*_):
|
||||||
nonlocal app
|
pass
|
||||||
nonlocal counter
|
|
||||||
group, *_ = app.signal_router.get("foo.bar.baz")
|
|
||||||
for signal in group:
|
|
||||||
counter += signal.ctx.event.is_set()
|
|
||||||
|
|
||||||
app.signal_router.finalize()
|
app.signal_router.finalize()
|
||||||
|
|
||||||
await app.dispatch("foo.bar.baz")
|
event_task1 = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
event_task2 = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
|
||||||
assert counter == 1
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert event_task1.done()
|
||||||
|
assert event_task2.done()
|
||||||
|
event_task1.result() # Will raise if there was an exception
|
||||||
|
event_task2.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_with_multiple_handlers_triggers_event_once(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
def sync_signal(*_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
async def async_signal(*_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -155,6 +241,40 @@ async def test_dispatch_signal_triggers_dynamic_route(app):
|
||||||
assert counter == 9
|
assert counter == 9
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_triggers_parameterized_dynamic_route_event(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.<baz:int>")
|
||||||
|
def sync_signal(baz):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.<baz:int>"))
|
||||||
|
await app.dispatch("foo.bar.9")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_triggers_starred_dynamic_route_event(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.<baz:int>")
|
||||||
|
def sync_signal(baz):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.*"))
|
||||||
|
await app.dispatch("foo.bar.9")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_with_requirements(app):
|
async def test_dispatch_signal_triggers_with_requirements(app):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
@ -172,6 +292,26 @@ async def test_dispatch_signal_triggers_with_requirements(app):
|
||||||
assert counter == 1
|
assert counter == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_to_event_with_requirements(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
def sync_signal(*_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.baz", condition={"one": "two"}))
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert not event_task.done()
|
||||||
|
|
||||||
|
await app.dispatch("foo.bar.baz", condition={"one": "two"})
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_with_requirements_exclusive(app):
|
async def test_dispatch_signal_triggers_with_requirements_exclusive(app):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
@ -189,6 +329,28 @@ async def test_dispatch_signal_triggers_with_requirements_exclusive(app):
|
||||||
assert counter == 2
|
assert counter == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_to_event_with_requirements_exclusive(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
def sync_signal(*_):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.baz", condition={"one": "two"}, exclusive=False))
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.baz", condition={"one": "two"}, exclusive=False))
|
||||||
|
await app.dispatch("foo.bar.baz", condition={"one": "two"})
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert event_task.done()
|
||||||
|
event_task.result() # Will raise if there was an exception
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_with_context(app):
|
async def test_dispatch_signal_triggers_with_context(app):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
@ -204,6 +366,22 @@ async def test_dispatch_signal_triggers_with_context(app):
|
||||||
assert counter == 9
|
assert counter == 9
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_to_event_with_context(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.baz")
|
||||||
|
def sync_signal(**context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
await app.dispatch("foo.bar.baz", context={"amount": 9})
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert event_task.done()
|
||||||
|
assert event_task.result()['amount'] == 9
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_with_context_fail(app):
|
async def test_dispatch_signal_triggers_with_context_fail(app):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
@ -219,6 +397,22 @@ async def test_dispatch_signal_triggers_with_context_fail(app):
|
||||||
await app.dispatch("foo.bar.baz", {"amount": 9})
|
await app.dispatch("foo.bar.baz", {"amount": 9})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_dispatch_signal_to_dynamic_route_event(app):
|
||||||
|
|
||||||
|
@app.signal("foo.bar.<something>")
|
||||||
|
def sync_signal(**context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
event_task = asyncio.create_task(app.event("foo.bar.<something>"))
|
||||||
|
await app.dispatch("foo.bar.baz")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert event_task.done()
|
||||||
|
assert event_task.result()['something'] == "baz"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_on_bp(app):
|
async def test_dispatch_signal_triggers_on_bp(app):
|
||||||
bp = Blueprint("bp")
|
bp = Blueprint("bp")
|
||||||
|
@ -267,61 +461,67 @@ async def test_dispatch_signal_triggers_on_bp_alone(app):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_event(app):
|
async def test_dispatch_signal_triggers_event_on_bp(app):
|
||||||
app_counter = 0
|
bp = Blueprint("bp")
|
||||||
|
|
||||||
@app.signal("foo.bar.baz")
|
@app.signal("foo.bar.baz")
|
||||||
def app_signal():
|
def app_signal():
|
||||||
...
|
...
|
||||||
|
|
||||||
async def do_wait():
|
@bp.signal("foo.bar.baz")
|
||||||
nonlocal app_counter
|
def bp_signal():
|
||||||
await app.event("foo.bar.baz")
|
...
|
||||||
app_counter += 1
|
|
||||||
|
|
||||||
|
app.blueprint(bp)
|
||||||
app.signal_router.finalize()
|
app.signal_router.finalize()
|
||||||
|
|
||||||
|
app_task = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
bp_task = asyncio.create_task(bp.event("foo.bar.baz"))
|
||||||
|
await asyncio.sleep(0)
|
||||||
await app.dispatch("foo.bar.baz")
|
await app.dispatch("foo.bar.baz")
|
||||||
waiter = app.event("foo.bar.baz")
|
|
||||||
assert isawaitable(waiter)
|
|
||||||
|
|
||||||
fut = asyncio.ensure_future(do_wait())
|
# Allow a few event loop iterations for tasks to finish
|
||||||
await app.dispatch("foo.bar.baz")
|
for _ in range(5):
|
||||||
await fut
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
assert app_counter == 1
|
assert app_task.done()
|
||||||
|
assert bp_task.done()
|
||||||
|
app_task.result()
|
||||||
|
bp_task.result()
|
||||||
|
|
||||||
|
app_task = asyncio.create_task(app.event("foo.bar.baz"))
|
||||||
|
bp_task = asyncio.create_task(bp.event("foo.bar.baz"))
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
await bp.dispatch("foo.bar.baz")
|
||||||
|
|
||||||
|
# Allow a few event loop iterations for tasks to finish
|
||||||
|
for _ in range(5):
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
assert bp_task.done()
|
||||||
|
assert not app_task.done()
|
||||||
|
bp_task.result()
|
||||||
|
app_task.cancel()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_dispatch_signal_triggers_event_on_bp(app):
|
async def test_dispatch_signal_triggers_event_on_bp_with_context(app):
|
||||||
bp = Blueprint("bp")
|
bp = Blueprint("bp")
|
||||||
bp_counter = 0
|
|
||||||
|
|
||||||
@bp.signal("foo.bar.baz")
|
@bp.signal("foo.bar.baz")
|
||||||
def bp_signal():
|
def bp_signal():
|
||||||
...
|
...
|
||||||
|
|
||||||
async def do_wait():
|
|
||||||
nonlocal bp_counter
|
|
||||||
await bp.event("foo.bar.baz")
|
|
||||||
bp_counter += 1
|
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
app.signal_router.finalize()
|
app.signal_router.finalize()
|
||||||
signal_group, *_ = app.signal_router.get(
|
|
||||||
"foo.bar.baz", condition={"blueprint": "bp"}
|
|
||||||
)
|
|
||||||
|
|
||||||
await bp.dispatch("foo.bar.baz")
|
event_task = asyncio.create_task(bp.event("foo.bar.baz"))
|
||||||
waiter = bp.event("foo.bar.baz")
|
await asyncio.sleep(0)
|
||||||
assert isawaitable(waiter)
|
await app.dispatch("foo.bar.baz", context={"amount": 9})
|
||||||
|
for _ in range(5):
|
||||||
fut = do_wait()
|
await asyncio.sleep(0)
|
||||||
for signal in signal_group:
|
assert event_task.done()
|
||||||
signal.ctx.event.set()
|
assert event_task.result()['amount'] == 9
|
||||||
await asyncio.gather(fut)
|
|
||||||
|
|
||||||
assert bp_counter == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_bad_finalize(app):
|
def test_bad_finalize(app):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user