Skip to content

Commit 136919b

Browse files
authored
Enforce the published runtime dependency contract (#8)
Require Tomli 2.4.1 for Python 3.10 in package metadata and add a parsed PEP 508 equality test that prevents the runtime audit manifest from drifting from published dependencies. The protected CodeQL, container, distribution, evidence-quality, and six-platform Python matrix passed.
1 parent da6ad56 commit 136919b

2 files changed

Lines changed: 34 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ requires-python = ">=3.10"
1313
dependencies = [
1414
"httpx>=0.27,<1",
1515
"rich>=13.7,<16",
16-
"tomli>=2.0; python_version < '3.11'",
16+
"tomli>=2.4.1; python_version < '3.11'",
1717
]
1818

1919
authors = [
@@ -47,6 +47,7 @@ dev = [
4747
"hypothesis>=6.100",
4848
"jsonschema>=4.22",
4949
"mypy>=1.11",
50+
"packaging>=24.0",
5051
"pip-audit>=2.7",
5152
"pytest>=8.0",
5253
"pytest-cov>=5.0",

tests/test_dependency_contract.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from pathlib import Path
2+
3+
from packaging.requirements import Requirement
4+
from packaging.utils import canonicalize_name
5+
6+
try:
7+
import tomllib
8+
except ModuleNotFoundError: # pragma: no cover - Python 3.10 CI exercises this branch.
9+
import tomli as tomllib
10+
11+
12+
def _requirements_by_name(values: list[str]) -> dict[str, Requirement]:
13+
requirements: dict[str, Requirement] = {}
14+
for value in values:
15+
requirement = Requirement(value)
16+
name = canonicalize_name(requirement.name)
17+
assert name not in requirements, f"duplicate dependency declaration: {name}"
18+
requirements[name] = requirement
19+
return requirements
20+
21+
22+
def test_runtime_audit_manifest_matches_published_dependencies():
23+
project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
24+
published = _requirements_by_name(project["project"]["dependencies"])
25+
audit_lines = [
26+
line.strip()
27+
for line in Path("requirements-audit.txt").read_text(encoding="utf-8").splitlines()
28+
if line.strip() and not line.lstrip().startswith("#")
29+
]
30+
audited = _requirements_by_name(audit_lines)
31+
32+
assert audited == published

0 commit comments

Comments
 (0)