Skip to content

Commit 0dee2fa

Browse files
fix(catalogs): reject boolean priority in extension and preset catalog readers (#2589)
`bool` is a subclass of `int` in Python, so `int(True)` silently returns `1`. The extension- and preset-catalog config readers coerced priority with a bare `int(item.get("priority", idx + 1))`, which meant a YAML config like: catalogs: - name: mine url: https://example.com/catalog.json priority: yes # parses to True was silently accepted as a valid priority of 1, quietly reordering the catalog stack instead of raising the same `Invalid priority` error a typo of `priority: not-a-number` already raises. The sibling integration-catalog reader in `src/specify_cli/catalogs.py` already guards this case (see `catalogs.py:137`). This change mirrors that pattern in `extensions.py` and `presets.py` so the three catalog validators stay consistent, and adds regression tests for both readers matching the existing `test_load_catalog_config_rejects_boolean_priority` template in `tests/integrations/test_integration_catalog.py`.
1 parent 7fda89d commit 0dee2fa

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

src/specify_cli/presets.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,12 +1903,24 @@ def _load_catalog_config(self, config_path: Path) -> Optional[List[PresetCatalog
19031903
if not url:
19041904
continue
19051905
self._validate_catalog_url(url)
1906+
raw_priority = item.get("priority", idx + 1)
1907+
# Reject bools explicitly: ``bool`` is a subclass of ``int`` so
1908+
# ``int(True)`` silently returns 1, which would let a YAML
1909+
# ``priority: true`` slip through as a valid priority of 1. The
1910+
# sibling integration-catalog reader in ``catalogs.py`` already
1911+
# guards this; mirror the check here so the three catalog
1912+
# validators stay consistent.
1913+
if isinstance(raw_priority, bool):
1914+
raise PresetValidationError(
1915+
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
1916+
f"expected integer, got {raw_priority!r}"
1917+
)
19061918
try:
1907-
priority = int(item.get("priority", idx + 1))
1919+
priority = int(raw_priority)
19081920
except (TypeError, ValueError):
19091921
raise PresetValidationError(
19101922
f"Invalid priority for catalog '{item.get('name', idx + 1)}': "
1911-
f"expected integer, got {item.get('priority')!r}"
1923+
f"expected integer, got {raw_priority!r}"
19121924
)
19131925
raw_install = item.get("install_allowed", False)
19141926
if isinstance(raw_install, str):

tests/test_presets.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,31 @@ def test_load_catalog_config_invalid_priority(self, project_dir):
18301830
with pytest.raises(PresetValidationError, match="Invalid priority"):
18311831
catalog._load_catalog_config(config_path)
18321832

1833+
def test_load_catalog_config_rejects_boolean_priority(self, project_dir):
1834+
"""A YAML ``priority: true`` is a typo, not a request for priority 1.
1835+
1836+
``bool`` is a subclass of ``int`` in Python, so ``int(True)`` silently
1837+
returns ``1``. Without an explicit guard a malformed config like
1838+
``priority: yes`` would be accepted as a valid priority of 1 and
1839+
silently change catalog ordering. The sibling integration-catalog
1840+
reader rejects this case (see ``catalogs.py``); the preset catalog
1841+
reader must stay consistent.
1842+
"""
1843+
config_path = project_dir / ".specify" / "preset-catalogs.yml"
1844+
config_path.write_text(yaml.dump({
1845+
"catalogs": [
1846+
{
1847+
"name": "bool-priority",
1848+
"url": "https://example.com/catalog.json",
1849+
"priority": True,
1850+
}
1851+
]
1852+
}))
1853+
1854+
catalog = PresetCatalog(project_dir)
1855+
with pytest.raises(PresetValidationError, match="Invalid priority|expected integer"):
1856+
catalog._load_catalog_config(config_path)
1857+
18331858
def test_load_catalog_config_install_allowed_string(self, project_dir):
18341859
"""Test that install_allowed accepts string values."""
18351860
config_path = project_dir / ".specify" / "preset-catalogs.yml"

0 commit comments

Comments
 (0)