Skip to content

Events

Name What it does
BoxEvents the psygnal signal group of a box: one (new, old) signal per field
FieldWatch new values of one field, for for and async for

BoxEvents

Bases: SignalGroup

The psygnal signal group of a box: one (new, old) signal per field.

It is the type of SharedBox.events and of the groups follow returns. The group of a class with reference fields also has the signal nested.

nested instance-attribute

nested: SignalInstance

Emitted as (path, new, old) for each change inside the boxes that follow() without a field follows.

path is the tuple of field names from this group's box to the changed field. It reports every field of each box's own class, and not changes of this box's own fields, which keep their own signals. box.events.connect(cb) receives it too. Only the group of a class with reference fields has it, and it emits only after follow is called without a field.

follow

follow(field: str) -> BoxEvents
follow(field: None = None) -> None
follow(field: str | None = None) -> BoxEvents | None

Forward changes made inside the boxes that reference fields refer to.

With field, return a group with the signals of the class field is annotated with, emitted for whichever box the field refers to when the change happens; a later call returns the same group. Without field, emit every change inside every box the reference fields reach, down the whole graph, on nested.

The group follow(field) returns has follow and unfollow too, so stage.events.follow("motor").follow("encoder") reaches one level further. Its signals are those of the annotated class: a field that only a subclass has is left out.

Forwarding follows each box once. A box it already follows, this group's box included, is not followed again where another reference leads to it, so a loop of references ends. A box that several paths reach is reported under one of them, and after a reference changes that may be a different one; it is forwarded as long as any path reaches it. An empty reference forwards nothing until a box is assigned.

When a reference changes, in any process, the watcher emits the reference's own signal first, then moves forwarding to the new box. Values the new box already held are not emitted, and a change made to the old box just before the move may not be. Moving forwarding away from a box waits for that box's callbacks to return.

A reference to a box that was removed or created again, or whose class this process has not defined, logs a warning to the sharedbox logger and forwards nothing until the field is assigned another box; follow does not raise for it. Any other error from attaching a box is raised by the follow call that attached it; that call then follows nothing new, and the next call tries again. When a reference change starts the attach instead, the error is logged, and that box is tried again the next time any reference field followed through this group changes.

Forwarding attaches its own handle to each box it follows and waits with that handle's watcher thread, so forwarded callbacks run on several threads, one per followed box, possibly at the same time. Connect with thread="main" and call psygnal.emit_queued() to run them on the main thread. The handle that reading the field returns is a different one.

A child process created with fork inherits the forwarding without its threads, and forwards nothing until it calls follow on this group, or on a group follow returned, with any field or none. That call follows the boxes the reference fields refer to at that moment, through the same groups, so callbacks connected before the fork keep receiving. Changes made before that call are not emitted. An error from attaching a box in that call is logged, and the box is tried again as after a reference change.

Raises:

Type Description
ValueError

If the class has no field field.

TypeError

If field is not a reference field.

BoxClosedError

If the box is closed.

unfollow

unfollow(field: str | None = None) -> None

Stop forwarding for field, or all forwarding started through this group.

With field, stop only the group that follow(field) returned, and leave running what follow() without a field started. The group is forgotten: callbacks connected to it receive nothing more, and a later follow returns a new group. Without field, stop both. The handles that reading the fields returned stay open. close on the box stops all its forwarding.

Raises:

Type Description
ValueError

If the class has no field field.

TypeError

If field is not a reference field.

FieldWatch

FieldWatch(watcher: Watcher, field: FieldSpec, since: int)

Bases: Generic[T]

New values of one field, for for and async for.

SharedBox.watch returns it. Only writes made after the watch was created count. Iterating with for blocks until the field is written and yields the new value; async for waits the same way without blocking the event loop. A consumer slower than the writers gets the latest value and skips the ones in between. Iteration ends when the box is closed.