mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
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:
1 parent
74e1424a84
commit
d424a7ccfa
3 files changed
+92
-14
No files matched your search
+27
-14
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user