exlab_wizard.template.resolution#
Read-side template resolver (generalizes Backend Spec §5.0).
The project / run wizards do not offer a single flat template list: a
template defined nearer the work (per-project, then per-equipment) should
override a same-named template defined further out (the global
paths.templates_dir). This module turns a config plus a wizard context
(template_type + optional equipment_id / project_path) into the
ordered, de-duplicated list of templates the wizard should offer, nearest
scope first, nearest scope winning on a name collision.
Directory layout searched (design spec §2):
Global –
paths.templates_dir(flat: templates sit directly inside, all types mixed, filtered by_exlab_type).Per-equipment –
<local_root>/<equipment_id>/.exlab-wizard/templates/<type>/(type-segregated).Per-project –
<project_path>/.exlab-wizard/templates/<type>/(type-segregated).
The per-instance directory scanning + manifest parsing is delegated to
exlab_wizard.ui.pages.templates.list_templates(); this module only
composes the search chain and merges the results.
Functions
|
Return the per-instance template directory for one scope. |
|
Return the absolute project directory, or |
|
Reconcile a prior template selection against freshly-resolved choices. |
|
Resolve the merged template list a wizard should offer. |
|
Return the template search directories, highest precedence first. |
Classes
|
The templates a wizard offers for one resolution context. |
- class exlab_wizard.template.resolution.TemplateChoices(names=<factory>, questions=<factory>, paths=<factory>)[source]#
Bases:
objectThe templates a wizard offers for one resolution context.
Bundles the three parallel views the wizard’s template + variables steps need so a single
on_resolve(equipment_id, project_name)call returns everything for the current selection:- names#
Offered template names, nearest scope first.
- questions#
Per-template parsed
copier.ymlquestions (drives the dynamic Variables step). Missing entry == no variables.
- paths#
Per-template absolute source path of the resolved template, so
on_submitrenders the exact file the wizard listed rather than re-derivingtemplates_dir / name(which would ignore a per-instance override). Keyed by template name.
- questions: dict[str, list[TemplateQuestion]]#
- exlab_wizard.template.resolution.instance_template_dir(instance_dir, template_type)[source]#
Return the per-instance template directory for one scope.
A per-instance (per-equipment / per-project) template store is
<instance_dir>/.exlab-wizard/templates/<template_type>/– the.exlab-wizardcache dir, atemplatessub-dir, then a child sub-dir per template type so the project / run stores never collide.
- exlab_wizard.template.resolution.project_dir(config, equipment_id, project_name)[source]#
Return the absolute project directory, or
Nonewhen not derivable.A run’s per-project template store lives under
<local_root>/<equipment_id>/<project_name>/(Backend Spec §3.2). The wizard knows the equipment id and the parent project’s folder name, so this composes the path the run-wizard resolver passes asproject_path. ReturnsNonewhenlocal_root,equipment_id, orproject_nameis missing – the caller then resolves without the per-project scope (per-equipment + global only).
- exlab_wizard.template.resolution.reconcile_selection(choices, selected_name)[source]#
Reconcile a prior template selection against freshly-resolved choices.
Called after a wizard re-resolves its template chain (the operator changed equipment / project). Returns
(name, path, dropped):The selected name survives the new context -> its resolved path is re-derived from
choicesand returned withdropped=False. This is the critical case: a same-named per-instance template shadows the global one at a different path, and a wizardui.selectthat keeps its value does not re-fire its change handler – so the stored path must be refreshed here or submit would render the stale source.The selected name is gone (or was
None) -> returns(None, None, True)so the caller clears the selection and its now-orphaned variables.
- Parameters:
choices (
TemplateChoices) – The freshly-resolved templates for the new context.selected_name (
str|None) – The template name selected under the old context.
- Return type:
- Returns:
(name, path, dropped)– the reconciled selection name, its re-derived absolute path (orNone), and whether the prior selection was dropped.
- exlab_wizard.template.resolution.resolve_template_chain(config, *, template_type, equipment_id=None, project_path=None, run_scope=None)[source]#
Resolve the merged template list a wizard should offer.
Walks
search_dirs()nearest-first, scanning each directory withlist_templates(), and merges the results by template name keeping the first (nearest-scope) occurrence – so a per-project template shadows a same-named per-equipment or global one. Nearest-first order is preserved in the result.template_typeis always passed tolist_templates(): the global dir is flat (mixed types) so the filter is required, and per-instance<type>/dirs pass it defensively so a misfiled template of the wrong type is skipped rather than offered.For run templates,
run_scopeadditionally narrows by the template’s declared scope: when given ("experimental"/"test") a run template is kept only when itsrun_scopeequals that scope or is"both".run_scope=Nonekeeps every run template. The parameter is ignored for non-run template types.- Parameters:
config (
Config) – The loaded config.template_type (
str) – One of"project"/"run"/"equipment".equipment_id (
str|None) – The equipment the wizard runs under, if any.project_path (
Path|None) – The absolute project directory, if any (run wizard).run_scope (
str|None) – Optional run-scope filter (run templates only).
- Return type:
- Returns:
The merged, de-duplicated list of templates, nearest scope first.
- exlab_wizard.template.resolution.search_dirs(config, *, template_type, equipment_id=None, project_path=None)[source]#
Return the template search directories, highest precedence first.
The precedence chain narrows from the work outwards: a per-project store beats a per-equipment store, which beats the global
paths.templates_dir. Which scopes apply depends on the template type the wizard is offering:"run"– per-project, then per-equipment, then global."project"– per-equipment, then global (a project has no children of its own to scope project templates by)."equipment"– global only.
A scope is included only when its backing config value is present: the global dir is skipped when
paths.templates_diris empty, the per-equipment dir whenequipment_idisNone(orlocal_rootis empty), and the per-project dir whenproject_pathisNone. Returned directories need not exist –list_templates()tolerates a missing directory by returning no templates.- Parameters:
config (
Config) – The loaded config (suppliespaths.templates_dirandpaths.local_root).template_type (
str) – One of"project"/"run"/"equipment".equipment_id (
str|None) – The equipment the wizard runs under, if any. Gates the per-equipment scope.project_path (
Path|None) – The absolute project directory, if any. Gates the per-project scope (run wizard only).
- Return type:
- Returns:
The ordered list of search directories, nearest scope first.