diff --git a/src/specify_cli/_assets.py b/src/specify_cli/_assets.py index a1f6151acc..acb7edd26a 100644 --- a/src/specify_cli/_assets.py +++ b/src/specify_cli/_assets.py @@ -121,7 +121,14 @@ def get_speckit_version() -> str: if pyproject_path.exists(): with open(pyproject_path, "rb") as f: data = tomllib.load(f) - return data.get("project", {}).get("version", "unknown") - except (OSError, ValueError, KeyError): + project = data.get("project") if isinstance(data, dict) else None + # A present but non-mapping ``project`` table must not turn + # into an AttributeError from the narrowing of this branch. + if isinstance(project, dict): + version = project.get("version") + if isinstance(version, str) and version: + return version + return "unknown" + except (OSError, KeyError, ValueError): pass return "unknown" diff --git a/tests/test_utils_assets_imports.py b/tests/test_utils_assets_imports.py index 388561bb3d..870d64a4b1 100644 --- a/tests/test_utils_assets_imports.py +++ b/tests/test_utils_assets_imports.py @@ -47,6 +47,24 @@ def _corrupt(name): assert isinstance(get_speckit_version(), str) +def test_get_speckit_version_survives_non_mapping_project(monkeypatch, tmp_path): + """A present but non-mapping ``project`` value must not raise. + + The narrowed pyproject branch previously called ``.get`` on whatever the + ``project`` key held, so ``project = 5`` turned the fallback into an + AttributeError. + """ + (tmp_path / "pyproject.toml").write_text("project = 5\n", encoding="utf-8") + monkeypatch.setattr(_assets, "_repo_root", lambda: tmp_path) + + def _not_found(name): + raise importlib.metadata.PackageNotFoundError(name) + + monkeypatch.setattr(importlib.metadata, "version", _not_found) + + assert get_speckit_version() == "unknown" + + def test_get_speckit_version_reads_pyproject_fallback(monkeypatch, tmp_path): """When the distribution is missing, a valid pyproject.toml is the source.""" (tmp_path / "pyproject.toml").write_text(