Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/specify_cli/workflows/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1009,14 +1009,27 @@ def workflow_catalog_list():
def workflow_catalog_add(
url: str = typer.Argument(..., help="Catalog URL to add"),
name: str | None = typer.Option(None, "--name", help="Catalog name"),
priority: int | None = typer.Option(None, "--priority", help="Priority (lower = higher priority)"),
install_allowed: bool = typer.Option(
True,
"--install-allowed/--no-install-allowed",
help="Allow workflows from this catalog to be installed",
),
description: str = typer.Option("", "--description", help="Description of the catalog"),
):
"""Add a workflow catalog source."""
from .catalog import WorkflowCatalog, WorkflowValidationError

project_root = _require_specify_project()
catalog = WorkflowCatalog(project_root)
try:
catalog.add_catalog(url, name)
catalog.add_catalog(
url,
name,
priority=priority,
install_allowed=install_allowed,
description=description,
)
except WorkflowValidationError as exc:
Comment on lines 1019 to 1033

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this call to pass priority, install_allowed, and description by keyword.

console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(1)
Expand Down Expand Up @@ -1661,6 +1674,13 @@ def workflow_step_catalog_list():
def workflow_step_catalog_add(
url: str = typer.Argument(..., help="Catalog URL to add"),
name: str | None = typer.Option(None, "--name", help="Catalog name"),
priority: int | None = typer.Option(None, "--priority", help="Priority (lower = higher priority)"),
install_allowed: bool = typer.Option(
True,
"--install-allowed/--no-install-allowed",
help="Allow steps from this catalog to be installed",
),
description: str = typer.Option("", "--description", help="Description of the catalog"),
):
"""Add a step catalog source."""
from .catalog import StepCatalog, StepValidationError
Expand All @@ -1669,7 +1689,13 @@ def workflow_step_catalog_add(

catalog = StepCatalog(project_root)
try:
catalog.add_catalog(url, name)
catalog.add_catalog(
url,
name,
priority=priority,
install_allowed=install_allowed,
description=description,
)
except StepValidationError as exc:
Comment on lines 1684 to 1699

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this call to pass priority, install_allowed, and description by keyword.

console.print(f"[red]Error:[/red] {exc}")
raise typer.Exit(1)
Expand Down
134 changes: 87 additions & 47 deletions src/specify_cli/workflows/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ class WorkflowCatalogEntry:
description: str = ""


def _normalize_catalog_priority(
value: Any,
*,
catalog_name: str | int,
error_cls: type[Exception],
) -> int:
"""Normalize a catalog priority to int and reject bool explicitly."""
if isinstance(value, bool):
raise error_cls(
f"Invalid priority for catalog '{catalog_name}': "
f"expected integer, got {value!r}"
)
try:
return int(value)
except (TypeError, ValueError) as exc:
raise error_cls(
f"Invalid priority for catalog '{catalog_name}': "
f"expected integer, got {value!r}"
) from exc


# ---------------------------------------------------------------------------
# WorkflowRegistry
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -210,14 +231,11 @@ def _load_catalog_config(
if not url:
continue
self._validate_catalog_url(url)
try:
priority = int(item.get("priority", idx + 1))
except (TypeError, ValueError):
raise WorkflowValidationError(
f"Invalid priority for catalog "
f"'{item.get('name', idx + 1)}': "
f"expected integer, got {item.get('priority')!r}"
)
priority = _normalize_catalog_priority(
item.get("priority", idx + 1),
catalog_name=item.get("name", idx + 1),
error_cls=WorkflowValidationError,
)
raw_install = item.get("install_allowed", False)
if isinstance(raw_install, str):
install_allowed = raw_install.strip().lower() in (
Expand Down Expand Up @@ -476,7 +494,14 @@ def get_catalog_configs(self) -> list[dict[str, Any]]:
for e in entries
]

def add_catalog(self, url: str, name: str | None = None) -> None:
def add_catalog(
self,
url: str,
name: str | None = None,
priority: int | str | None = None,
install_allowed: bool = True,
description: str = "",
) -> None:
Comment on lines +497 to +504
"""Add a catalog source to the project-level config."""
self._validate_catalog_url(url)
config_path = self.project_root / ".specify" / "workflow-catalogs.yml"
Expand Down Expand Up @@ -509,30 +534,35 @@ def add_catalog(self, url: str, name: str | None = None) -> None:
f"Catalog URL already configured: {url}"
)

# Derive priority from the highest existing priority + 1.
# Coerce existing priorities to int with a safe fallback so a user-edited
# workflow-catalogs.yml with a non-integer priority (e.g. "1") doesn't blow up.
def _coerce_priority(value: Any) -> int:
try:
return int(value)
except (TypeError, ValueError):
return 0

max_priority = max(
(
_coerce_priority(cat.get("priority", 0))
for cat in catalogs
_normalize_catalog_priority(
cat["priority"],
catalog_name=cat.get("name", idx + 1),
error_cls=WorkflowValidationError,
)
for idx, cat in enumerate(catalogs)
if isinstance(cat, dict)
if "priority" in cat
),
default=0,
)
catalog_name = name or f"catalog-{len(catalogs) + 1}"
catalogs.append(
{
"name": name or f"catalog-{len(catalogs) + 1}",
"name": catalog_name,
"url": url,
"priority": max_priority + 1,
"install_allowed": True,
"description": "",
"priority": (
max_priority + 1
if priority is None
else _normalize_catalog_priority(
priority,
catalog_name=catalog_name,
error_cls=WorkflowValidationError,
)
),
"install_allowed": install_allowed,
"description": description,
}
)
data["catalogs"] = catalogs
Expand Down Expand Up @@ -825,14 +855,11 @@ def _load_catalog_config(
if not url:
continue
self._validate_catalog_url(url)
try:
priority = int(item.get("priority", idx + 1))
except (TypeError, ValueError):
raise StepValidationError(
f"Invalid priority for catalog "
f"'{item.get('name', idx + 1)}': "
f"expected integer, got {item.get('priority')!r}"
)
priority = _normalize_catalog_priority(
item.get("priority", idx + 1),
catalog_name=item.get("name", idx + 1),
error_cls=StepValidationError,
)
raw_install = item.get("install_allowed", False)
if isinstance(raw_install, str):
install_allowed = raw_install.strip().lower() in (
Expand Down Expand Up @@ -1086,7 +1113,14 @@ def get_catalog_configs(self) -> list[dict[str, Any]]:
for e in entries
]

def add_catalog(self, url: str, name: str | None = None) -> None:
def add_catalog(
self,
url: str,
name: str | None = None,
priority: int | str | None = None,
install_allowed: bool = True,
description: str = "",
) -> None:
Comment on lines +1116 to +1123
"""Add a catalog source to the project-level config."""
self._validate_catalog_url(url)
config_path = self.project_root / ".specify" / "step-catalogs.yml"
Expand Down Expand Up @@ -1116,29 +1150,35 @@ def add_catalog(self, url: str, name: str | None = None) -> None:
f"Catalog URL already configured: {url}"
)

# Coerce existing priorities to int with a safe fallback so a user-edited
# step-catalogs.yml with a non-integer priority (e.g. "1") doesn't blow up.
def _coerce_priority(value: Any) -> int:
try:
return int(value)
except (TypeError, ValueError):
return 0

max_priority = max(
(
_coerce_priority(cat.get("priority", 0))
for cat in catalogs
_normalize_catalog_priority(
cat["priority"],
catalog_name=cat.get("name", idx + 1),
error_cls=StepValidationError,
)
for idx, cat in enumerate(catalogs)
if isinstance(cat, dict)
if "priority" in cat
),
default=0,
)
catalog_name = name or f"catalog-{len(catalogs) + 1}"
catalogs.append(
{
"name": name or f"catalog-{len(catalogs) + 1}",
"name": catalog_name,
"url": url,
"priority": max_priority + 1,
"install_allowed": True,
"description": "",
"priority": (
max_priority + 1
if priority is None
else _normalize_catalog_priority(
priority,
catalog_name=catalog_name,
error_cls=StepValidationError,
)
),
"install_allowed": install_allowed,
"description": description,
}
)
data["catalogs"] = catalogs
Expand Down
Loading