spacr.qt.recipes¶
Settings templates — named bundles you can reuse and share.
The historical Recipe API, JSON format and storage paths remain compatible.
A lab does not run one set of settings; it runs a handful, each tied to a preparation. “Toxo PVM, 40×” is a real thing people say to each other, and until now the only way to carry it between sessions was a settings CSV in a folder somebody had to remember, with no name on it, no record of which module it belonged to, and no way to tell whether it was written by the version of spaCR about to consume it.
A recipe fixes all four:
it has a name the user chose;
it knows its module, so applying a Mask recipe to Measure is refused rather than silently writing seventeen keys that mean nothing there;
it records the spaCR version it was captured with, and applying it under a different one says so, listing the settings that no longer exist and the ones that have appeared since;
it is one file, so sharing it is sending a file.
Storage is ~/.spacr/recipes/<module>/<slug>.json, honouring
RECIPE_DIR_ENV — the same shape spacr.macro.macros_dir() uses,
so a lab that redirects one redirects both.
The file format is deliberately boring:
{
"spacr_recipe": 1,
"name": "Toxo PVM, 40x",
"app_key": "mask",
"spacr_version": "1.3.6",
"created": "2026-08-03T22:41:07",
"notes": "",
"settings": {"cell_channel": 1, ...}
}
spacr_recipe is a format version, not the spaCR version: the two change
for unrelated reasons and conflating them is how a reader ends up refusing a
file it could have read.
Classes¶
One named settings bundle. |
|
List, apply, share and delete the recipes for one module. |
Functions¶
|
Write a recipe's settings into |
|
Build a recipe from a screen's current settings. |
|
Which of the recipe's settings this build has no home for. |
|
Remove a recipe's file. |
|
Add a Templates button to |
|
Add Settings templates… to the window's Help menu. |
|
Wire recipes into a live main window. |
|
Every readable recipe, newest first. |
|
Read one recipe file. |
|
Open the recipe dialog for |
|
The folder holding recipes, optionally for one module. |
|
Write |
|
The running spaCR version, or |
|
What to tell the user about the version gap, or |
Module Contents¶
- class spacr.qt.recipes.Recipe[source]¶
One named settings bundle.
- Parameters:
name – display name chosen by the user; its slug becomes the file stem when the recipe is saved.
app_key – key of the module the settings belong to; it picks the recipe’s subfolder and is checked against the screen on apply.
settings – setting key to value mapping written into the screen.
spacr_version – spaCR version the recipe was saved with; filled in on save when empty.
created – ISO-8601 timestamp of creation, to the second; filled in on save when empty and used to sort listings newest first.
notes – free-text notes stored with the recipe.
path – file the recipe was loaded from or saved to; not written into the file itself.
- classmethod from_json(data: Dict[str, Any], path: str = '') Recipe[source]¶
Build a recipe from a parsed file.
- Parameters:
data – the parsed JSON mapping; it must carry an integer
spacr_recipeformat version no newer than this build reads and asettingsdict. Missingnamefalls back to"Untitled".- Raises:
ValueError – when the mapping is not a recipe at all, or is a format version this build does not understand. Both are worth an explicit error: silently treating an arbitrary JSON file as a settings bundle is how a user ends up applying somebody’s
package.jsonto a segmentation run.
- class spacr.qt.recipes.RecipeDialog(screen, parent: PySide6.QtWidgets.QWidget | None = None)[source]¶
Bases:
PySide6.QtWidgets.QDialogList, apply, share and delete the recipes for one module.
- Parameters:
screen – the module screen whose recipes these are. Its
app_keyis what the list is filtered by, and it becomes the dialog’s parent when none is given.parent – parent widget. Defaults to
screen.
Build the recipe dialog for one module screen.
- Parameters:
screen – the module screen whose settings recipes are saved and applied; its
app_keyscopes which recipes are listed.parent – parent widget; defaults to
screen.
- set_confirmation_runner(runner: Callable[[PySide6.QtWidgets.QMessageBox], Any]) None[source]¶
Replace how the version/compatibility confirmation is run.
The default enters the real modal Apply/Cancel loop. Tests inject a runner that inspects the fully configured message box and returns an answer without blocking; a host can use the same seam for a custom presentation.
- Parameters:
runner – callable given the configured Apply/Cancel
QMessageBox; the recipe is applied only when it returnsQMessageBox.Apply.
- spacr.qt.recipes.apply_recipe(recipe: Recipe, screen) int[source]¶
Write a recipe’s settings into
screen. Returns how many landed.Delegates to
AppScreen.apply_settings_dict, which is the same path the settings-CSV import takes — so a recipe cannot reach a widget that an imported CSV could not, and neither can drift from the other.- Raises:
ValueError – when the recipe belongs to a different module. It is refused rather than partially applied: the keys that happen to overlap between two modules are exactly the generic ones (
src,verbose,n_jobs), so a “successful” cross-module apply writes the least meaningful half and reports success.- Parameters:
recipe – the recipe to apply; its settings are copied, not mutated.
screen – the module screen to write into; its
app_keymust match the recipe’s (when both are set) and it must provideapply_settings_dict.
- spacr.qt.recipes.capture_recipe(screen, name: str, notes: str = '') Recipe[source]¶
Build a recipe from a screen’s current settings.
Uses
SettingsWidgets.collect, so what is captured is exactly what a Run would use — including the defaults the user never touched. That is deliberate: a recipe is meant to reproduce a result, and a bundle of only the edits reproduces something different the day a default changes.- Parameters:
screen – the module screen to capture; it must have a settings model (
_settings_model) orValueErroris raised, and itsapp_keyis stored on the recipe.name – display name for the recipe; empty gives
"Untitled".
- spacr.qt.recipes.compatibility_note(recipe: Recipe, model) str[source]¶
Which of the recipe’s settings this build has no home for.
The version gap says that something may have moved; this says what. Reported as counts plus the first few names, because a recipe from two releases back can differ in thirty keys and a wall of them is not a warning, it is noise.
- Parameters:
recipe – the bundle.
model – the screen’s
SettingsWidgets.
- spacr.qt.recipes.delete_recipe(recipe: Recipe) bool[source]¶
Remove a recipe’s file.
Truewhen something was removed.- Parameters:
recipe – the recipe whose
pathis deleted; an empty or missing path removes nothing.
- spacr.qt.recipes.install(screen) PySide6.QtWidgets.QToolButton | None[source]¶
Add a Templates button to
screen’s settings search strip.The strip is where a settings bundle belongs — directly above the settings it bundles — and it already exists, so this costs no chrome of its own. Returns
Nonewhen the screen has no strip (a bespoke screen), or when one is already installed.- Parameters:
screen – the module screen; its
_settings_searchstrip must provideadd_trailing_widget. If the screen already has a_recipe_button, that button is returned unchanged.
- spacr.qt.recipes.install_help_action(window: PySide6.QtWidgets.QMainWindow) PySide6.QtGui.QAction | None[source]¶
Add Settings templates… to the window’s Help menu.
Returns the action, or
Nonewhen there is no Help menu or one is already installed. The command palette mirrors menu actions, so this also makes recipes reachable from Ctrl+K for free.- Parameters:
window – the main window; its menu-bar menu titled “Help” (
&ignored) receives the action, before the first separator.
- spacr.qt.recipes.install_window_hooks(window: PySide6.QtWidgets.QMainWindow) _StackWatcher | None[source]¶
Wire recipes into a live main window.
Called once from
spacr.qt.shortcuts.install(). Every failure is logged and swallowed: a missing recipe button must not cost a window.- Parameters:
window – the main window; the Help-menu action is added to it and its
_stackscreen stack is watched so each shown screen gets a Templates button. Without a_stackthe result isNone.
- spacr.qt.recipes.list_recipes(app_key: str | None = None) List[Recipe][source]¶
Every readable recipe, newest first.
Unreadable files are logged and skipped rather than raising: one corrupt file must not make the whole list unopenable.
- Parameters:
app_key – restrict to one module.
- spacr.qt.recipes.load_recipe(path: str) Recipe[source]¶
Read one recipe file.
- Parameters:
path – path to a recipe JSON file; it is recorded on the returned recipe.
- Raises:
ValueError – on anything that is not a readable recipe.
- spacr.qt.recipes.open_recipes(screen, parent: PySide6.QtWidgets.QWidget | None = None) RecipeDialog[source]¶
Open the recipe dialog for
screen.- Parameters:
screen – the module screen whose recipes are listed and applied, passed to
RecipeDialog.
- spacr.qt.recipes.recipes_dir(app_key: str | None = None) str[source]¶
The folder holding recipes, optionally for one module.
~/.spacr/recipes(orRECIPE_DIR_ENV), with one subfolder per module so a listing is already scoped and a user can hand over a whole module’s worth by copying one directory. Created on first use.- Parameters:
app_key – restrict to one module’s folder.
- spacr.qt.recipes.save_recipe(recipe: Recipe, directory: str | None = None) str[source]¶
Write
recipeand return its path.- Parameters:
recipe – the bundle. Its
spacr_versionandcreatedare filled in here when empty, so a caller never has to remember to stamp them — the stamp is the point of the format.directory – override the destination (used by export).
- spacr.qt.recipes.spacr_version() str[source]¶
The running spaCR version, or
"unknown"if it cannot be read.
- spacr.qt.recipes.version_note(recipe: Recipe, current: str | None = None) str[source]¶
What to tell the user about the version gap, or
""if there is none.Returns a sentence, not a boolean, because “captured with 1.3.4, you are on 1.3.6” is the information — a bare warning icon says only that something might be wrong and leaves the user no way to judge it.
- Parameters:
recipe – the bundle about to be applied.
current – override the running version (tests).