Skip to content

Events

EventDispatcher

EventDispatcher

Dispatches CDP events to registered async handlers.

Each CDPSession and CDPClient owns an EventDispatcher. Handlers are isolated: if one raises, others still run.

Parameters:

Name Type Description Default
strict_events bool

If True, handler exceptions are re-raised after logging, stopping dispatch to remaining handlers. Default False (fire-and-forget).

False
on_event_error EventErrorCallback | None

Optional callback invoked when a handler raises. Receives (event_name, params, exception). May be sync or async.

None
Source code in cdpwave/events/dispatcher.py
class EventDispatcher:
    """Dispatches CDP events to registered async handlers.

    Each CDPSession and CDPClient owns an EventDispatcher. Handlers are
    isolated: if one raises, others still run.

    Args:
        strict_events: If True, handler exceptions are re-raised after
            logging, stopping dispatch to remaining handlers. Default False
            (fire-and-forget).
        on_event_error: Optional callback invoked when a handler raises.
            Receives (event_name, params, exception). May be sync or async.
    """

    def __init__(
        self,
        strict_events: bool = False,
        on_event_error: EventErrorCallback | None = None,
    ) -> None:
        self._handlers: dict[str, list[EventHandler]] = {}
        self._strict = strict_events
        self._on_event_error = on_event_error

    def on(self, event_name: str, handler: EventHandler) -> Subscription:
        """Register an async handler for a CDP event.

        Args:
            event_name: CDP event name (e.g. ``"Page.loadEventFired"``).
            handler: Async callable that receives the event params dict.

        Returns:
            A Subscription that can be used to unsubscribe.
        """
        if event_name not in self._handlers:
            self._handlers[event_name] = []
        self._handlers[event_name].append(handler)
        return Subscription(self, event_name, handler)

    def off(self, event_name: str, handler: EventHandler) -> None:
        """Remove a previously registered handler.

        Args:
            event_name: CDP event name.
            handler: The handler to remove.
        """
        handlers = self._handlers.get(event_name)
        if handlers is None:
            return
        if handler in handlers:
            handlers.remove(handler)
            if not handlers:
                del self._handlers[event_name]

    async def dispatch(self, event_name: str, params: dict[str, Any]) -> None:
        """Dispatch an event to all registered handlers.

        Handler exceptions are caught and logged; other handlers still run.
        In strict mode, the first handler exception is re-raised after
        logging and the error callback runs.

        Args:
            event_name: CDP event name.
            params: Event parameters dict from the CDP message.

        Raises:
            Exception: If ``strict_events`` is True and a handler raises.
        """
        handlers = list(self._handlers.get(event_name, []))
        first_exc: BaseException | None = None
        for handler in handlers:
            try:
                result = handler(params)
                if inspect.isawaitable(result):
                    await result
            except Exception as exc:
                logger.exception(
                    "Handler error for event %s",
                    event_name,
                )
                if self._on_event_error is not None:
                    cb_result = self._on_event_error(event_name, params, exc)
                    if inspect.isawaitable(cb_result):
                        await cb_result
                if self._strict and first_exc is None:
                    first_exc = exc
        if first_exc is not None:
            raise first_exc

    @property
    def handler_count(self) -> int:
        """Total number of registered handlers across all events."""
        return sum(len(h) for h in self._handlers.values())

    def clear(self) -> None:
        """Remove all registered handlers."""
        self._handlers.clear()

handler_count property

handler_count: int

Total number of registered handlers across all events.

on

on(event_name: str, handler: EventHandler) -> Subscription

Register an async handler for a CDP event.

Parameters:

Name Type Description Default
event_name str

CDP event name (e.g. "Page.loadEventFired").

required
handler EventHandler

Async callable that receives the event params dict.

required

Returns:

Type Description
Subscription

A Subscription that can be used to unsubscribe.

Source code in cdpwave/events/dispatcher.py
def on(self, event_name: str, handler: EventHandler) -> Subscription:
    """Register an async handler for a CDP event.

    Args:
        event_name: CDP event name (e.g. ``"Page.loadEventFired"``).
        handler: Async callable that receives the event params dict.

    Returns:
        A Subscription that can be used to unsubscribe.
    """
    if event_name not in self._handlers:
        self._handlers[event_name] = []
    self._handlers[event_name].append(handler)
    return Subscription(self, event_name, handler)

