api/check_deps.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""
Compare dependency versions:
- constraint: from pyproject.toml
- locked : from uv.lock
- latest : from PyPI
Usage:
python check_deps.py
"""
from __future__ import annotations
import json
import urllib.error
import urllib.request
from pathlib import Path
try:
import tomllib # Python 3.11+
except ImportError: # pragma: no cover
import tomli as tomllib # type: ignore[no-redef]
GREEN = "\033[32m"
YELLOW = "\033[33m"
ORANGE = "\033[38;5;214m"
RED = "\033[31m"
RESET = "\033[0m"
def version_tuple(v: str) -> tuple[int, ...]:
parts = []
for p in v.split(".")[:3]:
digits = "".join(c for c in p if c.isdigit())
parts.append(int(digits) if digits else 0)
return tuple(parts)
def colorize(line: str, locked: str, latest: str) -> str:
if not locked or not latest:
return line
lv = version_tuple(locked)
rv = version_tuple(latest)
if lv == rv:
color = GREEN
elif lv[0] != rv[0]:
color = RED
elif lv[1] != rv[1]:
color = ORANGE
else:
color = YELLOW
return f"{color}{line}{RESET}"
ROOT = Path(__file__).resolve().parent
PYPROJECT = ROOT / "pyproject.toml"
LOCKFILE = ROOT / "uv.lock"
def load_pyproject_constraints() -> dict[str, str]:
"""
Read [project.dependencies] constraints from pyproject.toml.
(Only runtime deps; you can extend to include groups if you want.)
"""
data = tomllib.loads(PYPROJECT.read_text())
constraints: dict[str, str] = {}
for spec in data.get("project", {}).get("dependencies", []):
name = spec.split(";", 1)[0].split("[", 1)[0]
for separator in ("<", ">", "=", "!", "~"):
name = name.split(separator, 1)[0]
name = name.strip().lower()
if name:
constraints[name] = spec
return constraints
def load_locked_versions() -> dict[str, str]:
"""
Read package versions from uv.lock.
"""
data = tomllib.loads(LOCKFILE.read_text())
pkgs = data.get("package", [])
locked: dict[str, str] = {}
for pkg in pkgs:
name = pkg["name"].lower()
locked[name] = pkg["version"]
return locked
def load_pypi_latest(package_names: list[str]) -> dict[str, str]:
latest: dict[str, str] = {}
for name in package_names:
try:
with urllib.request.urlopen(
f"https://pypi.org/pypi/{name}/json", timeout=10
) as response:
payload = json.loads(response.read().decode("utf-8"))
except (urllib.error.URLError, TimeoutError):
continue
version = payload.get("info", {}).get("version")
if not version:
continue
latest[name] = version
return latest
def main() -> None:
constraints = load_pyproject_constraints()
locked = load_locked_versions()
pypi_latest = load_pypi_latest(sorted(constraints.keys()))
# Dynamic column widths based on content
col1 = max(len("package"), *(len(name) for name in constraints.keys())) + 2
col2 = (
max(
len("constraint"),
*(len(str(c)) for c in constraints.values()),
)
+ 2
)
header = (
f"{'package':{col1}} {'constraint':{col2}} {'locked':12} {'pypi-latest':12}"
)
print(header)
print("-" * len(header))
# Only show packages that are direct runtime deps in pyproject
for name in sorted(constraints.keys()):
constraint = constraints.get(name, "")
locked_version = locked.get(name, "")
latest = pypi_latest.get(name, "")
line = f"{name:{col1}} {constraint:{col2}} {locked_version:12} {latest:12}"
print(colorize(line, locked_version, latest))
if __name__ == "__main__":
main()