Skip to content

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Dates are specified in the format DD-MM-YYYY.

0.11.0 01-08-2026

Added

  • slot (redsun.virtual) - marks a method as connectable, making its name and signature part of a component's public surface. Only a marked method can be connected to a signal. @slot(name=...) sets the port name used in configuration; @slot(thread=...) overrides the thread affinity declared by the class.
  • AppContainer.wire() - override to declare the connections of an application. It runs after register_providers and before inject_dependencies; component attributes resolve to their built instances while it runs, so a connection reads as self.connect(self.det_ctrl.sig_new_data, self.img_widget.update_layers).
  • AppContainer.connect() and VirtualContainer.connect() - connect a signal to a slot, applying the thread affinity of the slot or its class and recording the link. A slot that is not marked, or whose signature psygnal rejects, raises WiringError naming both ports.
  • VirtualContainer.connections and VirtualContainer.disconnect_all() - the recorded wiring graph and its teardown. AppContainer.shutdown() now disconnects everything it connected.
  • ports() and Ports (redsun.virtual) - the signals and slots a component exposes, by port name. SignalGroup members appear under the member name.
  • A wiring section in the configuration file, listing from / to port paths (component.port) for a session that has no container class to override. Applied after wire(), through the same connect.
  • VirtualContainer.connect_paths() - the string form of connect, used by that section. A malformed path, an unbuilt component, or an unknown port raises WiringError listing what does exist.
  • Connection and WiringError (redsun.virtual).
  • VirtualContainer.provide(), require() and try_require() - share an object under a typed key instead of a dynamic attribute. require raises KeyError when nothing bound the key; try_require returns None, which is how an optional collaborator is expressed.
  • ProviderKey (redsun.virtual) - the type of such a key, a dependency_injector.providers.Dependency[T]. instance_of= is enforced by provide, so a wrong value is blamed where it is supplied.
  • PATH_PROVIDER (redsun.storage) - the key for the session path provider owned by StoragePresenter.
  • VirtualContainer.subscribe() and VirtualContainer.subscriptions - observe an ophyd-async device signal from a marked slot. The reading is marshalled through psygnal, so thread behaves as it does for connect, and the subscription is released by disconnect_all(). Previously a component had to call subscribe_reading itself, from inside a coroutine, with no way to set a thread affinity and nothing tracking the release.
  • Subscription (redsun.virtual) - the record of one, rendered as source ~> consumer.port.
  • SlotThread (redsun.virtual) - the type of a thread affinity, so a component can annotate its __redsun_slot_thread__ declaration.
  • VirtualContainer.unconnected and Unconnected (redsun.virtual) - the ports of the built components that no connection or subscription reaches, as component.port paths. A misspelled port fails at build; a connection that was never written fails nowhere, and this is what finds it. Falsy when everything is reached, so a script can assert on it.
  • StorageView (redsun.view.qt.builtins) - a Qt widget showing and editing the base directory of the provider bound to PATH_PROVIDER. Without a StoragePresenter in the application it degrades to a read-only placeholder. Available from a configuration file as plugin_name: redsun, plugin_id: storage under views, which the shipped manifest now declares.

Changed

  • QtView declares __redsun_slot_thread__: ClassVar[SlotThread] = "main", so every slot on a Qt view is delivered on the main thread unless the slot or the connection says otherwise. Connections that passed thread="main" explicitly still work and are now redundant.

  • declare_device(), declare_presenter() and declare_view() return the class they are given instead of Any, so a component attribute is typed as its component. A connection in wire() that names a port the class does not have is now a type error, where before it was only a build failure. No container needs changing: existing declarations become checked as they are.

  • StoragePresenter binds its provider with container.provide(PATH_PROVIDER, ...). Breaking: the dynamic attribute it used to set is gone; read the provider with container.require(PATH_PROVIDER) instead of container.path_provider().
  • StoragePresenter exposes set_plan and reset_plan as slots instead of discovering sig_pre_launch_notify and sig_plan_done by name in inject_dependencies. Breaking: an application that relied on that discovery must now connect them, in wire() or in the wiring: section:
self.connect(self.acquisition.sig_pre_launch_notify, self.storage.set_plan)
self.connect(self.acquisition.sig_plan_done, self.storage.reset_plan)

