Skip to content

Commit 65092d4

Browse files
committed
Address eighth review: fan-out namespaced IDs, early return, catalog validation
- Fan-out per-item step IDs use _fanout_{step_id}_{base}_{idx} namespace to avoid collisions with user-defined step IDs - Early return after fan-out loop when state is paused/failed/aborted - Catalog installs parse + validate downloaded YAML before registering, using definition metadata instead of catalog entry for registry
1 parent 704f62c commit 65092d4

2 files changed

Lines changed: 30 additions & 5 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4469,10 +4469,29 @@ def _validate_and_install_local(yaml_path: Path, source_label: str) -> None:
44694469
console.print(f"[red]Error:[/red] Failed to install workflow '{source}' from catalog: {exc}")
44704470
raise typer.Exit(1)
44714471

4472+
# Validate the downloaded workflow before registering
4473+
try:
4474+
definition = WorkflowDefinition.from_yaml(workflow_file)
4475+
except (ValueError, yaml.YAMLError) as exc:
4476+
import shutil
4477+
shutil.rmtree(workflow_dir, ignore_errors=True)
4478+
console.print(f"[red]Error:[/red] Downloaded workflow is invalid: {exc}")
4479+
raise typer.Exit(1)
4480+
4481+
from .workflows.engine import validate_workflow
4482+
errors = validate_workflow(definition)
4483+
if errors:
4484+
import shutil
4485+
shutil.rmtree(workflow_dir, ignore_errors=True)
4486+
console.print("[red]Error:[/red] Downloaded workflow validation failed:")
4487+
for err in errors:
4488+
console.print(f" \u2022 {err}")
4489+
raise typer.Exit(1)
4490+
44724491
registry.add(source, {
4473-
"name": info.get("name", source),
4474-
"version": info.get("version", "0.0.0"),
4475-
"description": info.get("description", ""),
4492+
"name": definition.name or info.get("name", source),
4493+
"version": definition.version or info.get("version", "0.0.0"),
4494+
"description": definition.description or info.get("description", ""),
44764495
"source": "catalog",
44774496
"catalog_name": info.get("_catalog_name", ""),
44784497
"url": workflow_url,

src/specify_cli/workflows/engine.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,10 +610,10 @@ def _execute_steps(
610610
fan_out_results = []
611611
for item_idx, item_val in enumerate(result.output["items"]):
612612
context.item = item_val
613-
# Create a per-item copy with a unique ID
613+
# Create a per-item copy with an ID in a reserved namespace
614614
item_step = dict(template)
615615
base_id = item_step.get("id", "item")
616-
item_step["id"] = f"{base_id}-{item_idx}"
616+
item_step["id"] = f"_fanout_{step_id}_{base_id}_{item_idx}"
617617
self._execute_steps(
618618
[item_step], context, state, registry,
619619
step_offset=-1,
@@ -635,6 +635,12 @@ def _execute_steps(
635635
fan_out_output["results"] = fan_out_results
636636
context.steps[step_id]["output"] = fan_out_output
637637
state.step_results[step_id]["output"] = fan_out_output
638+
if state.status in (
639+
RunStatus.PAUSED,
640+
RunStatus.FAILED,
641+
RunStatus.ABORTED,
642+
):
643+
return
638644

639645
def _resolve_inputs(
640646
self,

0 commit comments

Comments
 (0)