redsun.session¶
The session¶
Session
¶
Session(
config: Source | Sequence[Source] | None = None,
*,
log_level: int | str | None = None,
profile: ProfileKind | None = None,
profile_dir: str | Path | None = None,
)
Bases: BuildableSession
One running application, whose components are declared as annotations.
class MyApp(QtSession):
config = "session.yaml"
stage: AsDevice[MyStage]
motor_ctrl: AsPresenter[MotorPresenter]
motor_widget: Annotated[AsView[MotorView], Declare(step_size=5.0)]
def wire(self) -> Iterator[Link]:
yield self.motor_ctrl.sig_moved, self.motor_widget.update
An annotation is a declaration only if it names a layer, so a session may
hold ordinary attributes alongside its components. The attribute name is
both the component name and its configuration key; Alias and FromConfig
override each. Reading a declared attribute on a built session gives the
instance, typed by its annotation.
A component that failed to build is set on nothing, so reading its name
raises AttributeError rather than answering None. Inside wire it
reads as a stand-in instead, and a link naming it is skipped with a
warning.
Prepare an empty session, to be filled by build.
config layers over whatever the class declares rather than replacing
it, so a caller naming one key changes that key and leaves the rest.
log_level, a logging constant or its name, sets the redsun
logger's level; None leaves it as it is.
profile records where the session spends its time, with
pyinstrument from the profile extra: "start" up to the end of
build, "run" up to shutdown. The profile is an HTML file named
after the run's log file, under profiles/<session> beside the logs,
keeping the most recent runs; profile_dir puts it in that folder
instead and keeps everything. Only the thread the session is made on
is sampled: device connections, plans and services show as waits or
not at all.
Raises:
| Type | Description |
|---|---|
ValueError
|
If profile is neither |
TypeError
|
If profile_dir is given without profile. |
ImportError
|
If profile is given and |
config
class-attribute
¶
The configuration this session is declared with.
One source or several, each a path to a YAML file or a mapping already in hand. Several layer in the order given, and a subclass's layer over its bases', so a base holds what every session of an instrument shares and a subclass holds what makes it that session.
providers
class-attribute
¶
The shared services this session installs before any component.
Each is an ordinary class whose methods marked with
redsun.provides put values in the session for components to
ask for by type. A provider has no name, no layer and no wiring.
hook_points
class-attribute
¶
The points this session calls a hook at, by the protocol each demands.
Empty here: every hook point belongs to a toolkit, so a toolkit session
such as redsun.qt.QtSession names its own.
frontend
class-attribute
¶
The toolkit this session is built against.
Set by subclassing, as redsun.qt.QtSession does. The
default attaches nothing and constrains no view.
transport
property
¶
What the session's services speak, one for all of them.
Named once under services in the configuration; channel-access
when it names nothing.
presenters
property
¶
The presenters that built successfully.
views
property
¶
The views that built successfully, ready for a frontend to attach.
view_arguments
property
¶
What the session passes every view's constructor, besides its name.
Nothing here. A session bound to a toolkit passes what its views are built with, by keyword.
declarations
property
¶
The declarations collected from the class.
settings
property
¶
What this session remembers about how one user likes to run it.
Per user and per machine rather than part of the session file, and registered in the store, so an action asks for it by type.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If read before |
hooks
property
¶
The hook provider this session installs at each point, built once.
A subclass firing a point calls this rather than resolving again, so
that every point of one build acts on one set of providers. A provider
with a shutdown method is shut down with the session, once however
many points it serves, the last built first.
Raises:
| Type | Description |
|---|---|
HookError
|
If a point is claimed by more than one provider, a provider cannot be built, or one does not implement the protocol its point calls. |
name
property
¶
What this session is called.
The configuration's session, or this session's own class name when
the configuration says nothing.
storage
property
¶
path_provider
property
¶
The session's path provider, shared by every device taking one.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If read before |
callbacks
property
¶
The document routers the session built, by name.
unconnected
property
¶
Ports of the built components that no connection reaches.
The complement of connections: what a component
offers and nothing uses.
Raises:
| Type | Description |
|---|---|
WiringError
|
If a component exposes two signals under one port name. |
from_config
classmethod
¶
from_config(
source: Source | Sequence[Source],
*,
log_level: int | str | None = None,
profile: ProfileKind | None = None,
profile_dir: str | Path | None = None,
) -> Self
Return a session described entirely by source.
Every component the configuration names is declared, its layer coming
from the section it appears under, so a session needs no class
of its own. The frontend key chooses the class to build
on; naming none builds on this one, which is what a session with no
toolkit wants.
The session comes back unbuilt, so that whatever the configuration
cannot say is still said in Python before build runs.
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the configuration does not name the session: with no class of its own, it has no class name to fall back on. |
ValueError
|
If the configuration names a frontend no session is built against. |
ImportError
|
If it names one whose packages are not installed. |
TypeError
|
If it names one this session is not built against. |
make_store
¶
Return the registry this session builds its components out of.
Named after the session, and not entered in the process-wide registry
Store.create keeps.
A session owning an application of its own overrides this to return
that application's store, which app-model registers and
Application.destroy frees.
build
¶
Run each step of BuildableSession in turn, announcing all but two.
Devices are built first and on their own; one that fails is logged and skipped. The components follow in layer order, and within a layer in the order they are built from one another.
A session built against a toolkit fills start_runtime and
present rather than overriding this method.
read_configuration and start_runtime run before the span opens and
are not announced. A step that raises stops the build: the exception
is logged, and shutdown gives back what the finished steps took
before the exception leaves.
open_span
¶
Return the span the build announces its steps to.
By default this yields the reporter already in place, which does nothing.
on_release
¶
Register how to give something back, as the step takes it.
shutdown runs the releases registered so far, so a build that
stopped halfway gives back only what it took.
wire
¶
Yield the links of the session, each a signal and the slot it reaches.
def wire(self) -> Iterator[Link]:
yield self.motor_ctrl.sig_moved, self.motor_widget.update
yield self.stage.readback, self.motor_widget.on_reading
A signal is a psygnal signal of a component, or a signal of an
ophyd-async device, whose reading dictionary the slot is called
with. A slot is a bound method marked with slot, which may be a
coroutine function, and is delivered on the thread it declares, then
the one its class declares, then the one the frontend gives it.
links_between gives every link a pairing of
two components makes, to yield from.
Every component that built exists by the time this runs. One that failed reads as a stand-in, and a link naming it is skipped with a warning. Yields nothing by default.
shutdown
¶
Run every registered release, in the reverse of the order taken.
Connections go first, so nothing is delivered to a component that is
already finalizing. The releases follow: the shutdown method of
every component that has one, then whatever a toolkit put in place.
Calling it a second time, or on a session that was never built, runs
nothing: a release is dropped as it runs. What the build made is
forgotten, so the session can be built again.
serialize
¶
Return the configuration that would rebuild this session.
The merged configuration, holding the entry each built component
asked for through redsun.Serializable. A component
that implements none of it, that failed to build, or that asked for a
key its constructor would refuse keeps the entry the session was
built from, and no other component is affected by that.
Layered sources are merged before anything is built, so what comes back is one flat configuration whatever the session was built from.
write
¶
Write the configuration that would rebuild this session to path.
One flat file whatever the session was built from, so it opens on its own with nothing to assemble first. Comments do not survive, the file being written rather than edited, and the keys come out in the order the merged configuration holds them.
Raises:
| Type | Description |
|---|---|
ConfigurationInUse
|
If path is a source this session was built from. |
has_changes
¶
Whether any component asks to be written differently than at build.
The session compares against what each component serialized once the build finished, not against the configuration it was built from.
A value changed and changed back reads as unchanged, and a component that does not serialize itself never reports a change.
satisfying
¶
Return the built components satisfying protocol, by name.
rejected
¶
Return why each component that nearly satisfies protocol does not.
disconnect_all
¶
Undo every connection and subscription made through this session.
Build steps¶
build runs these methods of Session in this order. A subclass replaces one
by overriding it.
read_configuration
¶
Merge the sources, install the hooks, read the declarations, open the logs.
start_runtime
¶
Put in place what a component may not be constructed without.
The async backend, which a coroutine slot cannot be connected without. A session bound to a toolkit makes the toolkit's objects here as well, before the first component exists and before anything can watch the build.
start_services
¶
Start the launched services together, and attach to the rest.
The step takes as long as the slowest service. A service that does not
start is logged, and devices naming it are skipped. Each stop is a
release, so shutdown stops services after every component, the last
declared first. Before that it closes what the transport holds open
to them, so no connection is cut under it and a rebuilt session
reconnects at once.
A session whose configuration sets mock starts none: its devices
connect to simulated backends, which reach no service.
build_devices
¶
Construct the devices, which are built from no other component.
They are built before the store opens, each from its own declaration
alone. A device naming a service
receives that service's prefix as prefix, and is skipped when the
service is not declared, did not start, or gives no prefix.
connect_built_devices
¶
Connect every autoconnect device at once.
To a simulated backend when the configuration sets mock, to what
the device names otherwise. A device not connected within
CONNECT_TIMEOUT is dropped and recorded as failed, like one that
fails to build, and its shutdown is never called. One that
connected has its shutdown registered as a release.
open_registry
¶
Open the store the components are built out of, and fill it.
The settings and the shared services come first and the questions the components ask are answered next, so that everything a constructor may reach for is registered before the first one runs.
build_presenters
¶
Construct the presenter layer, in the order it depends in.
setup_components
¶
Hand every component what another component owns.
Every presenter and view exists by now, so a setup may take a value
another component shares, a census of the session, or a component
itself. One that cannot run is reported and changes nothing else: the
component keeps its place, its wiring and what its constructor made,
and what its setup was going to assign is missing where it is used.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the registry step has not opened the store yet. |
TypeError
|
If a |
apply_wiring
¶
Make the links the class yields, then those the file lists, then its pairs.
A link made already is not made again.
Raises:
| Type | Description |
|---|---|
WiringError
|
If |
present
¶
Assemble what was built into whatever shows it.
Nothing here: a session bound to no toolkit shows nothing, which is what a headless test wants. One bound to a toolkit puts its views where each asks to be.
log_summary
¶
Log what the build made, counted against what was declared.
Raises:
| Type | Description |
|---|---|
BuildError
|
If the configuration sets |
BuildableSession
¶
Bases: Protocol
The steps a session's build runs, each one a method of its own.
build calls them in the order they are written here and does nothing
else. A session bound to no toolkit answers start_runtime and present
with nothing, and one bound to a toolkit fills exactly those two: what
has to exist before a component can be constructed, and how what was
built is assembled into whatever shows it.
Every step takes nothing and returns nothing. What a step needs it reads
from the session, and what it leaves it leaves on the session. A step
taking something that has to be given back registers how with
on_release at the moment it takes it, and shutdown runs those in
reverse, after a finished build or one that failed partway.
Inherit it rather than satisfying it structurally. The members are abstract, so a session missing one is refused when it is constructed and a type checker refuses it too.
BuildableSession lists the same methods; they
are described above, with Session.
DesktopSession
¶
Bases: BuildableSession, Protocol[WindowT_co]
A session whose views are attached to a window and shown on a screen.
The window's type is the parameter, since it is the toolkit's and no two
toolkits share one: a session built on Qt satisfies
DesktopSession[QMainWindow].
main_window is a property here, so it is a data descriptor in every
implementer's method resolution order: answer it with a property of its
own, never by assigning self.main_window in __init__.
Declaring components¶
AsDevice
module-attribute
¶
An ophyd_async.core.Device, built before every other layer.
AsPresenter
module-attribute
¶
A component holding application logic, taking name first.
Satisfies redsun.NamedComponent, and declares no placement.
AsView
module-attribute
¶
A component presenting an interface, taking name first.
Satisfies redsun.AttachableComponent, so it declares the
redsun.Placement it asks the frontend to attach it at.
AsService
module-attribute
¶
A server the session's devices talk to, started before any component is built.
redsun.Launch describes a service the session runs, and
redsun.Attach one already running elsewhere. Without either,
the service comes from the session's services entry for it. An alias
carrying the marker can be declared once and used by several sessions:
AsHook
module-attribute
¶
A callback the session calls at one point of the toolkit's startup.
The attribute name is the point, redsun.Serves names them
instead, and redsun.Declare carries the constructor arguments.
A hook is not a component: it is never injected, nothing may depend on it, and
it has no say in what the session builds.
Declare
dataclass
¶
Inline keyword arguments, overriding anything the configuration gives.
kwargs
class-attribute
instance-attribute
¶
Keyword arguments for the constructor of the component.
FromConfig
dataclass
¶
Configuration key, when it cannot be the attribute name.
Needed only for keys that are not identifiers, or that deliberately differ from the attribute they are read into.
Alias
dataclass
¶
Component name, when it must differ from the attribute name.
Launch
dataclass
¶
Launch(
module: str,
*,
ready: str | None = None,
prefix: str | None = None,
args: Sequence[str]
| Mapping[str, ArgValue]
| None = None,
stop_timeout: float | None = None,
)
A service the session runs as python -m <module> <args>.
A keyword left as None is taken from the service's services entry,
and otherwise from redsun.services.Service.
ready
class-attribute
instance-attribute
¶
Text of the output line marking the service ready.
prefix
class-attribute
instance-attribute
¶
Prefix given to each device naming the service.
args
class-attribute
instance-attribute
¶
Command-line arguments following the module, as a list or as options.
stop_timeout
class-attribute
instance-attribute
¶
Seconds each step of stopping waits for the process to exit.
Attach
dataclass
¶
Serves
dataclass
¶
The hook points one provider serves, when the attribute name is not one.
Declaring several is how one provider instance serves several points: the annotation names the class once, so one object is built for them all.
Layer
¶
Bases: StrEnum
The layer a declared component belongs to.
Carried as the metadata of a declaration's annotation. redsun.session.components
spells the three out. A member is its own name in a message, so it needs no
.value, and its section is that name pluralised.
section
property
¶
The configuration section this layer's components are declared under.
Declaration
¶
Declaration(
cls: type,
name: str,
kind: Layer,
cfg_kwargs: dict[str, Any],
*,
attribute: str | None = None,
source: str | None = None,
refusal: Exception | None = None,
frontend: type[Frontend] = Frontend,
)
A declared component, before and after it is built.
key is a distinct type per component name, so two instances of one
class stay separable in a type-keyed graph. A device's service and
autoconnect keywords are kept here, not passed to its constructor.
A view's placement keyword is kept here too, read and checked against
the frontend: placement is where the view attaches, or None when its
class answers from a property and only the built instance can say.
refusal is why the class cannot be built in its layer, or None; a
refused declaration is never built. attribute is the annotation the
session class declares it under and source the configuration entry its
keywords are read from; both are the name unless a marker says otherwise.
Raises:
| Type | Description |
|---|---|
TypeError
|
If a device's keywords name its service ambiguously, or give an
|
What a component is held to¶
NamedComponent
¶
Bases: Protocol
A component that knows the name it was declared under.
Every component must satisfy this, presenters and views alike, and keep the name it was constructed with.
A plain instance attribute, a class attribute or a property all satisfy it.
AttachableComponent
¶
Bases: NamedComponent, Protocol
A component the frontend can attach, and where it asks to go.
placement is the only difference between a view and a presenter, and
it is what the frontend reads to attach the view. When it is answered
from the class, a session refuses a view its frontend cannot attach
before anything is built.
placement
property
¶
Where the component asks to be attached when its declaration names no placement.
HasSetup
¶
Bases: Protocol[SetupP]
A component taking what another component owns, once every one exists.
The session fills the parameters by type, the way it fills a constructor's, and calls it in a step of its own. It answers nothing: what it is given is the component's to keep.
HasShutdown
¶
Bases: Protocol
A component finalizing itself when the session shuts down.
The session registers the method as a release when the component is built, so a component that has one is finalized without asking for it.
HasAsyncShutdown
¶
Serializable
¶
Bases: Protocol
A component that supplies the configuration entry rebuilding it.
serialize returns the keyword arguments the component's own entry
would carry. The session writes them under that component's name and
nowhere else, so a component reaches no entry but its own, and the next
session reads back what this one wrote.
Implementing it is optional, and a component that leaves it out keeps whatever the configuration already said about it.
A value that moves on its own, such as a stage position or a frame count, is not a constructor argument and does not belong in the result.
serialize
¶
Return the keyword arguments this component would be rebuilt from.
Hook points¶
The protocol of each hook point, in the order a session reaches them.
CreatesApplication
¶
ConfiguresApplication
¶
WrapsBuild
¶
Bases: Protocol[AppT_contra]
Surrounds the build, from before the first component to after the window.
The one hook point that is a span, not a moment: a splash screen appears before anything is built, reports progress, and closes once the window is on screen.
during_build
abstractmethod
¶
Return a context manager open for the whole build.
What it yields is called with the name of each step as it starts.
ConfiguresMainView
¶
ConfirmsClose
¶
Frontends and placements¶
Frontend
¶
The toolkit an application is built against.
requires pairs each redsun.Placement the frontend
attaches with the toolkit type it demands of the view asking for it. A view
asking for a placement the frontend does not list, or one whose class is
not the type its placement demands, is refused before it is built. The
placements themselves and the attaching live in the frontend's own
package.
An empty table constrains nothing, which is what an application that names no toolkit gets.
thread_of says where the slots of a component run when the component
does not say. A slot held for a thread is called there once that thread
calls psygnal.emit_queued, which a session built on the frontend does
from the toolkit's event loop.
check_view
classmethod
¶
Refuse a view class this frontend cannot build.
Runs where the view is declared, beside check_placement. Nothing is
refused here; a frontend constraining how its views are constructed
overrides it.
Raises:
| Type | Description |
|---|---|
TypeError
|
In an override, naming what view lacks. |
thread_of
classmethod
¶
Return the thread the slots of consumer run on when nothing else says.
Asked after the slot itself and the class of consumer. None
here: the slot runs on the thread that emits.
read_placement
classmethod
¶
Return the placement a session file's value names, in this frontend's words.
A frontend that attaches views overrides it; this one reads no word.
Raises:
| Type | Description |
|---|---|
ValueError
|
Always, naming the frontend. |
check_placement
classmethod
¶
Confirm the frontend attaches placement, and view is what it demands.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
view
|
type | object
|
The class before anything is built and the instance afterwards. Either answers the question, the demand being on the class. |
required |
placement
|
Placement
|
Where the view asks to be attached. |
required |
where
|
str
|
How to name the view in a refusal, such as
|
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the frontend lists what it attaches and this is not one of them, or if the view is not the toolkit type that placement demands. |
Placement is described with
redsun.view.
Settings¶
Settings
¶
What a session remembers about how one user likes to run it.
Kept per user and per machine, apart from the session file.
A session builds one for itself and registers it, so an action asks for it by type. Reading a session that has never written one gives the defaults asked for; the file appears the first time something is set.
Read path if it is there, and remember where to write it back.
for_session
classmethod
¶
Return the settings of the session called name.
Sessions do not share a file.
Errors¶
The exceptions a session raises are in redsun.errors.
Build steps¶
BUILD_STEPS
module-attribute
¶
BUILD_STEPS: Final[tuple[str, ...]] = (
"services",
"devices",
"connect",
"registry",
"presenters",
"views",
"setup",
"seal",
"wiring",
"presentation",
"report",
)
The steps a build reports, in order, to whatever is watching it.
A during_build hook is told one of these names as each step starts.
Session.build runs two steps before the first of these, reading the
configuration and starting the toolkit's runtime, and reports neither.