From d3eb743570a3356fc5e4e0d2614f8496026814c7 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Tue, 14 Jul 2026 22:37:16 +0500 Subject: [PATCH 1/2] fix(catalogs): priority: .inf yields a clean validation error, not OverflowError _load_catalog_config coerces a catalog entry's priority with int() inside except (TypeError, ValueError). int(float('inf')) raises OverflowError, which is not in that tuple, so a YAML 'priority: .inf' escaped as an uncaught traceback instead of the intended 'expected integer' validation error (the bool-is-int case is already guarded just above). Add OverflowError to the except tuple. Test mirrors the existing rejects_boolean_priority test with priority: .inf (fails before: OverflowError; passes after: ValidationError naming the config). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/specify_cli/catalogs.py | 5 ++++- tests/test_extensions.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/catalogs.py b/src/specify_cli/catalogs.py index e4df8eae28..774aaa51d7 100644 --- a/src/specify_cli/catalogs.py +++ b/src/specify_cli/catalogs.py @@ -149,7 +149,10 @@ def _load_catalog_config(self, config_path: Path) -> list[CatalogEntry] | None: ) try: priority = int(raw_priority) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): + # OverflowError: int(float("inf")) — a YAML ``priority: .inf`` + # would otherwise escape as an uncaught traceback instead of the + # clean validation error. raise self._validation_error( f"Invalid catalog config {config_path}: " f"Invalid priority for catalog '{item.get('name', idx + 1)}': " diff --git a/tests/test_extensions.py b/tests/test_extensions.py index d2f4af8264..652540d1be 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -4542,6 +4542,36 @@ def test_load_catalog_config_rejects_boolean_priority(self, temp_dir): catalog.get_active_catalogs() assert str(config_path) in str(exc_info.value) + def test_load_catalog_config_rejects_infinite_priority(self, temp_dir): + """A ``priority: .inf`` yields a clean validation error, not an uncaught + OverflowError from int(float('inf')).""" + import yaml as yaml_module + + project_dir = self._make_project(temp_dir) + config_path = project_dir / ".specify" / "extension-catalogs.yml" + config_path.write_text( + yaml_module.dump( + { + "catalogs": [ + { + "name": "inf-priority", + "url": "https://example.com/catalog.json", + "priority": float("inf"), + } + ] + } + ), + encoding="utf-8", + ) + + catalog = ExtensionCatalog(project_dir) + + with pytest.raises( + ValidationError, match="Invalid priority|expected integer" + ) as exc_info: + catalog.get_active_catalogs() + assert str(config_path) in str(exc_info.value) + def test_load_catalog_config_defaults_blank_names(self, temp_dir): """Blank and null names normalize by valid catalog order.""" import yaml as yaml_module From ded1421a0f9a3adab23e57d112b4d6ca50e4a2ea Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Fri, 17 Jul 2026 15:35:59 +0500 Subject: [PATCH 2/2] fix(presets): priority: .inf in a preset catalog config yields a clean error The PresetCatalog._load_catalog_config priority parser has its own loader (separate from CatalogStackBase) that caught only TypeError/ValueError, so a YAML 'priority: .inf' escaped as an uncaught OverflowError from int(float('inf')). Add OverflowError to the except tuple (the bool-is-int case is already guarded just above), matching catalogs.py. Test mirrors rejects_boolean_priority with priority: .inf (fails before: OverflowError; passes after: PresetValidationError). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/specify_cli/presets/__init__.py | 5 ++++- tests/test_presets.py | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/presets/__init__.py b/src/specify_cli/presets/__init__.py index 98bba08fd4..14ffcdb165 100644 --- a/src/specify_cli/presets/__init__.py +++ b/src/specify_cli/presets/__init__.py @@ -2231,7 +2231,10 @@ def _load_catalog_config(self, config_path: Path) -> Optional[List[PresetCatalog ) try: priority = int(raw_priority) - except (TypeError, ValueError): + except (TypeError, ValueError, OverflowError): + # OverflowError: int(float("inf")) — a YAML ``priority: .inf`` + # would otherwise escape as an uncaught traceback instead of the + # clean validation error (mirrors catalogs.py). raise PresetValidationError( f"Invalid priority for catalog '{item.get('name', idx + 1)}': " f"expected integer, got {raw_priority!r}" diff --git a/tests/test_presets.py b/tests/test_presets.py index 797835a88c..4368bb0231 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -2659,6 +2659,24 @@ def test_load_catalog_config_rejects_boolean_priority(self, project_dir): with pytest.raises(PresetValidationError, match="Invalid priority|expected integer"): catalog._load_catalog_config(config_path) + def test_load_catalog_config_rejects_infinite_priority(self, project_dir): + """A ``priority: .inf`` yields a clean validation error, not an uncaught + OverflowError from int(float('inf')).""" + config_path = project_dir / ".specify" / "preset-catalogs.yml" + config_path.write_text(yaml.dump({ + "catalogs": [ + { + "name": "inf-priority", + "url": "https://example.com/catalog.json", + "priority": float("inf"), + } + ] + })) + + catalog = PresetCatalog(project_dir) + with pytest.raises(PresetValidationError, match="Invalid priority|expected integer"): + catalog._load_catalog_config(config_path) + def test_load_catalog_config_install_allowed_string(self, project_dir): """Test that install_allowed accepts string values.""" config_path = project_dir / ".specify" / "preset-catalogs.yml"