Skip to content

Signal

Signals are Saffier's event hooks around model lifecycle operations.

They are most commonly used for:

  • side effects around save or delete operations
  • keeping denormalized data in sync
  • integrating logging, auditing, or background work with ORM events

Common built-in lifecycle signals

Model broadcasters expose these built-in hooks:

  • pre_save
  • post_save
  • pre_update
  • post_update
  • pre_delete
  • post_delete

Use the guide page for the narrative walkthrough: Signals

saffier.Signal

Signal()

Minimal async signal dispatcher used by model lifecycle hooks.

Receivers are stored in insertion order and are invoked concurrently when the signal is sent.

Initialize an empty receiver registry for the signal.

Source code in saffier/core/signals/signal.py
30
31
32
def __init__(self) -> None:
    """Initialize an empty receiver registry for the signal."""
    self.receivers: dict[int | tuple[int, int], Callable] = {}

receivers instance-attribute

receivers = {}

connect

connect(receiver)

Connect one receiver to the signal.

PARAMETER DESCRIPTION
receiver

Callable accepting **kwargs.

TYPE: Callable

RAISES DESCRIPTION
SignalError

If the receiver is not callable or does not accept keyword arguments.

Source code in saffier/core/signals/signal.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def connect(self, receiver: Callable) -> None:
    """Connect one receiver to the signal.

    Args:
        receiver: Callable accepting `**kwargs`.

    Raises:
        SignalError: If the receiver is not callable or does not accept
            keyword arguments.
    """
    if not callable(receiver):
        raise SignalError("The signals should be callables")

    if not func_accepts_kwargs(receiver):
        raise SignalError("Signal receivers must accept keyword arguments (**kwargs).")

    key = make_id(receiver)
    if key not in self.receivers:
        self.receivers[key] = receiver

disconnect

disconnect(receiver)

Disconnect one receiver from the signal.

RETURNS DESCRIPTION
bool

True if a receiver was removed.

TYPE: bool

Source code in saffier/core/signals/signal.py
54
55
56
57
58
59
60
61
62
def disconnect(self, receiver: Callable) -> bool:
    """Disconnect one receiver from the signal.

    Returns:
        bool: `True` if a receiver was removed.
    """
    key = make_id(receiver)
    func: Callable | None = self.receivers.pop(key, None)
    return func is not None

send async

send(sender, **kwargs)

Dispatch the signal to all connected receivers concurrently.

PARAMETER DESCRIPTION
sender

Model class sending the signal.

TYPE: type[Model]

**kwargs

Signal payload forwarded to every receiver.

TYPE: Any DEFAULT: {}

Source code in saffier/core/signals/signal.py
64
65
66
67
68
69
70
71
72
async def send(self, sender: type["Model"], **kwargs: Any) -> None:
    """Dispatch the signal to all connected receivers concurrently.

    Args:
        sender: Model class sending the signal.
        **kwargs: Signal payload forwarded to every receiver.
    """
    receivers = [func(sender=sender, **kwargs) for func in self.receivers.values()]
    await asyncio.gather(*receivers)