fix(manifest): require tomli on Py3.10 and fail loud when missing

Declare tomli with an environment marker and raise ImportError instead of returning None, so missing parsers no longer look like empty manifests.

Fixes #3283

(cherry picked from commit c06ba2731c)
This commit is contained in:
dr mike authored and safishamsi committed 2026-09-05 01:19:09 +01:00
1 parent 74e1424a84
commit d424a7ccfa
3 files changed
+92 -14

No files matched your search

+27 -14
View File
@@ -36,6 +36,31 @@ PACKAGE_MANIFEST_NAMES: dict[str, str] = {
_MAX_MANIFEST_BYTES = 2_000_000 # 2 MB cap — manifests are small; this rejects junk
_TOMLI_REQUIRED = (
"Package-manifest ingestion on Python < 3.11 needs tomli. "
"Install with: pip install 'tomli' "
"(or reinstall graphifyy, which declares tomli for python_version < '3.11')."
)
def _load_toml_module():
"""Return a tomllib-compatible module, or raise ImportError (#3283).
Returning ``None`` used to look identical to a virtual workspace root with
nothing to emit, so missing ``tomli`` on Python 3.10 silently dropped every
``Cargo.toml`` / ``pyproject.toml``. Fail loud the way ``cargo_introspect``
already does.
"""
try:
import tomllib as _toml # type: ignore[import-not-found]
return _toml
except ImportError:
try:
import tomli as _toml # type: ignore[import-not-found,no-redef]
return _toml
except ImportError as exc:
raise ImportError(_TOMLI_REQUIRED) from exc
def is_package_manifest_path(path: Path) -> bool:
"""True if ``path`` is a recognized package manifest (by filename)."""
@@ -182,13 +207,7 @@ def _pep508_name(spec: str) -> str:
def _parse_pyproject(text: str) -> dict | None:
try:
import tomllib as _toml
except ImportError:
try:
import tomli as _toml # type: ignore
except ImportError:
return None
_toml = _load_toml_module()
data = _toml.loads(text)
proj = data.get("project", {}) if isinstance(data.get("project"), dict) else {}
poetry = (data.get("tool", {}) or {}).get("poetry", {}) if isinstance(data.get("tool"), dict) else {}
@@ -207,13 +226,7 @@ def _parse_cargo(text: str) -> dict | None:
"""Cargo.toml: name/version from ``[package]``, runtime deps from
``[dependencies]`` plus every ``[target.<cfg>.dependencies]`` table (mirrors
``_parse_pyproject``'s runtime-only scope; dev-/build-dependencies excluded)."""
try:
import tomllib as _toml
except ImportError: # pragma: no cover — Python < 3.11 without tomli only
try:
import tomli as _toml # type: ignore
except ImportError:
return None
_toml = _load_toml_module()
data = _toml.loads(text)
pkg = data.get("package", {}) if isinstance(data.get("package"), dict) else {}
name = pkg.get("name")
+1
View File
@@ -15,6 +15,7 @@ dependencies = [
"networkx>=3.4",
"numpy>=1.21",
"rapidfuzz>=3.0",
"tomli>=2.0.1; python_version < '3.11'",
"tree-sitter>=0.23.0,<0.26",
"tree-sitter-python>=0.23,<0.26",
"tree-sitter-javascript>=0.23,<0.26",
+64
View File
@@ -0,0 +1,64 @@
"""Regression tests for missing tomli on Python < 3.11 (#3283)."""
from __future__ import annotations
import builtins
import sys
from pathlib import Path
import pytest
from graphify.manifest_ingest import (
_TOMLI_REQUIRED,
_load_toml_module,
_parse_cargo,
_parse_pyproject,
extract_package_manifest,
)
def test_load_toml_module_raises_when_tomli_missing(monkeypatch):
real_import = builtins.__import__
def fake_import(name, *args, **kwargs):
if name in ("tomllib", "tomli"):
raise ImportError(f"blocked {name}")
return real_import(name, *args, **kwargs)
monkeypatch.setattr(builtins, "__import__", fake_import)
with pytest.raises(ImportError, match="tomli"):
_load_toml_module()
def test_parse_pyproject_surfaces_missing_parser(monkeypatch):
monkeypatch.setattr(
"graphify.manifest_ingest._load_toml_module",
lambda: (_ for _ in ()).throw(ImportError(_TOMLI_REQUIRED)),
)
with pytest.raises(ImportError, match="tomli"):
_parse_pyproject('[project]\nname = "x"\n')
def test_parse_cargo_surfaces_missing_parser(monkeypatch):
monkeypatch.setattr(
"graphify.manifest_ingest._load_toml_module",
lambda: (_ for _ in ()).throw(ImportError(_TOMLI_REQUIRED)),
)
with pytest.raises(ImportError, match="tomli"):
_parse_cargo('[package]\nname = "x"\nversion = "0.1.0"\n')
def test_extract_package_manifest_reports_missing_tomli(tmp_path, monkeypatch):
p = tmp_path / "pyproject.toml"
p.write_text('[project]\nname = "cool"\nversion = "0.1"\n', encoding="utf-8")
monkeypatch.setattr(
"graphify.manifest_ingest._load_toml_module",
lambda: (_ for _ in ()).throw(ImportError(_TOMLI_REQUIRED)),
)
result = extract_package_manifest(p)
assert result["nodes"] == []
assert "tomli" in result.get("error", "").lower()
@pytest.mark.skipif(sys.version_info >= (3, 11), reason="stdlib tomllib present")
def test_tomli_is_importable_on_py310():
import tomli # noqa: F401