From fb82e3947dbdd070deaf95d1c9e079ba132ab033 Mon Sep 17 00:00:00 2001 From: copilotcoder Date: Mon, 20 Jul 2026 15:16:28 -0400 Subject: [PATCH] Deduplicate dependency version pins from backend code/tests --- backend/src/backend/main.py | 74 +++++++++++++++++++++++---- backend/tests/integration/test_api.py | 5 +- backend/tests/test_basic.py | 9 +--- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/backend/src/backend/main.py b/backend/src/backend/main.py index 5e09c4f..4dc6c30 100644 --- a/backend/src/backend/main.py +++ b/backend/src/backend/main.py @@ -6,9 +6,11 @@ from __future__ import annotations import os import sys +import tomllib from contextlib import asynccontextmanager from importlib import metadata -from typing import Any +from pathlib import Path +from typing import Any, cast from fastapi import Depends, FastAPI, HTTPException, status from fastapi.responses import JSONResponse @@ -20,12 +22,65 @@ from backend.database import ( probe_database, ) -REQUIRED_PACKAGE_PINS: dict[str, str] = { - "fastapi": "0.139.0", - "psycopg": "3.2.12", - "sqlalchemy": "2.0.44", - "uvicorn": "0.51.0", -} +PROJECT_DISTRIBUTION_NAME = "plex-playlist-backend" + + +def _parse_exact_pin(requirement: str) -> tuple[str, str] | None: + """Extract normalized package name and exact version from a requirement string.""" + requirement_without_marker = requirement.split(";", 1)[0].strip() + if "==" not in requirement_without_marker: + return None + + package_part, version = requirement_without_marker.split("==", 1) + package_name = package_part.strip().split("[", 1)[0].lower() + pinned_version = version.strip() + if not package_name or not pinned_version: + return None + + return package_name, pinned_version + + +def _required_package_pins_from_dependencies(dependencies: list[str]) -> dict[str, str]: + """Build exact pins from project dependency declarations.""" + required_pins: dict[str, str] = {} + for requirement in dependencies: + parsed_pin = _parse_exact_pin(requirement) + if parsed_pin is None: + continue + package_name, version = parsed_pin + required_pins[package_name] = version + return required_pins + + +def _required_package_pins() -> dict[str, str]: + """Load required package pins from project metadata derived from pyproject.""" + try: + dependencies = metadata.requires(PROJECT_DISTRIBUTION_NAME) or [] + pins = _required_package_pins_from_dependencies(dependencies) + if pins: + return pins + except metadata.PackageNotFoundError: + pass + + pyproject_file = Path(__file__).resolve().parents[2] / "pyproject.toml" + pyproject = cast( + "dict[str, object]", + tomllib.loads(pyproject_file.read_text(encoding="utf-8")), + ) + project_section = pyproject.get("project") + if not isinstance(project_section, dict): + return {} + + project_config = cast("dict[str, object]", project_section) + dependencies = project_config.get("dependencies") + if not isinstance(dependencies, list): + return {} + + dependency_entries = cast("list[object]", dependencies) + dependency_strings = [ + dependency for dependency in dependency_entries if isinstance(dependency, str) + ] + return _required_package_pins_from_dependencies(dependency_strings) def _parse_required_python(required_python: str) -> tuple[int, int] | None: @@ -56,10 +111,11 @@ def compatibility_status() -> dict[str, object]: required_python = os.getenv("BACKEND_REQUIRED_PYTHON", "3.14") current_python = f"{sys.version_info.major}.{sys.version_info.minor}" required_python_parsed = _parse_required_python(required_python) + required_package_pins = _required_package_pins() package_checks: dict[str, bool] = {} package_errors: dict[str, str] = {} - for package_name, required_version in REQUIRED_PACKAGE_PINS.items(): + for package_name, required_version in required_package_pins.items(): try: package_checks[package_name] = ( _installed_version(package_name) == required_version @@ -78,7 +134,7 @@ def compatibility_status() -> dict[str, object]: "required_python": required_python, "current_python": current_python, "python_policy_valid": required_python_parsed is not None, - "required_packages": REQUIRED_PACKAGE_PINS, + "required_packages": required_package_pins, "package_checks": package_checks, "package_errors": package_errors, } diff --git a/backend/tests/integration/test_api.py b/backend/tests/integration/test_api.py index 9ae0e93..3f96354 100644 --- a/backend/tests/integration/test_api.py +++ b/backend/tests/integration/test_api.py @@ -84,6 +84,7 @@ class TestAPIIntegration: ) -> None: """Compatibility endpoint should expose runtime and pinning policy status.""" monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14") + expected_required_packages = compatibility_status()["required_packages"] with TestClient(app) as local_client: response = local_client.get("/compatibility") @@ -94,9 +95,7 @@ class TestAPIIntegration: assert payload["required_python"] == "3.14" assert "current_python" in payload assert payload["python_policy_valid"] is True - assert "required_packages" in payload - assert payload["required_packages"]["fastapi"] == "0.139.0" - assert payload["required_packages"]["uvicorn"] == "0.51.0" + assert payload["required_packages"] == expected_required_packages assert payload["package_errors"] == {} monkeypatch.delenv("BACKEND_REQUIRED_PYTHON", raising=False) diff --git a/backend/tests/test_basic.py b/backend/tests/test_basic.py index c0e1e31..ed83e5a 100644 --- a/backend/tests/test_basic.py +++ b/backend/tests/test_basic.py @@ -98,15 +98,10 @@ def test_compatibility_status_success(monkeypatch: pytest.MonkeyPatch): """Compatibility status should be healthy when all checks match policy.""" monkeypatch.setenv("BACKEND_REQUIRED_PYTHON", "3.14") + required_pins = cast("dict[str, str]", compatibility_status()["required_packages"]) def installed_version(package_name: str) -> str: - versions = { - "fastapi": "0.139.0", - "psycopg": "3.2.12", - "sqlalchemy": "2.0.44", - "uvicorn": "0.51.0", - } - return versions[package_name] + return required_pins[package_name] monkeypatch.setattr("backend.main._installed_version", installed_version) status = cast("dict[str, Any]", compatibility_status())