spacr.qt.chaining

The chaining strip: where a module says what it will read, and what is stale.

Three things a pipeline screen has never been able to say, all of them one strip above the Run button — because that is the moment the user is about to act, and a panel they would have to go and open is a panel nobody opens:

  • what it will read. spacr.chaining asks the artifact registry where the previous module actually wrote and fills this module’s source folder with it, so opening Measure after Mask no longer means retyping the plate. A path the user typed themselves is pinned and never overwritten — when the upstream later moves, the new location is offered beside the pinned one with a button, never pushed into the field;

  • what is out of date. spacr.chaining.staleness_notes() turns the registry’s cause codes into a sentence and a fix, so “these measurements came from a Mask run you have since redone” is on screen before the figure is opened rather than discovered afterwards;

  • what comes next. A finished run offers its successors from spacr.ports.next_modules(), each pre-filled with the artifact just produced and each checked with spacr.ports.check_ready() — so an offer either works, or is shown greyed with the reason it cannot.

Installation goes through the two seams that already exist rather than through the shared screen: spacr.qt.app.APP_FACTORIES (consulted by MainWindow._build_screen before its built-in chain) and spacr.qt.SELF_REGISTERING_MODULES, which imports this module after app.py is loaded and before the first window is built. AppScreen itself is untouched.

register() is the whole of the installation and is idempotent, so a build whose launch list has not yet learned about this module can call it from anywhere that runs before the first window.

Classes

ChainingBar

The strip above a module's Run button.

Functions

chained_app_keys(→ Tuple[str, ...])

Return the app keys that get a chaining strip, sorted.

chaining_bar(→ Optional[ChainingBar])

Return the strip installed on screen, or None.

install_chaining(→ Optional[ChainingBar])

Put a ChainingBar above screen's Run row.

register(→ bool)

Install the chaining strip on every ported module screen.

screen_for_module(→ str)

Resolve a module key to the GUI screen that currently hosts it.

unregister(→ int)

Undo register(). Returns how many factories were removed.

Module Contents

class spacr.qt.chaining.ChainingBar(screen: PySide6.QtWidgets.QWidget, *, pins=None, parent=None)[source]

Bases: PySide6.QtWidgets.QFrame

The strip above a module’s Run button.

Four rows, each hidden when it has nothing to say, so a project with no recorded runs sees exactly what it saw before this existed:

  1. where the inputs came from;

  2. a pinned path whose upstream has since moved, with a button to take the new one;

  3. what is stale, why (by cause), and what to do;

  4. after a finished run, what to do next.

Parameters:
  • screen – the spacr.qt.screens.app_screen.AppScreen this belongs to.

  • pins – the pin store to use. Defaults to the shared one; tests hand in their own so the developer’s real pins are never touched.

  • parent – parent widget; ownership only.

Build the strip that carries settings forward from the previous module.

What this strip wrote into a widget is remembered per key, which is what separates “the user chose this” from “we put it there”.

Parameters:
  • screen – the module screen the strip sits on.

  • pins – the pin store to read and write; None uses the shared one.

  • parent – parent widget; defaults to screen.

__init__(screen: PySide6.QtWidgets.QWidget, *, pins=None, parent=None) → None[source]

Build the strip that carries settings forward from the previous module.

What this strip wrote into a widget is remembered per key, which is what separates “the user chose this” from “we put it there”.

Parameters:
  • screen – the module screen the strip sits on.

  • pins – the pin store to read and write; None uses the shared one.

  • parent – parent widget; defaults to screen.

adopt(values: Dict[str, Any]) → int[source]

Apply values as though this strip had chained them itself.

Used by the Continue button on the previous module’s strip: the seed it hands over is an artifact the registry resolved, not a path the user typed, so it must not become a pin.

Parameters:

values – settings key → value.

Returns:

how many keys the screen accepted.

current_settings() → Dict[str, Any][source]

Return the screen’s settings, or {} when they will not collect.

collect raises on a half-filled form, which is the normal state of a screen the user is still working on — and the strip has to keep working there, because that is exactly when it is useful.

host_window()[source]

Return the window that owns navigation, or None.

