"""可信临时包扫描器;只解析 dist-info 元数据,不 import 用户代码。""" from __future__ import annotations import csv import email.parser import json from pathlib import Path BASE = Path("/sandbox/base-python-packages.json") ROOT = Path("/tmp/.local/lib") def safe_file(root: Path, path: Path) -> bool: try: path.relative_to(root) except ValueError: return False current = root for part in path.relative_to(root).parts: current = current / part if current.is_symlink(): return False return path.is_file() def safe_dir(root: Path, path: Path) -> bool: try: rel = path.relative_to(root) except ValueError: return False current = root for part in rel.parts: current = current / part if current.is_symlink(): return False return path.is_dir() def canonical(name: str) -> str: return name.lower().replace("_", "-").replace(".", "-") def scan_packages(root: Path, base: dict) -> list[dict]: out = [] if root.is_dir(): for site in sorted(root.glob("python*/site-packages")): if not safe_dir(root, site): continue for dist in sorted(site.glob("*.dist-info")): if len(out) >= 5000: return out meta_path = dist / "METADATA" if not safe_file(site, meta_path) or meta_path.stat().st_size > 1024 * 1024: continue msg = email.parser.Parser().parsestr(meta_path.read_text(encoding="utf-8", errors="replace"), headersonly=True) name, version = msg.get("Name", "").strip(), msg.get("Version", "").strip() if not name: continue total = 0 record = dist / "RECORD" if safe_file(site, record) and record.stat().st_size <= 32 * 1024 * 1024: for index, row in enumerate(csv.reader(record.read_text(encoding="utf-8", errors="replace").splitlines())): if index >= 200_000: break if not row: continue candidate = site / row[0] if safe_file(site, candidate): try: total += candidate.stat().st_size except OSError: pass key = canonical(name) old = base.get(key) change = "added" if old is None else ("reinstall" if old == version else "override") out.append({"name": name, "version": version, "base_version": old, "change": change, "direct_requested": safe_file(site, dist / "REQUESTED") or safe_file(site, dist / "direct_url.json"), "installed_bytes": total}) return out def main() -> None: base = json.loads(BASE.read_text(encoding="utf-8")) print(json.dumps(scan_packages(ROOT, base), ensure_ascii=False)) if __name__ == "__main__": main()