How to migrate from 0.13¶
Release 0.14 replaced the container layer of 0.13 (redsun.containers,
redsun.virtual, AppContainer) with sessions. Nothing of the old layer is
kept under its old name, so this page lists each change and what to write
instead.
Imports¶
| 0.13 | now |
|---|---|
redsun.AppContainer |
redsun.Session |
redsun.qt.QtAppContainer |
redsun.qt.QtSession |
redsun.containers.declare_device, declare_presenter, declare_view |
AsDevice, AsPresenter, AsView annotations |
redsun.containers.declare_service |
AsService with Launch or Attach |
redsun.containers.declare_hook |
AsHook |
redsun.containers.ConfigurationError, HookError |
redsun.ConfigurationError, redsun.HookError |
redsun.virtual.slot |
redsun.slot |
redsun.virtual.Signal |
psygnal.Signal |
redsun.virtual.CallbackType |
redsun.CallbackType |
redsun.virtual.WiringError, ports |
redsun.WiringError, redsun.ports.ports |
redsun.virtual.Connection, ComponentNotBuilt, HasShutdown |
redsun.Connection, redsun.ComponentNotBuilt, redsun.HasShutdown |
redsun.virtual.Ports, Unconnected, SlotThread |
the same names in redsun.ports |
redsun.virtual.Subscription |
removed: a redsun.Connection records a link from a device signal too |
redsun.virtual.RedSunConfig |
redsun.SessionConfig, a frozen dataclass with the same four fields |
redsun.virtual.IsProvider, IsInjectable, ProviderKey |
removed: see Sharing values between components |
redsun.virtual.VirtualContainer, SignalCache |
removed: see Wiring |
redsun.presenter.PPresenter, Presenter |
nothing to inherit: see Presenters |
redsun.view.PView, View, ViewPosition, redsun.view.qt.QtView |
a QWidget with a placement: see Views |
redsun.utils.find_signals |
removed: yield a link in wire |
Declaring components¶
# 0.13
class MyApp(QtAppContainer):
motor = declare_device(MyStage, axis=["X", "Y"])
motor_ctrl = declare_presenter(MotorPresenter)
motor_widget = declare_view(MotorView, step_size=5.0)
# now
class MyApp(QtSession):
motor: Annotated[AsDevice[MyStage], Declare(axis=["X", "Y"])]
motor_ctrl: AsPresenter[MotorPresenter]
motor_widget: Annotated[AsView[MotorView], Declare(step_size=5.0)]
- The
config=class keyword is now a class attribute:config = "session.yaml". from_config="key"isFromConfig("key"), and a different registered name isAlias("name"), both insideAnnotated.MyApp(session="lab")isMyApp({"session": "lab"}): a session takes its settings as a configuration source.
Presenters¶
A presenter no longer starts with (name, devices), and it inherits nothing. The
session calls it with every argument by keyword and fills each parameter by
type:
# 0.13
class MotorPresenter(Presenter):
def __init__(
self, name: str, devices: Mapping[str, Device], /, step: float = 1.0
) -> None:
super().__init__(name, devices)
self.step = step
# now
class MotorPresenter:
def __init__(self, name: str, *, devices: DeviceMapping, step: float = 1.0) -> None:
self.name = name
self.devices = devices
self.step = step
Ask for DevicesOf[P] to get only the devices satisfying a protocol.
Views¶
A view is a QWidget whose constructor starts with (name: str, parent:
QWidget), and which says where it goes with a placement:
# 0.13
class MotorView(QtView):
def __init__(self, name: str, /, **kwargs: Any) -> None:
super().__init__(name)
@property
def view_position(self) -> ViewPosition:
return ViewPosition.LEFT
# now
class MotorView(QWidget):
placement: Placement = Dock("left")
def __init__(self, name: str, parent: QWidget) -> None:
super().__init__(parent)
self.name = name
ViewPosition.CENTER is Central(). A view's slots still run on the main
thread, as QtView's did.
Sharing values between components¶
register_providers, inject_dependencies, ProviderKey and the
provide/require/try_require calls are gone. The type is the key:
# 0.13
MOTOR_READINGS = ProviderKey(instance_of=MotorReadings)
def register_providers(self, container):
container.provide(MOTOR_READINGS, self._readings)
def inject_dependencies(self, container):
self.readings = container.require(MOTOR_READINGS)
# now
@provides
def readings(self) -> MotorReadings:
return self._readings
def setup(self, readings: MotorReadings) -> None:
self.readings = readings
try_require(KEY) becomes a parameter x: X | None = None. See
Share a value.
The keys redsun.path_provider.PATH_PROVIDER, redsun.catalog.CATALOG and
redsun.engine.DEFERRALS are removed. Ask for the type instead: a
SessionPathProvider or CatalogAddress | None parameter, and Deferrals
from the component that shares it.
Wiring¶
wire no longer connects anything itself. It's a generator that yields each
link as a signal and the slot it reaches, and the session makes the
connection. connect, subscribe and connect_paths are gone. You yield a
psygnal signal and an ophyd-async device signal the same way, and the
session tells them apart:
# 0.13
def wire(self) -> None:
self.connect(self.ctrl.sig_moved, self.widget.refresh)
self.virtual_container.subscribe(self.stage.readback, self.widget.on_reading)
# now
def wire(self) -> Iterator[Link]:
yield self.ctrl.sig_moved, self.widget.refresh
yield self.stage.readback, self.widget.on_reading
The wiring section of a session file is now a mapping from a signal path to
one slot path or a list of them, not a list of from:/to: entries:
# 0.13
wiring:
- from: ctrl.sig_moved
to: widget.refresh
# now
wiring:
ctrl.sig_moved: widget.refresh
The virtual container's other members moved too:
| 0.13 | now |
|---|---|
container.virtual_container.connections, .unconnected, .disconnect_all |
the same names on the session |
container.virtual_container.subscriptions |
removed: connections lists a link from a device signal with the others |
register_callbacks |
a component that is a DocumentRouter is collected; ask for Mapping[str, CallbackType] in setup |
register_signals, signals, find_signals |
removed |
The session skips and logs a link to a component that failed to build, instead of raising.
Hooks¶
# 0.13
class MyApp(QtAppContainer):
configure_application = declare_hook(DarkTheme, accent="#d47f4c")
# now
class MyApp(QtSession):
configure_application: Annotated[AsHook[DarkTheme], Declare(accent="#d47f4c")]
- A hook can no longer be given as a built object; pass the class and its arguments.
- One provider for several points uses
Serves(...), not the same object declared twice. QtCreatesApplication,QtConfiguresApplication,QtWrapsBuildandQtConfiguresMainVieware removed.QtHooknames the points, and their protocols areCreatesApplication,ConfiguresApplication,WrapsBuildandConfiguresMainView, imported fromredsun.during_buildnow covers the build steps only, not the window's first paint.
Services¶
# 0.13
class MyApp(AppContainer):
transport = "pv-access"
camera_ioc = declare_service(
module="mylab.iocs.camera", ready="serving", prefix="CAM:"
)
# now
class MyApp(Session):
config = {"services": {"transport": "pv-access"}}
camera_ioc: Annotated[
AsService, Launch("mylab.iocs.camera", ready="serving", prefix="CAM:")
]
A service without a module is Attach("BL01:"). Services start in the first
build step, so you can no longer call start_services before build.
Session files¶
| 0.13 | now |
|---|---|
frontend: pyqt or pyside |
frontend: qt, or no key; QT_API picks the binding |
schema_version and frontend required |
both optional |
from_config accepted a file without session |
session is required |
an entry that did not resolve raised PluginError |
it is logged and left out; set strict: true to stop instead |
| an entry naming no plugin and no declared component was ignored | it is logged and left out |
New keys: strict, mock, providers, actions, color_scheme. See
Write a session file.
Plans¶
# 0.13
@continous(togglable=True, pausable=True)
def live(detectors: Sequence[DetectorProtocol]) -> MsgGenerator[None]: ...
# now
@continuous(pausable=True)
def live(detectors: Sequence[DetectorProtocol]) -> MsgGenerator[None]: ...
| 0.13 | now |
|---|---|
redsun.engine.actions.continous |
redsun.engine.actions.continuous; the old spelling is not kept |
continous(togglable=...) |
removed: a continuous plan always has a toggle to start and stop it |
redsun.engine.actions.ContinousPlan |
removed: read __continuous__ from the plan |
__togglable__, __pausable__ on a plan |
__continuous__, a Continuous with a pausable field |
PlanSpec.togglable |
PlanSpec.continuous |
wait_for_actions(events, timeout=...) |
wait_for_actions(events, poll_interval=...) |
Action |
PlanAction |
Action(togglable=True, toggle_states=...) |
PlanAction(toggle_states=...) |
Action.togglable |
removed: toggle_states is None for a button that is clicked |
action.event_map |
removed: a PlanAction holds no latch |
name, latch = yield from wait_for_actions(action.event_map) |
name = yield from actions.wait(action) |
wait_for_actions(action.event_map, wait_for="reset") |
actions.wait_released(action) |
latch.reset() once the plan has acted |
actions.done(name) |
| a latch set from a slot | actions.request(name), linked in wire to the signal of the view |
wait_for_actionsnever timed out, and still does not: the argument is the time between two checkpoints.wait_for_actionsraisesValueErrorwheneventsis empty.actionsis anActionManagerfromredsun.engine.actions, made by whoever owns the plans. See Actions while a plan runs.PlanActionis frozen, andPlanAction.toggle_statesdefaults toNonewhere it defaulted to("On", "Off").create_plan_specraisesValueErrorfor a plan declaring two actions of one name.
Other changes¶
- The
experimentalextra is gone;in-n-outis a dependency ofredsun, anddependency-injectoris no longer one. - The session opens its log files when the build reads the configuration, not when the session object is made.
- The main window no longer starts maximised; it opens where the user last left it.
AppContainer.config, the merged configuration, isSession.serialize().runexists only on a session with a frontend.DescriptorTreeView.update_readingandconfirm_changeare replaced byset_value(key, value), which shows what the device read back and settles a pending edit, andrevert(key), for an edit the device refused.redsun.engine.Statusis removed; import it frombluesky.protocols.connect_devicesis removed. A session file settingmock: trueconnects everyautoconnectdevice to a simulated backend during the build, and a device declared withautoconnect: falseis connected by the component that uses it.