off

off(event_name: str, handler: EventHandler) -> None

Remove a previously registered handler.

Parameters:

Name Type Description Default
event_name str

CDP event name.

required
handler EventHandler

The handler to remove.

required
Source code in cdpwave/events/dispatcher.py
def off(self, event_name: str, handler: EventHandler) -> None:
    """Remove a previously registered handler.

    Args:
        event_name: CDP event name.
        handler: The handler to remove.
    """
    handlers = self._handlers.get(event_name)
    if handlers is None:
        return
    if handler in handlers:
        handlers.remove(handler)
        if not handlers:
            del self._handlers[event_name]

dispatch async

dispatch(event_name: str, params: dict[str, Any]) -> None

Dispatch an event to all registered handlers.

Handler exceptions are caught and logged; other handlers still run. In strict mode, the first handler exception is re-raised after logging and the error callback runs.

Parameters:

Name Type Description Default
event_name str

CDP event name.

required
params dict[str, Any]

Event parameters dict from the CDP message.

required

Raises:

Type Description
Exception

If strict_events is True and a handler raises.

Source code in cdpwave/events/dispatcher.py
async def dispatch(self, event_name: str, params: dict[str, Any]) -> None:
    """Dispatch an event to all registered handlers.

    Handler exceptions are caught and logged; other handlers still run.
    In strict mode, the first handler exception is re-raised after
    logging and the error callback runs.

    Args:
        event_name: CDP event name.
        params: Event parameters dict from the CDP message.

    Raises:
        Exception: If ``strict_events`` is True and a handler raises.
    """
    handlers = list(self._handlers.get(event_name, []))
    first_exc: BaseException | None = None
    for handler in handlers:
        try:
            result = handler(params)
            if inspect.isawaitable(result):
                await result
        except Exception as exc:
            logger.exception(
                "Handler error for event %s",
                event_name,
            )
            if self._on_event_error is not None:
                cb_result = self._on_event_error(event_name, params, exc)
                if inspect.isawaitable(cb_result):
                    await cb_result
            if self._strict and first_exc is None:
                first_exc = exc
    if first_exc is not None:
        raise first_exc

clear

clear() -> None

Remove all registered handlers.

Source code in cdpwave/events/dispatcher.py
def clear(self) -> None:
    """Remove all registered handlers."""
    self._handlers.clear()

Subscription

Subscription

Handle returned by on() for unsubscribing a handler.

Holds a weak reference to the dispatcher so that unsubscribing after the dispatcher is garbage-collected is a no-op.

Source code in cdpwave/events/handlers.py
class Subscription:
    """Handle returned by ``on()`` for unsubscribing a handler.

    Holds a weak reference to the dispatcher so that unsubscribing after
    the dispatcher is garbage-collected is a no-op.
    """

    def __init__(
        self,
        dispatcher: EventDispatcher,
        event_name: str,
        handler: EventHandler,
    ) -> None:
        self._dispatcher_ref: weakref.ReferenceType[EventDispatcher] = (
            weakref.ref(dispatcher)
        )
        self._event_name = event_name
        self._handler = handler

    def unsubscribe(self) -> None:
        """Remove the handler from the dispatcher."""
        dispatcher = self._dispatcher_ref()
        if dispatcher is not None:
            dispatcher.off(self._event_name, self._handler)

    @property
    def event_name(self) -> str:
        """The CDP event name this subscription is for."""
        return self._event_name

    @property
    def handler(self) -> EventHandler:
        """The handler callable attached to this subscription."""
        return self._handler

event_name property

event_name: str

The CDP event name this subscription is for.

handler property

handler: EventHandler

The handler callable attached to this subscription.

unsubscribe

unsubscribe() -> None

Remove the handler from the dispatcher.

Source code in cdpwave/events/handlers.py
def unsubscribe(self) -> None:
    """Remove the handler from the dispatcher."""
    dispatcher = self._dispatcher_ref()
    if dispatcher is not None:
        dispatcher.off(self._event_name, self._handler)

EventHandler

EventHandler module-attribute

EventHandler = Callable[[dict[str, Any]], Awaitable[None]]

Async callable that receives CDP event params.