#!/usr/bin/env python3 """Audit ``uv.lock`` for license compliance. Parses the lockfile (TOML) and reports each package's ``license`` field. The tool is intentionally read-only — it never modifies ``uv.lock``. Permissive licenses (MIT, BSD-2/3-Clause, Apache-2.0, ISC, MPL-2.0, PSF-2.0, etc.) are flagged as OK; everything else is reported as a warning. Unknown or missing licenses are also reported as warnings. Usage:: uv run python tools/license-audit.py """ from __future__ import annotations import sys import tomllib from pathlib import Path from typing import Any LOCK_PATH = Path(__file__).resolve().parent.parent / "uv.lock" PERMISSIVE_LICENSES: frozenset[str] = frozenset( { # SPDX expressions (case-insensitive) "mit", "mit-0", "apache-2.0", "apache-2.0-with-clauses", "bsd-2-clause", "bsd-3-clause", "isc", "mpl-2.0", "python-2.0", "psf-2.0", "zlib", "cc0-1.0", "cc-by-4.0", "unlicense", "0bsd", "blueoak-1.0.0", "lgpl-2.1", "lgpl-3.0", } ) def main() -> int: if not LOCK_PATH.exists(): print(f"error: {LOCK_PATH} not found", file=sys.stderr) return 2 with LOCK_PATH.open("rb") as fh: data: dict[str, Any] = tomllib.load(fh) packages: list[dict[str, Any]] = list(data.get("package", [])) if not packages: print("error: no [[package]] entries in uv.lock", file=sys.stderr) return 2 warnings_count = 0 for pkg in packages: name = pkg.get("name", "?") version = pkg.get("version", "?") license_field = pkg.get("license") if license_field is None: # uv stores a separate ``[[package.license]]`` table only for # SPDX-refs; for many packages the field is absent. print(f"WARN {name}=={version}: license field missing") warnings_count += 1 continue text = str(license_field).strip().lower() # Compound expressions like ``MIT OR Apache-2.0`` are accepted when # any single token is permissive. tokens = { tok.strip(" ()") for tok in text.replace(" and ", " ").replace(" or ", " ").split() } if tokens & PERMISSIVE_LICENSES: print(f"OK {name}=={version}: {text}") else: print(f"WARN {name}=={version}: {text}") warnings_count += 1 print(f"\nAudited {len(packages)} packages; {warnings_count} warning(s).") return 0 if __name__ == "__main__": raise SystemExit(main())