feat(client): initialize procurement tool skeleton
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""验证 wheel 元数据从 requirements.txt 声明了全部运行时依赖。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from email import policy
|
||||
from email.parser import BytesParser
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
|
||||
def normalize_project_name(name: str) -> str:
|
||||
"""使用足以比较 requirements 与 Core Metadata 的项目名规范化规则。"""
|
||||
|
||||
return re.sub(r"[-_.]+", "-", name).lower()
|
||||
|
||||
|
||||
def dependencies_from_requirements(requirements_file: Path) -> set[str]:
|
||||
"""从唯一依赖来源读取项目名;当前 requirements 不允许间接或可编辑依赖。"""
|
||||
|
||||
dependencies: set[str] = set()
|
||||
for line in requirements_file.read_text(encoding="utf-8").splitlines():
|
||||
requirement = line.partition("#")[0].strip()
|
||||
if not requirement:
|
||||
continue
|
||||
match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*", requirement)
|
||||
if match is None:
|
||||
raise ValueError(f"requirements.txt 包含不支持的依赖声明:{requirement}")
|
||||
dependencies.add(normalize_project_name(match.group()))
|
||||
return dependencies
|
||||
|
||||
|
||||
def dependencies_from_wheel(wheel_file: Path) -> set[str]:
|
||||
"""读取 wheel 的 Core Metadata 中声明的 Requires-Dist 项目名。"""
|
||||
|
||||
with zipfile.ZipFile(wheel_file) as wheel:
|
||||
metadata_members = [name for name in wheel.namelist() if name.endswith(".dist-info/METADATA")]
|
||||
if len(metadata_members) != 1:
|
||||
raise ValueError("wheel 中必须恰有一个 .dist-info/METADATA 文件")
|
||||
metadata = BytesParser(policy=policy.default).parsebytes(wheel.read(metadata_members[0]))
|
||||
|
||||
dependencies = set()
|
||||
for requirement in metadata.get_all("Requires-Dist", []):
|
||||
match = re.match(r"[A-Za-z0-9][A-Za-z0-9._-]*", requirement)
|
||||
if match is None:
|
||||
raise ValueError(f"wheel METADATA 包含无效的 Requires-Dist:{requirement}")
|
||||
dependencies.add(normalize_project_name(match.group()))
|
||||
return dependencies
|
||||
|
||||
|
||||
def verify_wheel_metadata(wheel_file: Path, requirements_file: Path) -> set[str]:
|
||||
"""返回没有被 wheel 元数据声明的 requirements 项目名。"""
|
||||
|
||||
return dependencies_from_requirements(requirements_file) - dependencies_from_wheel(wheel_file)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="检查 wheel 是否包含 requirements.txt 的依赖元数据")
|
||||
parser.add_argument("wheel", type=Path, help="待检查的 wheel 文件")
|
||||
parser.add_argument(
|
||||
"--requirements",
|
||||
type=Path,
|
||||
default=Path(__file__).resolve().parents[1] / "requirements.txt",
|
||||
help="唯一依赖来源 requirements.txt 的路径",
|
||||
)
|
||||
arguments = parser.parse_args(argv)
|
||||
|
||||
missing = verify_wheel_metadata(arguments.wheel, arguments.requirements)
|
||||
if missing:
|
||||
print(f"wheel METADATA 缺少依赖:{', '.join(sorted(missing))}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("wheel METADATA 已声明 requirements.txt 中的全部依赖。")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user