#!/usr/bin/env python3 # SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Fails on stdlib APIs newer than the oldest CI interpreter (via vermin); syntax checks miss these.""" from __future__ import annotations import json import re import shutil import subprocess import sys from pathlib import Path REPO = Path(__file__).resolve().parents[1] WORKFLOW = REPO / ".github" / "workflows" / "studio-backend-ci.yml" # Both trees the CI matrix legs execute: studio-backend-ci also runs `pytest unsloth_cli/tests`. ROOTS = ( REPO / "studio" / "backend", REPO / "unsloth_cli", ) # Scan everything under studio/backend, tests included: the CI legs execute all of it. EXCLUDE_PARTS = ("vendor", "node_modules", "__pycache__", ".venv") # An above-floor symbol reached deliberately is suppressed AT THE SITE, with `# novermin` and a comment saying why, not by dropping its file from the scan. The one live case is locale.getencoding() in the data-designer plugin's state_store, inside a try/except AttributeError with a pre-3.11 fallback. # Floor is declared in the workflow; the matrix and pyproject (>= 3.9) cannot supply the real floor. FLOOR_KEY = "PYTHON_FLOOR" def declared_floor() -> tuple[int, int]: """The floor the workflow declares, as (major, minor).""" text = WORKFLOW.read_text(encoding = "utf-8") found = re.search(rf"^\s*{FLOOR_KEY}:\s*['\"]?(\d+)\.(\d+)['\"]?\s*$", text, re.M) if not found: raise SystemExit( f"{WORKFLOW.name} declares no {FLOOR_KEY}, so this lint has no target. It is " f"declared there rather than here so that the number lives with the CI that " f"used to test it." ) return int(found.group(1)), int(found.group(2)) def targets() -> list[str]: """Every .py the matrix legs ship or execute, found rather than listed.""" found = [] for root in ROOTS: if not root.is_dir(): raise SystemExit(f"{root} is gone; the scan would silently stop covering it") found.extend( str(path) for path in sorted(root.rglob("*.py")) if not any(part in EXCLUDE_PARTS for part in path.relative_to(root).parts) ) if not found: raise SystemExit(f"no python files found under {ROOTS}; the scan would pass on nothing") return found def main() -> int: floor = declared_floor() target = f"{floor[0]}.{floor[1]}" # The console script: vermin has no __main__, so `python -m vermin` always fails. vermin = shutil.which("vermin") if vermin is None: raise SystemExit( "vermin is not installed, so the backend floor is unchecked. Install it in " "the job that runs this, rather than letting the check quietly pass." ) files = targets() print(f"[floor] {len(files)} files must run on Python {target}, " f"the declared floor") command = [ vermin, "--no-tips", "--violations", f"-t={target}", *files, ] result = subprocess.run(command, capture_output = True, text = True) sys.stdout.write(result.stdout) sys.stderr.write(result.stderr) if result.returncode == 0: print(f"[floor] OK: nothing needs more than {target}") return 0 print( f"::error title=Backend needs a newer Python than the matrix floor::" f"something under studio/backend or unsloth_cli requires more than Python {target}, " f"which is the " f"floor studio-backend-ci declares. Nothing runs that interpreter any more, so " f"this check is the only thing standing between an above-floor symbol and a user " f"on that version. Either guard the usage behind a sys.version_info check, or " f"raise {FLOOR_KEY} in the workflow and say why." ) return 1 if __name__ == "__main__": raise SystemExit(main())