A method rather than an inline self._screen.window() so a test can stand a window in without reaching into Qt’s ownership chain, and so a screen shown outside a MainWindow has no navigation host.

refresh(*, finished: bool = False) → None[source]

Re-read the registry and redraw every row.

Never raises: the strip is an aid, and an aid that can take a module screen down with it is worse than no aid.

Parameters:

finished – a run just finished successfully, so offer the next step as well.

search_roots() → Tuple[str, ...][source]

Return the projects to look in, in preference order.

This module’s own folder first, then the folders the modules upstream of it last ran in. The second is what makes a blank Measure screen find the plate Mask just finished: the registry lives in the project root, so without a candidate root there is no registry to ask.

stale_notes() → Tuple[spacr.chaining.StaleNote, ...][source]

Return the staleness the strip would show right now.

property held: Dict[str, spacr.chaining.HeldPin][source]

The settings keys a pin is holding, keyed by setting.

property steps: Tuple[spacr.chaining.NextStep, ...][source]

The successors currently offered.

spacr.qt.chaining.chained_app_keys() → Tuple[str, ...][source]

Return the app keys that get a chaining strip, sorted.

Every module that both declares ports and is a registered app: the graph decides, not a list kept in step by hand.

spacr.qt.chaining.chaining_bar(screen) → ChainingBar | None[source]

Return the strip installed on screen, or None.

Parameters:

screen – the screen widget whose _chaining_bar attribute is read.

spacr.qt.chaining.install_chaining(screen, *, pins=None) → ChainingBar | None[source]

Put a ChainingBar above screen’s Run row.

The strip goes into the runtime panel immediately above the actions row, which is the last thing the eye crosses on its way to Run. Both anchors (_runtime_wrap and _actions_row) are attributes AppScreen keeps for exactly this kind of reach, so nothing here depends on the panel’s internal layout order.

Parameters:
  • screen – an AppScreen.

  • pins – a pin store, for tests.

Returns:

the strip, or None when this screen cannot carry one — a module with no declared ports, or a screen that failed to build its panels. Never raises: a screen that opens without the strip is the old behaviour, and that is always better than a screen that does not open.

spacr.qt.chaining.register() → bool[source]

Install the chaining strip on every ported module screen.

Idempotent, and called by spacr.qt.register_self_registering_modules() after app.py has finished importing and before the first window is built.

Returns:

True when anything was registered.

spacr.qt.chaining.screen_for_module(app_key: str) → str[source]

Resolve a module key to the GUI screen that currently hosts it.

Modules consolidated into another screen retain their pipeline keys for command-line, saved-run, and chaining compatibility. Their keys are resolved through _SUCCEEDED_BY; keys without a successor mapping are returned unchanged.

Parameters:

app_key – Pipeline module key supplied by a record or signal.

Returns:

GUI screen key that presents the module.

spacr.qt.chaining.unregister() → int[source]

Undo register(). Returns how many factories were removed.

Nested helpers

ChainingBar.__init__._let_go(*_args) → None

Drop the probe connection while this wrapper still works.

Connected to destroyed rather than done in a destructor: by the time Python collects the wrapper the C++ object is gone and disconnect raises out of whatever happened to emit it.

Parameters:

_args – whatever destroyed sends; unused.

spacr/qt/chaining.py:265

ChainingBar.__init__._root_answered(_path: str, _answer: bool) → None

Redraw once a probe has an answer for a root.

Both arguments are ignored: the strip re-reads every root it cares about, so WHICH path answered does not change what has to be done. RuntimeError means the strip has been destroyed and the signal outlived it, which is not an error worth raising.

Parameters:
  • _path – the path that was probed; unused.

  • _answer – what the probe found; unused.

spacr/qt/chaining.py:247

ChainingBar._refresh.done(payload)

Paint what the worker resolved, back on the GUI thread.

Clears _resolving FIRST, so a request that arrived while this one was in flight – held in _resolve_again – can start immediately rather than being refused by a flag this callback has not got round to clearing yet.

Parameters:

payload – the worker’s (resolution, notes), or None when it produced nothing to draw.

spacr/qt/chaining.py:676

ChainingBar._refresh.work()

Off the GUI thread. Touches no widget – returns data only.

spacr/qt/chaining.py:667