diff --git a/CHANGELOG.md b/CHANGELOG.md index 34bbd0cf..4d54d77f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -568,6 +568,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- When `cel-python` or `tree-sitter-language-pack` fails to import, the + error now gives the real cause. Both guards said "not installed" for any + import error, which was wrong for an installed package with a missing + dependency or a broken release (#573). - Installs without the development dependencies work again (`uv tool install` from a checkout, and any install from a built wheel). Two things broke them: `tree-sitter-language-pack` 1.6.3, the newest release the old diff --git a/packages/darnit-baseline/src/darnit_baseline/threat_model/parsing.py b/packages/darnit-baseline/src/darnit_baseline/threat_model/parsing.py index 6a7a1440..8e15c1a9 100644 --- a/packages/darnit-baseline/src/darnit_baseline/threat_model/parsing.py +++ b/packages/darnit-baseline/src/darnit_baseline/threat_model/parsing.py @@ -29,15 +29,18 @@ from tree_sitter_language_pack import get_language, get_parser TREE_SITTER_AVAILABLE = True -except ImportError: # pragma: no cover — dependency is required in pyproject.toml +except ImportError as _import_error: # pragma: no cover — dependency is required in pyproject.toml TREE_SITTER_AVAILABLE = False ts = None # type: ignore[assignment] + # Keep the real cause. A release that installs without an importable module + # (tree-sitter-language-pack 1.6.3, #573) is not "not installed". + _IMPORT_FAILURE = f"tree-sitter-language-pack could not be imported: {_import_error}" def get_language(name: str) -> Any: # type: ignore[misc] - raise ImportError("tree-sitter-language-pack is not installed") + raise ImportError(_IMPORT_FAILURE) def get_parser(name: str) -> Any: # type: ignore[misc] - raise ImportError("tree-sitter-language-pack is not installed") + raise ImportError(_IMPORT_FAILURE) logger = logging.getLogger("darnit_baseline.threat_model.parsing") diff --git a/packages/darnit/src/darnit/sieve/cel_evaluator.py b/packages/darnit/src/darnit/sieve/cel_evaluator.py index c7eb2811..cf514480 100644 --- a/packages/darnit/src/darnit/sieve/cel_evaluator.py +++ b/packages/darnit/src/darnit/sieve/cel_evaluator.py @@ -179,7 +179,7 @@ def _get_environment(self) -> Any: from celpy.c7nlib import C7N_Interpreted_Runner except ImportError as e: raise CELCompilationError( - "cel-python not installed. Install with: pip install cel-python" + f"cel-python could not be imported: {e}" ) from e # Create environment with C7N runner that supports custom functions @@ -275,7 +275,7 @@ def compile(self, expression: str) -> CELProgram: import celpy except ImportError as e: raise CELCompilationError( - "cel-python not installed. Install with: pip install cel-python" + f"cel-python could not be imported: {e}" ) from e env = self._get_environment() @@ -344,7 +344,7 @@ def _evaluate_with_timeout( import celpy except ImportError as e: raise CELEvaluationError( - "cel-python not installed. Install with: pip install cel-python" + f"cel-python could not be imported: {e}" ) from e result_container: list[Any] = [] diff --git a/tests/darnit/sieve/test_cel_import_failure.py b/tests/darnit/sieve/test_cel_import_failure.py new file mode 100644 index 00000000..dd4fb7ee --- /dev/null +++ b/tests/darnit/sieve/test_cel_import_failure.py @@ -0,0 +1,27 @@ +"""An import failure inside cel-python reports its real cause, not "not installed".""" + +from __future__ import annotations + +import builtins + +import pytest + +from darnit.sieve.cel_evaluator import CELCompilationError, evaluate_cel + + +def test_cel_import_failure_names_the_real_cause(monkeypatch) -> None: + real_import = builtins.__import__ + + def failing_import(name, *args, **kwargs): + if name == "celpy" or name.startswith("celpy."): + raise ImportError("No module named 'packaging.version'") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", failing_import) + + with pytest.raises(CELCompilationError) as excinfo: + evaluate_cel("1 == 1", {}) + + message = str(excinfo.value) + assert "packaging.version" in message + assert "not installed" not in message