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:
Josh Bartlett
2023-09-23 22:59:44 +10:00
parent a5a9658896
commit 4d0a0a3570
5 changed files with 367 additions and 75 deletions

View File

@@ -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
async def test_dispatch_signal_with_enum_event(app):
counter = 0
@@ -97,6 +140,26 @@ async def test_dispatch_signal_with_enum_event(app):
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
async def test_dispatch_signal_triggers_multiple_handlers(app):
counter = 0
@@ -121,22 +184,45 @@ async def test_dispatch_signal_triggers_multiple_handlers(app):
@pytest.mark.asyncio
async def test_dispatch_signal_triggers_triggers_event(app):
counter = 0
async def test_dispatch_signal_triggers_multiple_events(app):
@app.signal("foo.bar.baz")
def sync_signal(*args):
nonlocal app
nonlocal counter
group, *_ = app.signal_router.get("foo.bar.baz")
for signal in group:
counter += signal.ctx.event.is_set()
def sync_signal(*_):
pass
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
@@ -155,6 +241,40 @@ async def test_dispatch_signal_triggers_dynamic_route(app):
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
async def test_dispatch_signal_triggers_with_requirements(app):
counter = 0
@@ -172,6 +292,26 @@ async def test_dispatch_signal_triggers_with_requirements(app):
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
async def test_dispatch_signal_triggers_with_requirements_exclusive(app):
counter = 0
@@ -189,6 +329,28 @@ async def test_dispatch_signal_triggers_with_requirements_exclusive(app):
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
async def test_dispatch_signal_triggers_with_context(app):
counter = 0
@@ -204,6 +366,22 @@ async def test_dispatch_signal_triggers_with_context(app):
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
async def test_dispatch_signal_triggers_with_context_fail(app):
counter = 0
@@ -219,6 +397,22 @@ async def test_dispatch_signal_triggers_with_context_fail(app):
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
async def test_dispatch_signal_triggers_on_bp(app):
bp = Blueprint("bp")
@@ -267,61 +461,67 @@ async def test_dispatch_signal_triggers_on_bp_alone(app):
@pytest.mark.asyncio
async def test_dispatch_signal_triggers_event(app):
app_counter = 0
async def test_dispatch_signal_triggers_event_on_bp(app):
bp = Blueprint("bp")
@app.signal("foo.bar.baz")
def app_signal():
...
async def do_wait():
nonlocal app_counter
await app.event("foo.bar.baz")
app_counter += 1
@bp.signal("foo.bar.baz")
def bp_signal():
...
app.blueprint(bp)
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")
waiter = app.event("foo.bar.baz")
assert isawaitable(waiter)
fut = asyncio.ensure_future(do_wait())
await app.dispatch("foo.bar.baz")
await fut
# Allow a few event loop iterations for tasks to finish
for _ in range(5):
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
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_counter = 0
@bp.signal("foo.bar.baz")
def bp_signal():
...
async def do_wait():
nonlocal bp_counter
await bp.event("foo.bar.baz")
bp_counter += 1
app.blueprint(bp)
app.signal_router.finalize()
signal_group, *_ = app.signal_router.get(
"foo.bar.baz", condition={"blueprint": "bp"}
)
await bp.dispatch("foo.bar.baz")
waiter = bp.event("foo.bar.baz")
assert isawaitable(waiter)
fut = do_wait()
for signal in signal_group:
signal.ctx.event.set()
await asyncio.gather(fut)
assert bp_counter == 1
event_task = asyncio.create_task(bp.event("foo.bar.baz"))
await asyncio.sleep(0)
await app.dispatch("foo.bar.baz", context={"amount": 9})
for _ in range(5):
await asyncio.sleep(0)
assert event_task.done()
assert event_task.result()['amount'] == 9
def test_bad_finalize(app):