Signals¶
Signals run asynchronous callbacks when connections change state or a handler starts, succeeds or fails. Connect callbacks before serving requests. Callbacks must be coroutine functions; they run sequentially, and a callback exception is logged without stopping the remaining callbacks.
Available signals¶
Connection signals belong to the WebSocket handler class. Their socket argument is the underlying aiohttp WebSocketResponse, and request is the incoming aiohttp request.
Signal |
Keyword arguments |
When it runs |
|---|---|---|
|
|
Before authorization and the WebSocket upgrade |
|
|
After |
|
|
After |
|
|
If preparing the WebSocket response raises an exception |
|
|
When handling an established connection finishes, before cleanup |
Call signals are available on both server and Python client connections:
Signal |
Keyword arguments |
When it runs |
|---|---|---|
|
|
Before resolving and executing a received call |
|
|
After the handler returns, before sending the result |
|
|
If executing the handler raises an exception |
ON_CALL_FAIL describes exceptions from the handler body. A failure to resolve a route still returns an RPC error, but does not reach this signal.
Measure handler duration¶
This example uses a ContextVar so concurrent calls keep separate start times. Signals are class attributes: subclasses inherit the same signal objects unless they replace them. Define fresh signals when an application needs independent subscriptions.
import logging
from contextvars import ContextVar
from time import monotonic
from wsrpc_aiohttp import WebSocketAsync
from wsrpc_aiohttp.signal import Signal
log = logging.getLogger(__name__)
started_at: ContextVar[float] = ContextVar("started_at")
class Socket(WebSocketAsync):
ON_CALL_START: Signal[...] = Signal()
ON_CALL_SUCCESS: Signal[...] = Signal()
ON_CALL_FAIL: Signal[...] = Signal()
async def on_start(method: str, **kwargs) -> None:
started_at.set(monotonic())
async def on_finish(method: str, **kwargs) -> None:
log.info("%s took %.3fs", method, monotonic() - started_at.get())
Socket.ON_CALL_START.connect(on_start)
Socket.ON_CALL_SUCCESS.connect(on_finish)
Socket.ON_CALL_FAIL.connect(on_finish)
The args and kwargs values describe the handler invocation. A successful function route receives the socket as its first positional argument; a bound route method already has its instance.
Typed standalone signals¶
Signal is generic over its callback parameters. The following example is executed as part of the documentation tests:
import asyncio
from wsrpc_aiohttp.signal import Signal
async def main() -> None:
messages: list[str] = []
signal: Signal[[str]] = Signal()
async def receive(message: str) -> None:
messages.append(message)
signal.connect(receive)
await signal.call("connected")
assert messages == ["connected"]
signal.freeze()
assert signal.is_frozen
copy = signal.copy()
assert not copy.is_frozen
await copy.call("copied")
assert messages == ["connected", "copied"]
asyncio.run(main())
freeze() prevents further subscriptions. copy() preserves existing receivers and returns an unfrozen signal. Socket.freeze() freezes all of that handler’s connection and call signals.