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 afterregister_providersand beforeinject_dependencies; component attributes resolve to their built instances while it runs, so a connection reads asself.connect(self.det_ctrl.sig_new_data, self.img_widget.update_layers).AppContainer.connect()andVirtualContainer.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, raisesWiringErrornaming both ports.VirtualContainer.connectionsandVirtualContainer.disconnect_all()- the recorded wiring graph and its teardown.AppContainer.shutdown()now disconnects everything it connected.ports()andPorts(redsun.virtual) - the signals and slots a component exposes, by port name.SignalGroupmembers appear under the member name.- A
wiringsection in the configuration file, listingfrom/toport paths (component.port) for a session that has no container class to override. Applied afterwire(), through the sameconnect. VirtualContainer.connect_paths()- the string form ofconnect, used by that section. A malformed path, an unbuilt component, or an unknown port raisesWiringErrorlisting what does exist.ConnectionandWiringError(redsun.virtual).VirtualContainer.provide(),require()andtry_require()- share an object under a typed key instead of a dynamic attribute.requireraisesKeyErrorwhen nothing bound the key;try_requirereturnsNone, which is how an optional collaborator is expressed.ProviderKey(redsun.virtual) - the type of such a key, adependency_injector.providers.Dependency[T].instance_of=is enforced byprovide, so a wrong value is blamed where it is supplied.PATH_PROVIDER(redsun.storage) - the key for the session path provider owned byStoragePresenter.VirtualContainer.subscribe()andVirtualContainer.subscriptions- observe an ophyd-async device signal from a marked slot. The reading is marshalled through psygnal, sothreadbehaves as it does forconnect, and the subscription is released bydisconnect_all(). Previously a component had to callsubscribe_readingitself, 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 assource ~> consumer.port.SlotThread(redsun.virtual) - the type of a thread affinity, so a component can annotate its__redsun_slot_thread__declaration.VirtualContainer.unconnectedandUnconnected(redsun.virtual) - the ports of the built components that no connection or subscription reaches, ascomponent.portpaths. 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 toPATH_PROVIDER. Without aStoragePresenterin the application it degrades to a read-only placeholder. Available from a configuration file asplugin_name: redsun,plugin_id: storageunderviews, which the shipped manifest now declares.
Changed¶
-
QtViewdeclares__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 passedthread="main"explicitly still work and are now redundant. -
declare_device(),declare_presenter()anddeclare_view()return the class they are given instead ofAny, so a component attribute is typed as its component. A connection inwire()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. StoragePresenterbinds its provider withcontainer.provide(PATH_PROVIDER, ...). Breaking: the dynamic attribute it used to set is gone; read the provider withcontainer.require(PATH_PROVIDER)instead ofcontainer.path_provider().StoragePresenterexposesset_planandreset_planas slots instead of discoveringsig_pre_launch_notifyandsig_plan_doneby name ininject_dependencies. Breaking: an application that relied on that discovery must now connect them, inwire()or in thewiring: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 fromredsun.engine; import it fromredsun.aio, where it is defined. It is the only piece of the async runtime intended for use outside the application container, alongsiderun_coro().
0.10.0 25-07-2026¶
Added¶
get_shared_loop()(redsun.engine) - returns the singleasyncioevent loop created at module import time.AppContainer.connect_devices(mock=False)- connects all registered ophyd-async devices via their async connect lifecycle. Call afterbuild(). Passmock=Trueto 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'sFileStoragePresenter), which owns theSessionPathProvider, exposes it on the virtual container as thepath_providerDI provider, and wires plan names fromsig_pre_launch_notify/sig_plan_done.redsun.pluginsentry 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_providerread-only property.find_signalsaccepts an optionalownerkeyword to scope the lookup to one component's signal cache (ADR 0004).SinkFactory,StorageIO,OpenStore, andPathSignalsare exported fromredsun.storage- the backend protocols are part of the public contract.benchmarks/- acquire-zarr dual-load benchmark (live view viabps.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/OpenStoreimplementation driven throughBaseStorage).
Changed (breaking)¶
redsun.storagerewritten per ADR 0002:BaseStorage.sink()returns aFrameSink(culsans-backed) usable from async device logics and sync document callbacks;open()/close()are explicit and idempotent.- Removed
StorageStateMachine,StorageState,InvalidStoreState, and theFrameSenderasync-generator API.StoreStateErrorreplacesInvalidStoreState. - Removed
redsun.device.DeviceMap- ophyd-async now shipsDeviceMapas a built-in; import it fromophyd_async.coreinstead (downstream consumers such as redsun-mimir should migrate on their next refactor). - Signal naming convention:
sig_snake_casereplacessigCamelCase(ADR 0004).StoragePresenterwiressig_pre_launch_notify/sig_plan_done;DescriptorTreeView.sig_property_changedrenamed. - Presenter/view protocols reworked for sound structural subtyping (ADR
0003):
PPresenter.name/devicesandPView.nameare read-only property members; thePresenter/ViewABCs no longer inherit the protocols; validation is a dual gate - constructor positional shape ((name, devices)/(name,)) checked viainspectat declaration/discovery, protocol compliance validated on built instances (raisingTypeError) - replacing the class-level attribute screen;AppContainer.presentersand.viewsare typeddict[str, PPresenter]/dict[str, PView].
Changed¶
- Custom device layer removed,
redsun.devicenow 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 todeclare_device(),declare_presenter(),declare_view()for clarity. Update all container subclasses and imports accordingly.AppContainerMetametaclass replaced with__init_subclass__for container subclass registration.- Dropped
beartypeas 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_APIpinning the Qt binding per matrix leg, and ruff checks the whole repository instead ofsrc/redsunonly.
Removed¶
- Removed
attrsfrom dev dependencies - drop support for it in favor ofophyd-async. - Removed unused utilities:
redsun.utils.resolve_sync_or_asyncandredsun.utils.descriptors.make_key/make_descriptor/make_reading- descriptors and readings come from ophyd-async signal backends; theparse_key/parse_map_keyhelpers remain.
0.9.1 - 06-03-2026¶
- Moved documentation dependencies to separate group
- Added support for
booleandtype descriptor - Updated lockfile
0.9.0 - 27-02-2026¶
Added¶
- Migrated code from
redsun-mimirto 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: newValueWidgetsubclass renderingSequence[PDevice]andSet[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: singleQWidgetwrapping 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:isdevicesetpredicate and_handle_device_sethandler;resolve_argumentscoerces toset()for set-typed params.HasWriterprotocol expressing the ability of a device to encapsulate a writer.SessionPathProviderwith automatic run-number increment, replacingAutoIncrementFileProvider.- Metadata registry on
Writer; metadata collected atpreparetime is written immediately after stream open. clear_sourcesmechanism for presenters to explicitly clear writer sources after a plan finishes.groupparameter on path providers for sub-group addressing within a Zarr store.
Changed¶
- Storage layer migrated to per-device
Writerinstances identified by URI (singleton viaget()). - Device preparation migrated from
StorageInfo/StorageConfigdict-based API toPrepareInfo. make_writersignature updated to(uri, mimetype).- Shareable plan-spec and widget infrastructure migrated from redsun-mimir into the SDK.
create_plan_widgetnow 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 raisingRuntimeError. _try_factory_entrynow only swallows predicate errors; factory crashes propagate immediately.is_device_setremoved fromParamDescription; set coercion derived from annotation directly viaisdeviceset(p.annotation), symmetric with howisdevicesequencewas already handled.
0.8.2 - 23-02-2026¶
Changed¶
- Drop the
StaticandUUIDfilename providers in favor ofAutoIncrementas default - Will be reintroduced at a later date when storage API is stabilized
Fixed¶
- Fixed broken links in changelog
- Store the suffix of a
FilenameProvideror it gets lost - Convert URI to standard path for
acquire-zarrbackend
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
QTabWidgetforQtAppContainer - Fix the attribute look-up in loop construction to get the
view_positionattribute ofPView
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 viewsIsInjectable.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 callsconnect_to_virtual()on allVirtualAwareview components after all components are fully constructed, symmetrically with the existing presenter loop. Previously, views were connected only via aQtMainViewdelegator called fromQtAppContainer.run(), meaning the wiring was Qt-specific and bypassed the base build phase entirely.- Removed the now-redundant
connect_to_virtual()delegator fromQtMainViewand the explicit call to it inQtAppContainer.run(). - Fixed a spurious warning when a
from_configkey exists in the YAML but has no kwargs (bare key with null value, e.g.camera2:with nothing after it). Previouslydict.get()returnedNonefor 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.qtpublic namespace exposingQtAppContainerfor use in explicit, developer-written application configurations:- Clarified documentation
0.5.4 - 18-02-2026¶
Fixed¶
- Relaxed the
component()overloads: all three (layer="device",layer="presenter",layer="view") now accepttypeinstead oftype[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¶
AppContainerandcomponentare now importable directly from the top-levelredsunpackage:
Changed¶
component()now takes the component class as its first positional argument:RedSunConfigremoved from the public API; it is an internalTypedDictused 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
FrontendTypesandViewPositionTypesfromsunflaretoredsun - 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
naparimanifest, where plugins are to be published via ayamlconfiguration 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
factorymodule. - 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