Ctrl K
ComunidadGuías…

api/check_deps.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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()