#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PYTHON="${PYTHON:-$ROOT/.venv/bin/python}" CHECK_TESTCLIENT_DEPRECATIONS="${CHECK_TESTCLIENT_DEPRECATIONS:-auto}" if [[ ! -x "$PYTHON" ]]; then PYTHON=python fi cd "$ROOT" "$PYTHON" - <<'PY' import importlib.util import sys if importlib.util.find_spec("pip") is None: print("Dependency hygiene failed: this Python environment cannot import pip.", file=sys.stderr) print("Recreate or repair the venv, then reinstall requirements:", file=sys.stderr) print(" python -m venv .venv", file=sys.stderr) print(" ./.venv/bin/python -m pip install --upgrade pip", file=sys.stderr) print(" ./.venv/bin/python -m pip install -r requirements-dev.txt", file=sys.stderr) raise SystemExit(127) PY "$PYTHON" -m pip check "$PYTHON" - <<'PY' from __future__ import annotations import importlib.metadata import pathlib import sys prefix = pathlib.Path(sys.prefix) legacy_patterns = ( "__editable__.govoplan_module_multimailer-*.pth", "__editable___govoplan_module_multimailer_*_finder.py", "govoplan_module_multimailer-*.dist-info", ) problems: list[pathlib.Path] = [] for site_packages in (prefix / "lib").glob("python*/site-packages"): for pattern in legacy_patterns: problems.extend(site_packages.glob(pattern)) for dist in importlib.metadata.distributions(): if dist.metadata.get("Name", "").lower() == "govoplan-module-multimailer": problems.append(pathlib.Path(str(dist.locate_file("")))) if problems: print("Dependency hygiene failed: stale legacy multimailer install metadata found.", file=sys.stderr) for path in sorted(set(problems)): print(f" {path}", file=sys.stderr) print("Remove the stale files or recreate the venv. This package pins obsolete dependencies.", file=sys.stderr) raise SystemExit(1) print("No stale legacy multimailer package metadata found.") PY "$PYTHON" - <<'PY' from __future__ import annotations import pathlib import sys root = pathlib.Path.cwd() scan_roots = [ root / "src", root / "tests", root.parent / "govoplan-access" / "src", root.parent / "govoplan-admin" / "src", root.parent / "govoplan-audit" / "src", root.parent / "govoplan-calendar" / "src", root.parent / "govoplan-campaign" / "src", root.parent / "govoplan-dashboard" / "src", root.parent / "govoplan-files" / "src", root.parent / "govoplan-identity" / "src", root.parent / "govoplan-mail" / "src", root.parent / "govoplan-ops" / "src", root.parent / "govoplan-organizations" / "src", root.parent / "govoplan-policy" / "src", root.parent / "govoplan-tenancy" / "src", ] deprecated_tokens = { "HTTP_422_UNPROCESSABLE_ENTITY": "Use HTTP_422_UNPROCESSABLE_CONTENT; the numeric status remains 422.", } hits: list[str] = [] for scan_root in scan_roots: if not scan_root.exists(): continue for path in scan_root.rglob("*.py"): try: text = path.read_text(encoding="utf-8") except UnicodeDecodeError: continue for token, replacement in deprecated_tokens.items(): if token in text: hits.append(f"{path}: deprecated {token}. {replacement}") if hits: print("Dependency hygiene failed: deprecated framework constants are still used.", file=sys.stderr) print("\n".join(hits), file=sys.stderr) raise SystemExit(1) print("Deprecated framework constant scan passed.") PY RUN_TESTCLIENT_DEPRECATION_SMOKE=0 case "$CHECK_TESTCLIENT_DEPRECATIONS" in 0|false|False|no|No) echo "TestClient deprecation smoke skipped by CHECK_TESTCLIENT_DEPRECATIONS=$CHECK_TESTCLIENT_DEPRECATIONS" ;; auto) if "$PYTHON" - <<'PY' import importlib.util raise SystemExit(0 if importlib.util.find_spec("fastapi") and importlib.util.find_spec("starlette") and importlib.util.find_spec("httpx2") else 1) PY then RUN_TESTCLIENT_DEPRECATION_SMOKE=1 else echo "TestClient deprecation smoke skipped; install dev requirements to enable it." fi ;; *) RUN_TESTCLIENT_DEPRECATION_SMOKE=1 ;; esac if [[ "$RUN_TESTCLIENT_DEPRECATION_SMOKE" -eq 1 ]]; then "$PYTHON" - <<'PY' import warnings from starlette.exceptions import StarletteDeprecationWarning warnings.simplefilter("error", StarletteDeprecationWarning) from fastapi import FastAPI from fastapi.testclient import TestClient app = FastAPI() @app.get("/dependency-hygiene-ping") def ping() -> dict[str, bool]: return {"ok": True} with TestClient(app) as client: response = client.get("/dependency-hygiene-ping") assert response.status_code == 200 assert response.json() == {"ok": True} print("TestClient deprecation smoke passed.") PY fi echo "Dependency hygiene check passed."