Which signals announce a plan is the application's knowledge; the presenter no longer guesses it from a name, and a misspelled one now fails instead of silently connecting nothing.

find_signals and hand-written inject_dependencies are otherwise unaffected. - redsun.aio.set_async_backend() - installs CulsansAsyncioBackend as psygnal's active async backend, so coroutines connected to a signal are dispatched onto the shared event loop from any thread. Idempotent; raises if a different backend is already active. Tear it down with psygnal's clear_async_backend(). QtAppContainer calls it in build() and clears it in shutdown() (ADR 0005). - CulsansAsyncioBackend and AwaitableEvent (redsun.aio) - the backend itself and the resettable, awaitable event it reports running through. Exceptions raised by a dispatched slot are logged on the redsun logger instead of being discarded.

Changed (breaking)

  • get_shared_loop() is no longer re-exported from redsun.engine; import it from redsun.aio, where it is defined. It is the only piece of the async runtime intended for use outside the application container, alongside run_coro().

0.10.0 25-07-2026

Added

  • get_shared_loop() (redsun.engine) - returns the single asyncio event loop created at module import time.
  • AppContainer.connect_devices(mock=False) - connects all registered ophyd-async devices via their async connect lifecycle. Call after build(). Pass mock=True to skip hardware communication in tests.
  • FrameSink, StoreStateError, and the process-wide storage registry (register_storage, get_storage, reset_group, clear_registry).
  • culsans (>=0.11.0) as a runtime dependency.
  • redsun.presenter.builtins - built-in, reusable presenter components. First entry: StoragePresenter (ported from redsun-mimir's FileStoragePresenter), which owns the SessionPathProvider, exposes it on the virtual container as the path_provider DI provider, and wires plan names from sig_pre_launch_notify/sig_plan_done.
  • redsun.plugins entry point: redsun ships its own plugin manifest (plugins.yaml), so built-in components resolve from configuration files through the same discovery path as external plugins (plugin_name: redsun, plugin_id: storage).
  • BaseStorage.path_provider read-only property.
  • find_signals accepts an optional owner keyword to scope the lookup to one component's signal cache (ADR 0004).
  • SinkFactory, StorageIO, OpenStore, and PathSignals are exported from redsun.storage - the backend protocols are part of the public contract.
  • benchmarks/ - acquire-zarr dual-load benchmark (live view via bps.monitor + disk storage, two detectors, inline processing callback). Shipped in the sdist only, never in wheels, not collected by pytest.
  • Tutorial: writing a custom storage backend (StorageIO/OpenStore implementation driven through BaseStorage).

Changed (breaking)

  • redsun.storage rewritten per ADR 0002: BaseStorage.sink() returns a FrameSink (culsans-backed) usable from async device logics and sync document callbacks; open()/close() are explicit and idempotent.
  • Removed StorageStateMachine, StorageState, InvalidStoreState, and the FrameSender async-generator API. StoreStateError replaces InvalidStoreState.
  • Removed redsun.device.DeviceMap - ophyd-async now ships DeviceMap as a built-in; import it from ophyd_async.core instead (downstream consumers such as redsun-mimir should migrate on their next refactor).
  • Signal naming convention: sig_snake_case replaces sigCamelCase (ADR 0004). StoragePresenter wires sig_pre_launch_notify / sig_plan_done; DescriptorTreeView.sig_property_changed renamed.
  • Presenter/view protocols reworked for sound structural subtyping (ADR 0003): PPresenter.name/devices and PView.name are read-only property members; the Presenter/View ABCs no longer inherit the protocols; validation is a dual gate - constructor positional shape ((name, devices) / (name,)) checked via inspect at declaration/discovery, protocol compliance validated on built instances (raising TypeError) - replacing the class-level attribute screen; AppContainer.presenters and .views are typed dict[str, PPresenter] / dict[str, PView].

Changed

  • Custom device layer removed, redsun.device now re-exports ophyd-async primitives directly. Removed: PDevice, HasChildren, AttrR, AttrRW, AttrW, AttrT, SoftAttrR, SoftAttrRW, SoftAttrT, AcquisitionController, DataWriter, ControllableDataWriter, TriggerType, PrepareInfo. Use their ophyd-async equivalents (Device, StandardReadable, SignalR/RW/W/X, soft_signal_rw, soft_signal_r_and_setter, DetectorController, DetectorWriter, TriggerInfo, DetectorTrigger).
  • device(), presenter(), view() field specifiers renamed to declare_device(), declare_presenter(), declare_view() for clarity. Update all container subclasses and imports accordingly.
  • AppContainerMeta metaclass replaced with __init_subclass__ for container subclass registration.
  • Dropped beartype as a runtime dependency.
  • Updated CI tag pattern to support release candidates (e.g. v0.10.0rc0).
  • Re-enabled CI after the test-suite rewrite: the cross-platform test matrix and Codecov upload run again, and docs deployment / package build depend on green tests once more. CI mypy now uses the config-driven invocation (tests and benchmarks in scope) with QT_API pinning the Qt binding per matrix leg, and ruff checks the whole repository instead of src/redsun only.

Removed

  • Removed attrs from dev dependencies - drop support for it in favor of ophyd-async.
  • Removed unused utilities: redsun.utils.resolve_sync_or_async and redsun.utils.descriptors.make_key / make_descriptor / make_reading - descriptors and readings come from ophyd-async signal backends; the parse_key / parse_map_key helpers remain.

0.9.1 - 06-03-2026

  • Moved documentation dependencies to separate group
  • Added support for boolean dtype descriptor
  • Updated lockfile

0.9.0 - 27-02-2026

Added

  • Migrated code from redsun-mimir to here
  • In particular the whole plan specification and action system
  • Some things still require additional tests, although have been empirically tested in redsun-mimir
  • DeviceSequenceEdit: new ValueWidget subclass rendering Sequence[PDevice] and Set[PDevice] parameters as a checkbox list with a live selection count label.
  • PlanWidget.device_widgets: exposes device parameter widgets for external validation.
  • PlanWidget.params_widget: single QWidget wrapping the Devices and Parameters group boxes; disabled atomically during plan execution so all inputs lock without affecting run/stop/pause buttons.
  • Set[PDevice] / AbstractSet[PDevice] annotation support in plan spec: isdeviceset predicate and _handle_device_set handler; resolve_arguments coerces to set() for set-typed params.
  • HasWriter protocol expressing the ability of a device to encapsulate a writer.
  • SessionPathProvider with automatic run-number increment, replacing AutoIncrementFileProvider.
  • Metadata registry on Writer; metadata collected at prepare time is written immediately after stream open.
  • clear_sources mechanism for presenters to explicitly clear writer sources after a plan finishes.
  • group parameter on path providers for sub-group addressing within a Zarr store.

Changed

  • Storage layer migrated to per-device Writer instances identified by URI (singleton via get()).
  • Device preparation migrated from StorageInfo/StorageConfig dict-based API to PrepareInfo.
  • make_writer signature updated to (uri, mimetype).
  • Shareable plan-spec and widget infrastructure migrated from redsun-mimir into the SDK.
  • create_plan_widget now splits device and scalar parameters into separate "Devices" and "Parameters" group boxes.
  • Widget factory predicates now match on annotation shape rather than choices is not None; empty-choices case produces a valid empty widget instead of raising RuntimeError.
  • _try_factory_entry now only swallows predicate errors; factory crashes propagate immediately.
  • is_device_set removed from ParamDescription; set coercion derived from annotation directly via isdeviceset(p.annotation), symmetric with how isdevicesequence was already handled.

0.8.2 - 23-02-2026

Changed

  • Drop the Static and UUID filename providers in favor of AutoIncrement as default
  • Will be reintroduced at a later date when storage API is stabilized

Fixed

  • Fixed broken links in changelog
  • Store the suffix of a FilenameProvider or it gets lost
  • Convert URI to standard path for acquire-zarr backend

Added

  • Added some helper utilities for making descriptor/reading keys following canonical convention

0.8.0 - 22-02-2026

Changed

  • Migrated sunflare codebase to redsun. Sunflare will be archived.

0.7.2 - 22-02-2026

Changed

  • Merged SDK (formerly sunflare) into redsun
  • Migrated the HasStorage protocol to toolkit

Fixed

  • Fixed path lookup for storage

0.7.0 - 21-02-2026

Added

  • Added initial support for opt-in storage capacities for devices via descriptor protocol
  • Currently supporting only Zarr V3 format via acquire-zarr

0.6.1 - 20-02-2026

Fixed

  • Allow multiple widgets to be stacked in the center via QTabWidget for QtAppContainer
  • Fix the attribute look-up in loop construction to get the view_position attribute of PView

0.6.0 - 20-02-2026

Added

  • Added device(), presenter(), view() typed field specifiers for declarative component registration

Changed

  • IsProvider.register_providers() now runs over both presenters and views
  • IsInjectable.inject_dependencies() now runs over both presenters and views
  • Refactored build loop in component construction, provider registration and dependency injection
  • _ComponentBase: alias slot removed; name fully resolved at metaclass time
  • _PresenterComponent.build(): removed unused container: VirtualContainer parameter
  • All _*Component.build() methods use self.name directly
  • Changed plugin manifest format: from { class: "module:Type" } to flat "module:Type" string
  • Updated documentation

Removed

  • Removed component() catch-all field declarator in favor of layer-specific functions

0.5.6 - 18-02-2026

Fixed

  • AppContainer.build() now calls connect_to_virtual() on all VirtualAware view components after all components are fully constructed, symmetrically with the existing presenter loop. Previously, views were connected only via a QtMainView delegator called from QtAppContainer.run(), meaning the wiring was Qt-specific and bypassed the base build phase entirely.
  • Removed the now-redundant connect_to_virtual() delegator from QtMainView and the explicit call to it in QtAppContainer.run().
  • Fixed a spurious warning when a from_config key exists in the YAML but has no kwargs (bare key with null value, e.g. camera2: with nothing after it). Previously dict.get() returned None for both a missing key and a null value, making them indistinguishable. A sentinel is now used so only a genuinely absent key triggers the warning; a present-but-empty section is silently normalised to {}.

Added

  • redsun.qt public namespace exposing QtAppContainer for use in explicit, developer-written application configurations:
    from redsun.qt import QtAppContainer
    
  • Clarified documentation

0.5.4 - 18-02-2026

Fixed

  • Relaxed the component() overloads: all three (layer="device", layer="presenter", layer="view") now accept type instead of type[Device], type[Presenter], type[View]. This fixes mypy errors for classes built from protocol mixins that do not inherit from the sunflare base classes directly.

0.5.3 - 18-02-2026

Warning

This release was yanked from PyPI due to a broken distribution

Added

  • AppContainer and component are now importable directly from the top-level redsun package:
    from redsun import AppContainer, component
    

Changed

  • component() now takes the component class as its first positional argument:
    # Before
    motor: MyMotor = component(layer="device", axis=["X"])
    # After
    motor = component(MyMotor, layer="device", axis=["X"])
    
  • RedSunConfig removed from the public API; it is an internal TypedDict used only for YAML configuration validation.

0.5.0 - 17-02-2026

Changed

  • Fully refactor the package to go towards a containerization approach
  • Declare applications as containers, list relevant components as fields of a class
  • Provide support also for building from a configuration file as before
  • Upgrade to sunflare>=0.9.0
  • Move the FrontendTypes and ViewPositionTypes from sunflare to redsun
  • They're part of the overall configuration and should not concern the core package
  • Revamped documentation with more comprehensive information

0.4.0 - 15-12-2025

Changed

  • Apply a more strict check on imported plugins
  • Add support for 3.13 (simply declared on PyPI and tested via CI)
  • Upgrade to sunflare>=0.7.0

0.3.0 - 04-07-2025

Changed

  • Upgraded to sunflare>=0.6.1
  • Switch to uv
  • Drop support for Python 3.9

0.2.0 - 03-03-2025

Changed

  • Reworked the plugin system
  • The approach now loosely follows the napari manifest, where plugins are to be published via a yaml configuration file in the root folder of the plugin package, specifiying where the classes have to be imported.
  • The manifest is taken as the actual entry point of a plugin, which will be used to redirect to the actual imports which is executed via the standard library importlib.
  • Added additional coverage for the factory module.
  • Bumped sunflare version to sunflare>=0.5.0, which implements the above changes at toolkit level

0.1.0 - 22-02-2025

Added

  • Initial release on PyPI