Component system¶
redsun's component system lets other packages provide devices, presenters and views, discovered and loaded at runtime.
Overview¶
Components ship in ordinary Python packages that register through entry points. Building an application from a YAML configuration file, redsun reads those entry points to find the installed plugins and load the components the file asks for.
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 to learn which devices, presenters and views are needed.
- Queries entry points for installed packages in the
redsun.pluginsgroup. - Loads manifests: each plugin's YAML manifest maps plugin IDs to Python classes.
- Validates components: a device class must subclass
ophyd_async.core.Device; presenter and view instances are checked againstPPresenter/PViewwhen the container builds (see Protocol validation). - Creates the container class from the discovered components.
Component manifest¶
A component package includes a YAML manifest, redsun.yaml, declaring its components.
Each entry maps a plugin ID to a "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"
Register the manifest as a Python entry point in the package's pyproject.toml:
Tip
The design follows the napari manifest.
Check that your packaging tool includes redsun.yaml in the built package, or the components cannot be discovered.
Configuration file format¶
An application configuration file names plugins by name and ID:
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 describe the application:
schema_versionis the component system's version, kept for compatibility;sessionis the application's display name;frontendis the UI toolkit, which picks theAppContainersubclass;metadataholds application-level context.
plugin_name and plugin_id resolve the plugin and are not passed to the constructor. Every other key becomes a keyword argument of the component.
Protocol validation¶
Each check runs where its information exists:
- Devices are checked at discovery: the class must subclass
ophyd_async.core.Device. A class check is enough, because devices conform by inheritance. - Presenters and views are checked twice:
- Constructor signature, at discovery. The leading positional parameters
must be exactly
(name, devices)for presenters and(name,)for views, and any further parameter must accept a keyword: the container callscls(*positionals, **config_kwargs). A plugin failing this is rejected before it is instantiated. - Protocol, at build. The instance must satisfy
PPresenter(nameanddevices) orPView(nameandview_position) by shape. Attributes assigned in__init__exist only now; an instance that does not conform raises aTypeErrornaming the missing members.
- Constructor signature, at discovery. The leading positional parameters
must be exactly
Inheriting the Presenter or
View ABC is optional, since conformance is by shape (see
ADR 0003).
Built-in components¶
redsun ships its own manifest under the redsun entry point, so a
configuration file names built-in components the same way as any plugin's:
A manifest entry is imported only when a configuration names it, so a headless installation never imports the Qt views.
The logs view is described in
Configure logging.
Inline vs. config-based registration¶
Plugin discovery only runs when building from a configuration file with
AppContainer.from_config().
A container subclass declared with
declare_device(),
declare_presenter() or
declare_view() passes the
classes directly and skips discovery. The build checks both the same way.
Either way the result is an
AppContainer with its devices,
presenters and views declared and ready to build.