spacr.macro¶
The macro recorder: every run also writes the script that would repeat it.
A GUI run used to leave settings behind and nothing else. The settings are
the inputs, not the method — reading settings.json tells you what the
knobs were, never which function consumed them, in what order, or how the
second module found what the first one made. So the record of an analysis
was a folder of numbers plus whatever the analyst remembered.
This module closes that. Every run that opens a journal
(spacr.run_journal.open_run() — the one seam the Qt GUI, the Tk GUI
and the CLI all launch through) also emits macro.py: real imports, a
real settings dict, a real call. Run it and the same thing happens again.
It is three deliverables in one file, and the third is why the emitted script is also a data structure:
A reproducibility record. The script is the method section. Its header
carries the spaCR version, the run id and the settings hash, and those are
not decoration: the run id is the id spacr.runctx stamped on every
log line the run emitted and spacr.artifacts stamped on every output
it registered, so the script, the log and the files all join on one column.
The settings hash is spacr.artifacts.settings_hash(), the same digest
the artifact rows carry.
The on-ramp from clicking to the API. A user who has outgrown the GUI
opens macro.py and finds the code they would have written — not a
wrapper, not a replay harness, the actual two lines.
Most of the input to the methods-and-results exporter. Which is why
the script also carries MACRO, a plain dict literal holding every
step’s module, entry point, run id, settings, which of those settings were
merely defaults, what it produced and how long it took.
read_macro() reads it back without executing the script — it is
parsed, not imported — so the exporter can consume a macro it did not
generate and does not trust.
Four things the emitted script is careful about¶
Defaults are written out. A settings dict that omits cell_diameter
runs with whatever spaCR’s default is today. Pin the version and that
is reproducible; anything less is not. So every key of the module’s
defaults is emitted with its value, and MACRO records which keys
the user actually set (user_set) and which were filled in
(defaulted) — the exporter needs that distinction and the script must
not lose it.
A chain is one script. Mask, then Measure, then Classify on the same
project is one file with three steps in dependency order, not three files.
The edge is confirmed against spacr.ports.next_modules(), so the
order in the script is the order the pipeline contract declares.
Intermediate paths are threaded, not repeated. The project each step ran
on becomes a named constant, and every path underneath it is rebuilt from
that constant with os.path.join(). Repointing the whole chain at
another plate is one edit on one line.
Nothing here may fail a run. Recording is bracketed by
begin_recording() / finish_recording(), both of which swallow
everything. A macro that could not be written costs one log line.
Usage¶
from spacr.macro import current_macro, read_macro
macro = current_macro() # the chain recorded in this process
print(macro.source()) # the script
meta = read_macro(macro.path) # the same thing, as data
Public API¶
begin_recording,finish_recordingThe hook
spacr.run_journal.open_run()calls. Everything else is downstream of these two.Macro,MacroStep,current_macro,macros,resetThe recorded chains, in this process.
render,read_macro,macro_path,macros_dirRendering the script, and reading one back as data.
entry_for,module_defaults,explicit_settingsThe resolution steps, usable on their own — what function a module key runs, what its defaults are, and the fully-explicit settings dict.
Exceptions¶
A file is not a spaCR macro, or carries a schema this build cannot read. |
Classes¶
One chain of runs, and the script that repeats it. |
|
One recorded run: what ran, on what, under which id. |
|
One run being recorded. Created by |
Functions¶
|
Start recording a run. Half of the hook; never raises. |
|
The chain the next run would join, or None before anything has run. |
|
Return |
|
Return the settings a reproduction needs, with nothing left implicit. |
|
Finish a recording, append its step, write the script. Never raises. |
|
Return the macro script path inside a run journal folder. |
|
Every chain recorded in this process, oldest first. |
|
Return the folder holding one script per recorded chain. |
|
Return |
|
Return the |
|
Render a |
|
Forget every recorded chain. |
|
Return a one-block human summary of a record from |
|
Return a record from |
Module Contents¶
- exception spacr.macro.MacroError[source]¶
Bases:
ValueErrorA file is not a spaCR macro, or carries a schema this build cannot read.
Initialize self. See help(type(self)) for accurate signature.
- class spacr.macro.Macro[source]¶
One chain of runs, and the script that repeats it.
A chain grows while consecutive runs stay connected — the next module consumes what the previous one produced, on the same project. A run that connects to nothing starts a new
Macro, because a script that welds two unrelated plates together is not a reproduction of either.- Parameters:
macro_id – twelve hex characters, the same shape as a run id.
steps – the recorded steps, in the order they ran.
created_utc – when the chain started.
touched – wall-clock timestamp of the most recent step, used to decide when an idle chain must be closed.
- write(path: Any) str[source]¶
Write the script to
pathand return the path.- Parameters:
path – destination Python-script path.
Written to a neighbouring temporary file and renamed, so a reader that opens it while a later step is being appended never sees half a script.
- property path: str[source]¶
The stable script path for this chain, under
macros_dir().
- class spacr.macro.MacroStep[source]¶
One recorded run: what ran, on what, under which id.
- Parameters:
module – the module / app key —
"mask","measure".entry_module – the import path of the entry point.
entry_func – the function name.
settings – the fully explicit settings dict.
defaulted – keys filled in from the module defaults.
user_set – keys the caller actually supplied.
defaults_source – which source answered; see
module_defaults().run_id – the id the run stamped on its log lines and outputs.
run_ids – every run id observed during the run, in order. More than one means the pipeline opened nested runs.
run_id_source –
"runctx"when the id came from the run’s own log records,"journal"when it was taken from the journal folder because no run context opened.settings_hash –
spacr.artifacts.settings_hash()over the explicit settings — the digest the artifact rows carry.project_root – the project the step ran on.
run_dir – the journal folder.
status –
"success","failed","cancelled".started_utc – when the step opened.
finished_utc – when it closed.
elapsed_s – how long it took.
outputs – declared output locations that exist on disk.
link – how this step follows the previous one —
"ports"whenspacr.ports.next_modules()declares the edge,"project"when they merely share a project,""for the first step.coerced – settings keys whose value is not a Python literal and was rendered as its string form.
spacr_version – the version that ran it.
- class spacr.macro.Recording[source]¶
One run being recorded. Created by
begin_recording().- Parameters:
module – normalized application key used to resolve the recorded entry point, defaults, project root, outputs, and step module.
settings – copied launch settings used when
finish_recording()receives no override; copying protects them from pipeline mutation.run_dir – run-journal directory copied to the recorded step and used to recover a fallback run id when no log record exposes one.
started –
time.time()value captured at start and subtracted at finish to produce a nonnegative elapsed duration.started_utc – UTC start timestamp copied into the reproducibility step.
capture – optional root-log handler that collects run ids during the invocation; finishing removes and closes it before recording its ids.
- spacr.macro.begin_recording(module: str, settings: Mapping[str, Any] | None = None, *, run_dir: Any = '') Recording | None[source]¶
Start recording a run. Half of the hook; never raises.
- Parameters:
module – the module / app key the run was launched for.
settings – the settings it was launched with. Copied, because several pipelines mutate the dict they are given and the script must show what was asked for, not what the run left behind.
run_dir – the journal folder, when there is one.
- Returns:
the
Recordingto handfinish_recording(), or None when recording could not start — in which case finishing is a no-op and the run is entirely unaffected.
- spacr.macro.current_macro() Macro | None[source]¶
The chain the next run would join, or None before anything has run.
- spacr.macro.entry_for(module: str) Tuple[str, str][source]¶
Return
(import_path, function_name)for a module key.Three sources, in the order
spacr.qt.bridge.resolve_pipeline_entry()consults them, so the script calls what the Run button called:spacr.validate.APP_FUNCTIONS, the shipped table;register_app(..., entry="mod:func"), the registration seam;a plugin’s
entrypoint.
Resolved textually. Rendering a script must not import Cellpose, Torch and pandas to find out what a name is, and a recorder that imported the pipeline it was describing would turn a cheap write into a several-second stall at the end of every run.
- Parameters:
module – the module / app key.
- Returns:
the pair, or
("", "")when the key names an interactive-only app (Annotate, Make Masks) or nothing at all.
- spacr.macro.explicit_settings(module: str, settings: Mapping[str, Any] | None) Tuple[Dict[str, Any], Tuple[str, ...], str][source]¶
Return the settings a reproduction needs, with nothing left implicit.
The module’s defaults, then the run-control defaults (
spacr.runctx.apply_defaults()—random_seedabove all, since an unpinned seed is the one “default” that changes the numbers on its own), then the caller’s values on top. Keys are ordered defaults-first so a diff between two macros lines up.- Parameters:
module – the module / app key.
settings – what the run was launched with.
- Returns:
(settings, defaulted_keys, defaults_source)wheredefaulted_keysare the keys the caller did not set and the script is therefore pinning on their behalf.
- spacr.macro.finish_recording(recording: Recording | None, *, status: str = '', settings: Mapping[str, Any] | None = None) MacroStep | None[source]¶
Finish a recording, append its step, write the script. Never raises.
- Parameters:
recording – what
begin_recording()returned.Noneis a no-op, which is how a recorder that failed to start stays harmless.status – the run’s outcome, as the journal recorded it.
settings – override the settings to record.
- Returns:
the appended
MacroStep, or None when nothing was recorded.
- spacr.macro.macro_path(run_dir: Any) str[source]¶
Return the macro script path inside a run journal folder.
- Parameters:
run_dir – run journal directory that will contain the script.
Deliberately beside
manifest.jsonandsettings.json: the journal folder is already whatspacr-reproandspacr.notebook_export.export_run()are pointed at, so the script is found by anything that can already find the run.
- spacr.macro.macros_dir() str[source]¶
Return the folder holding one script per recorded chain.
~/.spacr/macros, honouringMACRO_DIR_ENV. Created on first use. This is the stable copy: a chain’s file is rewritten in place as each step joins it, so the path does not change while the chain grows. Every run also gets its own copy next to its manifest — seemacro_path().
- spacr.macro.module_defaults(module: str) Tuple[Dict[str, Any], str][source]¶
Return
(defaults, source)for a module key.The sources, in order, are the same ones a settings panel consults — reused rather than re-derived, so the script cannot disagree with the screen it came from:
"registered"spacr.settings.defaults_for(), theregister_defaultsseam."plugin"a plugin app’s
defaultsfactory."settings_model"spacr.qt.screens.settings_model.resolve_default_settings(), the built-in dispatch. Guarded: it imports Qt, which a headless run does not have, and a missing Qt must cost the script its default-filling, never the script."none"nothing answered. The settings the caller passed are still emitted in full; only the unset keys are missing, and
MACROsays so throughdefaults_source.
- Parameters:
module – the module / app key.
- Returns:
a fresh dict, and the name of the source that produced it.
- spacr.macro.read_macro(path: Any) Dict[str, Any][source]¶
Return the
MACROrecord of an emitted script.Parsed, never executed. The exporter’s input is a file spaCR may not have written — someone else’s macro, an edited one, one from a newer build — and importing it would run whatever is in it. So this walks the AST, evaluates the top-level literal assignments (which is how the
PROJECT_1and*_SETTINGSnames the metadata refers to are resolved) and returns the record.- Parameters:
path – the script.
- Returns:
the record, with each step’s
"settings"resolved to the real dict.- Raises:
MacroError – when the file has no
MACRO, or its schema is newer than this build understands.OSError – when the file cannot be read.
SyntaxError – when it is not Python at all.
- spacr.macro.render(macro: Macro) str[source]¶
Render a
Macroas a standalone, runnable Python script.- Parameters:
macro – the chain to render.
- Returns:
the source. Always parses:
tests/test_macro.pycompiles every script this function produces, because a recorder that emits plausible-looking code that does not run is worse than no recorder.
- spacr.macro.reset() None[source]¶
Forget every recorded chain.
The next run starts a fresh one. For a test, and for a caller that wants a chain boundary it chose rather than one inferred from the project layout.
- spacr.macro.summarise(record: Mapping[str, Any]) str[source]¶
Return a one-block human summary of a record from
read_macro().- Parameters:
record – decoded macro metadata record to summarize.
What a methods section starts from: the version, the chain, and per step the entry point, the run id and how many settings were the user’s rather than defaults.
- spacr.macro.to_json(record: Mapping[str, Any], **kwargs: Any) str[source]¶
Return a record from
read_macro()as JSON.- Parameters:
record – decoded macro metadata record to serialize.
For a consumer that would rather have JSON than a Python dict — the methods exporter prompt, a web view, a diff.
default=strso a value that survived as a coerced string does not take the dump down.