Component system¶
redsun component system allows third-party packages to provide devices, presenters, and views that are dynamically discovered and loaded at runtime.
Overview¶
From Python point of view, components are standard Python packages that register themselves via entry points. When redsun builds an application from a YAML configuration file, it uses these entry points to discover available plugins and load the requested components.
graph TB
Config[YAML config] -->|references| Plugins
Plugins -->|discovered via| EntryPoints[entry points]
EntryPoints -->|load| Manifest[plugin manifest]
Manifest -->|resolves to| Classes[component classes]
Classes -->|registered in| Container[AppContainer]
Plugin discovery¶
When AppContainer.from_config()
is called with a configuration file, Redsun:
- Reads the configuration - parses the YAML file to determine which devices, presenters, and views are needed.
- Queries entry points - looks up installed packages registered under the
redsun.pluginsentry point group. - Loads manifests - each plugin provides a YAML manifest file that maps plugin IDs to their Python class locations.
- Validates components - devices are checked at discovery (must subclass
ophyd_async.core.Device); presenter and view instances are validated againstPPresenter/PViewwhen the container builds (see Protocol validation). - Creates the container - a dynamic container class is assembled with the discovered components.
Component manifest¶
Each component package must include a YAML manifest file named redsun.yaml that declares the available components.
Each entry maps a plugin ID directly to its "module:ClassName" class path:
# redsun.yaml
devices:
my_motor: "my_plugin.devices:MyMotor"
presenters:
my_controller: "my_plugin.presenters:MyController"
views:
my_ui: "my_plugin.views:MyView"
The manifest must be registered as a Python entry point in the package pyproject.toml:
Tip
This is inspired by the napari manifest.
Make sure that depending on the packaging system you use, the redsun.yaml is included in the built package otherwise your components will not be discoverable.
Configuration file format¶
The application configuration file references plugins by name and ID. A full example follows:
schema_version: 1.0
session: "My application"
frontend: "pyqt"
metadata:
user: Jacopo Abramo
location: Jena
setup: iSCAT
devices:
motor:
plugin_name: my-plugin
plugin_id: my_motor
axis:
- X
- Y
presenters:
controller:
plugin_name: my-plugin
plugin_id: my_controller
views:
ui:
plugin_name: my-plugin
plugin_id: my_ui
The top-level keys represent application level information:
schema_versionis the version value of the component system, kept for future compatibility;sessionis the name which will be assigned to the application for display;frontendis the UI toolkit used to load the correct subclass ofAppContainer;metadataare application-level metadata to add contextual informations.
The plugin_name and plugin_id keys are used for plugin resolution and are not passed to the component constructors. All other keys become keyword arguments for the component.
Protocol validation¶
Validation happens at two different moments, matching where the required information actually exists:
- Devices are checked at discovery time: the loaded class must subclass
ophyd_async.core.Device. This is a sound class-level check because the device layer is nominal. - Presenters and views pass a dual gate, each part checked where
the information exists:
- Constructor signature (class level, at discovery) - the leading
positional parameters must be exactly
(name, devices)for presenters and(name,)for views; further parameters must be keyword-assignable (the container callscls(*positionals, **config_kwargs)and has no control over the keywords). Plugins failing this gate are rejected before instantiation. - Protocol compliance (instance level, at build) - the constructed
instance must satisfy
PPresenter(exposingnameanddevices) orPView(exposingnameandview_position) structurally. Attributes assigned in__init__are only visible here; a non-compliant instance raises aTypeErrornaming the missing members.
- Constructor signature (class level, at discovery) - the leading
positional parameters must be exactly
Inheriting the Presenter /
View ABCs is optional - compliance is purely
structural (see
ADR 0003).
Built-in components¶
redsun itself ships a plugin manifest under the redsun entry point, so
built-in components resolve through the same discovery path as external
plugins - no special-casing in configuration files:
presenters:
storage:
plugin_name: redsun
plugin_id: storage
views:
storage:
plugin_name: redsun
plugin_id: storage
A manifest entry is imported only when a configuration names it, so a headless installation never imports the Qt views.
The available built-ins are documented in Presenters.
Inline vs. config-based registration¶
The plugin system is used when building from configuration files via
AppContainer.from_config().
When using the declarative class-based approach (defining a container subclass with
declare_device(), declare_presenter() or declare_view() field functions), component classes are
passed directly as the first argument to the respective function and do not go through plugin discovery. Build-time protocol validation applies identically to both paths.
Both approaches produce the same result: an
AppContainer with registered device,
presenter, and view components ready to be built.