FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

#!/usr/bin/env python3 """Render the PyPI package compatibility matrix as a Markdown table. Resolves every package in ``tests/packages/matrix.toml`` for each device target with the same pip cross-resolution ``pn deps`` uses, then prints a table suitable for ``docs/guides/pypi-packages.md``: uv run scripts/package-matrix.py # Markdown to stdout uv run scripts/package-matrix.py --json # machine-readable uv run scripts/package-matrix.py --check # exit 1 if the live # results disagree with # the manifest Requires network access (PyPI, BeeWare's index, Chaquopy's index). The ``packages`` CI workflow runs ``--check`` on a schedule and uploads the Markdown as an artifact so the guide's table can be refreshed by copy and paste rather than by hand. """ from __future__ import annotations import json import subprocess import sys import tomllib from datetime import date from pathlib import Path from typing import Dict, List, Optional, Sequence ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "src")) from pythonnative.project import deps # noqa: E402 from pythonnative.project.builder import CommandResult, CommandRunner # noqa: E402 from pythonnative.project.config import AppConfig # noqa: E402 MATRIX_PATH = ROOT / "tests" / "packages" / "matrix.toml" COLUMNS = [ ("iOS device", lambda cfg: deps.ios_targets(cfg, sdks=("iphoneos",))[0]), ("iOS Simulator", lambda cfg: deps.ios_targets(cfg, sdks=("iphonesimulator",), simulator_arch="arm64")[0]), ("Android arm64-v8a", lambda cfg: deps.android_targets(cfg)[0]), ("Android x86_64", lambda cfg: deps.android_targets(cfg)[1]), ] class PipRunner(CommandRunner): """Run pip as a real subprocess, capturing its output.""" def run( self, args: Sequence[str], *, cwd: Optional[Path] = None, env: Optional[dict] = None, capture: bool = False, ) -> CommandResult: """Execute ``args`` and return its exit code, stdout, and stderr.""" proc = subprocess.run(list(args), cwd=cwd, env=env, capture_output=True, text=True) return CommandResult(proc.returncode, proc.stdout, proc.stderr) def _config(scratch: Path, python_version: str, requirement: str) -> AppConfig: (scratch / "app").mkdir(parents=True, exist_ok=True) (scratch / "pythonnative.toml").write_text( f'[app]\nid = "com.pythonnative.matrix"\nname = "matrix"\npython_version = "{python_version}"\n' f'[android]\nabi_filters = ["arm64-v8a", "x86_64"]\n[requirements]\npackages = ["{requirement}"]\n', encoding="utf-8", ) return AppConfig.load(scratch) def _cell(res: deps.Resolution, name: str) -> str: if not res.ok: return "no wheel" top = next((p for p in res.packages if deps._canonical(p.name) == deps._canonical(name.split("=")[0])), None) if top is None: return "ok" text = top.version if top.downgraded: text += f" (latest {top.latest})" return text if top.is_pure else f"{text}, binary" def run(matrix: dict) -> List[Dict[str, object]]: """Resolve every manifest entry for every column target; one row per package.""" import tempfile rows: List[Dict[str, object]] = [] with tempfile.TemporaryDirectory() as tmp: for entry in matrix["package"]: config = _config(Path(tmp), matrix["python_version"], entry["name"]) targets = [make(config) for _, make in COLUMNS] resolutions = deps.resolve_all(config, targets, runner=PipRunner()) row: Dict[str, object] = { "name": entry["name"], "kind": entry["kind"], "note": entry.get("note", ""), "expected": {"ios": entry["ios"], "android": entry["android"]}, "cells": {label: _cell(res, entry["name"]) for (label, _), res in zip(COLUMNS, resolutions)}, "ok": {label: res.ok for (label, _), res in zip(COLUMNS, resolutions)}, } rows.append(row) return rows def as_markdown(matrix: dict, rows: List[Dict[str, object]]) -> str: """Render ``rows`` as the Markdown table embedded in the PyPI packages guide.""" head = ["Package", "Kind", *[label for label, _ in COLUMNS], "Notes"] out = [ f"", "| " + " | ".join(head) + " |", "| " + " | ".join("---" for _ in head) + " |", ] for row in rows: cells = row["cells"] assert isinstance(cells, dict) out.append( "| `" + str(row["name"]) + "` | " + str(row["kind"]) + " | " + " | ".join(str(cells[label]) for label, _ in COLUMNS) + " | " + str(row["note"]) + " |" ) return "\n".join(out) def mismatches(rows: List[Dict[str, object]]) -> List[str]: """Describe every cell whose live outcome disagrees with the manifest.""" problems: List[str] = [] for row in rows: expected = row["expected"] ok = row["ok"] assert isinstance(expected, dict) and isinstance(ok, dict) for label, _ in COLUMNS: platform = "ios" if label.startswith("iOS") else "android" if ok[label] != expected[platform]: problems.append(f"{row['name']} on {label}: expected {'ok' if expected[platform] else 'no wheel'}") return problems def main(argv: List[str]) -> int: """CLI entry point; see the module docstring for flags.""" with open(MATRIX_PATH, "rb") as handle: matrix = tomllib.load(handle) rows = run(matrix) if "--json" in argv: print(json.dumps({"python_version": matrix["python_version"], "packages": rows}, indent=2)) else: print(as_markdown(matrix, rows)) problems = mismatches(rows) if problems: print("\nLive results disagree with tests/packages/matrix.toml:", file=sys.stderr) for problem in problems: print(f" - {problem}", file=sys.stderr) return 1 if "--check" in argv else 0 return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))

Back | FazBrowse Home | New Git URL