From e122c3ff8f7d394dd06fe73359f8776a3b78d92d Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Wed, 4 Mar 2026 11:20:55 +0000 Subject: [PATCH 01/13] Add dashabord --- .github/workflows/test_and_docs.yml | 1 + scripts/tooling/.python-version | 1 + scripts/tooling/README.md | 0 scripts/tooling/cli/__init__.py | 0 scripts/tooling/cli/main.py | 65 ++ scripts/tooling/lib/__init__.py | 0 .../tooling/lib/assets/report_template.html | 563 ++++++++++++++++++ scripts/tooling/lib/html_report.py | 77 +++ scripts/tooling/lib/known_good/__init__.py | 18 + scripts/tooling/lib/known_good/known_good.py | 78 +++ scripts/tooling/lib/known_good/module.py | 127 ++++ scripts/tooling/pyproject.toml | 16 + scripts/tooling/tests/__init__.py | 0 scripts/tooling/tests/test_known_good.py | 232 ++++++++ scripts/tooling/tests/test_report.py | 297 +++++++++ scripts/tooling/uv.lock | 158 +++++ 16 files changed, 1633 insertions(+) create mode 100644 scripts/tooling/.python-version create mode 100644 scripts/tooling/README.md create mode 100644 scripts/tooling/cli/__init__.py create mode 100644 scripts/tooling/cli/main.py create mode 100644 scripts/tooling/lib/__init__.py create mode 100644 scripts/tooling/lib/assets/report_template.html create mode 100644 scripts/tooling/lib/html_report.py create mode 100644 scripts/tooling/lib/known_good/__init__.py create mode 100644 scripts/tooling/lib/known_good/known_good.py create mode 100644 scripts/tooling/lib/known_good/module.py create mode 100644 scripts/tooling/pyproject.toml create mode 100644 scripts/tooling/tests/__init__.py create mode 100644 scripts/tooling/tests/test_known_good.py create mode 100644 scripts/tooling/tests/test_report.py create mode 100644 scripts/tooling/uv.lock diff --git a/.github/workflows/test_and_docs.yml b/.github/workflows/test_and_docs.yml index 0d0f1574259..4ba7290700f 100644 --- a/.github/workflows/test_and_docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -111,6 +111,7 @@ jobs: --github_user=${{ github.repository_owner }} \ --github_repo=${{ github.event.repository.name }} + cd scripts/tooling && uv run python -m cli.main misc html_report --output _build/integration_status.html tar -cf github-pages.tar _build - name: Upload documentation artifact uses: actions/upload-artifact@v4.4.0 diff --git a/scripts/tooling/.python-version b/scripts/tooling/.python-version new file mode 100644 index 00000000000..e4fba218358 --- /dev/null +++ b/scripts/tooling/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/scripts/tooling/README.md b/scripts/tooling/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/tooling/cli/__init__.py b/scripts/tooling/cli/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py new file mode 100644 index 00000000000..854f68c17d5 --- /dev/null +++ b/scripts/tooling/cli/main.py @@ -0,0 +1,65 @@ +import argparse +import sys +from pathlib import Path + + +def _find_repo_root() -> Path: + """Walk up from this file to find the repo root (contains known_good.json).""" + candidate = Path(__file__).resolve() + for parent in candidate.parents: + if (parent / "known_good.json").exists(): + return parent + return Path.cwd() + + +def _cmd_html_report(args: argparse.Namespace) -> int: + from lib.html_report import write_report + from lib.known_good.known_good import load_known_good + + known_good_path = Path(args.known_good) / "known_good.json" + try: + known_good = load_known_good(known_good_path) + except (FileNotFoundError, ValueError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + + output = Path(args.output) if args.output else Path("report.html") + write_report(known_good, output) + print(f"Report written to {output}") + return 0 + + +def main() -> None: + parser = argparse.ArgumentParser(prog="tooling") + subparsers = parser.add_subparsers(dest="group", metavar="GROUP") + subparsers.required = True + + # --- misc group --- + misc_parser = subparsers.add_parser("misc", help="Miscellaneous utilities") + misc_sub = misc_parser.add_subparsers(dest="command", metavar="COMMAND") + misc_sub.required = True + + html_parser = misc_sub.add_parser( + "html_report", help="Generate an HTML status report from known_good.json" + ) + html_parser.add_argument( + "--known_good", + metavar="PATH", + default=str(_find_repo_root()), + help="Directory containing known_good.json (default: repo root)", + ) + html_parser.add_argument( + "--output", + metavar="FILE", + default="report.html", + help="Output HTML file path (default: report.html)", + ) + + args = parser.parse_args() + + if args.group == "misc" and args.command == "html_report": + sys.exit(_cmd_html_report(args)) + + +if __name__ == "__main__": + main() diff --git a/scripts/tooling/lib/__init__.py b/scripts/tooling/lib/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/tooling/lib/assets/report_template.html b/scripts/tooling/lib/assets/report_template.html new file mode 100644 index 00000000000..fb6e2be84fc --- /dev/null +++ b/scripts/tooling/lib/assets/report_template.html @@ -0,0 +1,563 @@ + + + + + + Known Good Status + + + +
+

Known Good Status

+

Snapshot: {{ timestamp }}

+ +
+
+
+
+ +
+
+
+ +
+ 🔒 A GitHub personal access token (PAT) is required to fetch latest modules status. + Enter it in the token field above, or + create one here. + Without a token, cards are shown but status is unavailable.Your PAT is not send anywhere, it's only keept in local cache of this page +
+ +
+
+ + + + + + diff --git a/scripts/tooling/lib/html_report.py b/scripts/tooling/lib/html_report.py new file mode 100644 index 00000000000..4614c4468bf --- /dev/null +++ b/scripts/tooling/lib/html_report.py @@ -0,0 +1,77 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""HTML report generator for known_good.json status.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, Dict, List + +from jinja2 import Environment, FileSystemLoader, select_autoescape + +from .known_good import KnownGood + +_TEMPLATE_DIR = Path(__file__).parent / "assets" +_ENV = Environment( + loader=FileSystemLoader(_TEMPLATE_DIR), + autoescape=select_autoescape(["html"]), +) + + +def _collect_entries(known_good: KnownGood) -> List[Dict[str, Any]]: + entries = [] + for group_name, group_modules in known_good.modules.items(): + for module in group_modules.values(): + try: + owner_repo = module.owner_repo + except ValueError: + owner_repo = None + entries.append( + { + "name": module.name, + "group": group_name, + "repo": module.repo, + "owner_repo": owner_repo, + "hash": module.hash, + "version": module.version, + "branch": module.branch, + } + ) + return entries + + +def generate_report(known_good: KnownGood) -> str: + """Return a self-contained HTML report string for *known_good*. + + The page embeds module data as JSON and uses client-side JavaScript to + query the GitHub compare API, showing how many commits behind each module + is relative to its target branch HEAD. A GitHub personal access token + (PAT) must be supplied via the token input in the header. Without a token + the grid is still rendered but each card shows an "unavailable / requires + PAT" badge instead of live status. + """ + entries = _collect_entries(known_good) + tmpl = _ENV.get_template("report_template.html") + return tmpl.render( + modules_json=json.dumps(entries, indent=2), + timestamp=known_good.timestamp, + ) + + +def write_report( + known_good: KnownGood, + output_path: Path, +) -> None: + """Write the HTML report to *output_path*.""" + Path(output_path).write_text(generate_report(known_good), encoding="utf-8") diff --git a/scripts/tooling/lib/known_good/__init__.py b/scripts/tooling/lib/known_good/__init__.py new file mode 100644 index 00000000000..ff5ddeb436f --- /dev/null +++ b/scripts/tooling/lib/known_good/__init__.py @@ -0,0 +1,18 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""known_good parsing utilities.""" + +from .known_good import KnownGood, load_known_good +from .module import Metadata, Module + +__all__ = ["KnownGood", "load_known_good", "Module", "Metadata"] diff --git a/scripts/tooling/lib/known_good/known_good.py b/scripts/tooling/lib/known_good/known_good.py new file mode 100644 index 00000000000..dda189366e0 --- /dev/null +++ b/scripts/tooling/lib/known_good/known_good.py @@ -0,0 +1,78 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""KnownGood model and loader for score reference integration.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Dict + +from .module import Module + + +@dataclass +class KnownGood: + """Parsed contents of known_good.json. + + modules: {"group_name": {"module_name": Module, ...}, ...} + """ + + modules: Dict[str, Dict[str, Module]] + timestamp: str + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> KnownGood: + parsed: Dict[str, Dict[str, Module]] = {} + for group_name, group_modules in data.get("modules", {}).items(): + if isinstance(group_modules, dict): + parsed[group_name] = {m.name: m for m in Module.parse_modules(group_modules)} + return cls(modules=parsed, timestamp=data.get("timestamp", "")) + + +def load_known_good(path: Path) -> KnownGood: + """Parse known_good.json at *path* and return a typed :class:`KnownGood`. + + Args: + path: Path to the known_good.json file. + + Returns: + Fully-typed KnownGood instance. + + Raises: + ValueError: On malformed JSON or unexpected top-level structure. + FileNotFoundError: If *path* does not exist. + """ + text = Path(path).read_text(encoding="utf-8") + try: + data = json.loads(text) + except json.JSONDecodeError as e: + lines = text.splitlines() + line = lines[e.lineno - 1] if 0 <= e.lineno - 1 < len(lines) else "" + pointer = " " * (e.colno - 1) + "^" + hint = ( + "Possible causes: trailing comma, missing value, or extra comma." + if "Expecting value" in e.msg + else "" + ) + raise ValueError( + f"Invalid JSON at line {e.lineno}, column {e.colno}\n{line}\n{pointer}\n{e.msg}. {hint}" + ) from None + + if not isinstance(data, dict) or not isinstance(data.get("modules"), dict): + raise ValueError( + f"Invalid known_good.json at {path}: expected object with 'modules' dict" + ) + + return KnownGood.from_dict(data) diff --git a/scripts/tooling/lib/known_good/module.py b/scripts/tooling/lib/known_good/module.py new file mode 100644 index 00000000000..c9c562ece05 --- /dev/null +++ b/scripts/tooling/lib/known_good/module.py @@ -0,0 +1,127 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""Module dataclass for score reference integration.""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass, field +from typing import Any, Dict, List +from urllib.parse import urlparse + + +@dataclass +class Metadata: + """Metadata configuration for a module.""" + + code_root_path: str = "//score/..." + extra_test_config: list[str] = field(default_factory=list) + exclude_test_targets: list[str] = field(default_factory=list) + langs: list[str] = field(default_factory=lambda: ["cpp", "rust"]) + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> Metadata: + return cls( + code_root_path=data.get("code_root_path", "//score/..."), + extra_test_config=data.get("extra_test_config", []), + exclude_test_targets=data.get("exclude_test_targets", []), + langs=data.get("langs", ["cpp", "rust"]), + ) + + def to_dict(self) -> Dict[str, Any]: + return { + "code_root_path": self.code_root_path, + "extra_test_config": self.extra_test_config, + "exclude_test_targets": self.exclude_test_targets, + "langs": self.langs, + } + + +@dataclass +class Module: + """A single known-good module entry.""" + + name: str + hash: str + repo: str + version: str | None = None + bazel_patches: list[str] | None = None + metadata: Metadata = field(default_factory=Metadata) + branch: str = "main" + pin_version: bool = False + + @classmethod + def from_dict(cls, name: str, data: Dict[str, Any]) -> Module: + repo = data.get("repo", "") + commit_hash = data.get("hash") or data.get("commit", "") + version = data.get("version") + + if commit_hash and version: + raise ValueError( + f"Module '{name}' has both 'hash' and 'version' set. " + "Use either 'hash' (git_override) or 'version' (single_version_override), not both." + ) + + bazel_patches = data.get("bazel_patches") or data.get("patches", []) + + metadata_data = data.get("metadata") + metadata = Metadata.from_dict(metadata_data) if metadata_data is not None else Metadata() + + return cls( + name=name, + hash=commit_hash, + repo=repo, + version=version, + bazel_patches=bazel_patches if bazel_patches else None, + metadata=metadata, + branch=data.get("branch", "main"), + pin_version=data.get("pin_version", False), + ) + + @classmethod + def parse_modules(cls, modules_dict: Dict[str, Any]) -> List[Module]: + modules = [] + for name, module_data in modules_dict.items(): + module = cls.from_dict(name, module_data) + if not module.repo and not module.version: + logging.warning("Skipping module %s with missing repo", name) + continue + modules.append(module) + return modules + + @property + def owner_repo(self) -> str: + """Return 'owner/repo' extracted from a GitHub HTTPS URL.""" + parsed = urlparse(self.repo) + if parsed.netloc != "github.com": + raise ValueError(f"Not a GitHub URL: {self.repo}") + path = parsed.path.lstrip("/").removesuffix(".git") + parts = path.split("/", 2) + if len(parts) != 2: + raise ValueError(f"Cannot parse owner/repo from: {self.repo}") + return f"{parts[0]}/{parts[1]}" + + def to_dict(self) -> Dict[str, Any]: + result: Dict[str, Any] = {"repo": self.repo} + if self.version: + result["version"] = self.version + else: + result["hash"] = self.hash + result["metadata"] = self.metadata.to_dict() + if self.bazel_patches: + result["bazel_patches"] = self.bazel_patches + if self.branch and self.branch != "main": + result["branch"] = self.branch + if self.pin_version: + result["pin_version"] = True + return result diff --git a/scripts/tooling/pyproject.toml b/scripts/tooling/pyproject.toml new file mode 100644 index 00000000000..d416e167cb2 --- /dev/null +++ b/scripts/tooling/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "tooling" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = ["jinja2>=3"] + +[project.scripts] +tooling = "cli.main:main" + +[dependency-groups] +dev = ["pytest>=8"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/scripts/tooling/tests/__init__.py b/scripts/tooling/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/tooling/tests/test_known_good.py b/scripts/tooling/tests/test_known_good.py new file mode 100644 index 00000000000..797a3992fc0 --- /dev/null +++ b/scripts/tooling/tests/test_known_good.py @@ -0,0 +1,232 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +import json +import pytest +from pathlib import Path + +from lib.known_good import KnownGood, Metadata, Module, load_known_good + + +KNOWN_GOOD_JSON = Path(__file__).parents[3] / "known_good.json" + +MINIMAL_JSON = { + "modules": { + "target_sw": { + "score_baselibs": { + "repo": "https://github.com/eclipse-score/baselibs.git", + "hash": "abc123", + } + } + }, + "timestamp": "2026-01-01T00:00:00+00:00Z", +} + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture +def minimal_json_file(tmp_path: Path) -> Path: + p = tmp_path / "known_good.json" + p.write_text(json.dumps(MINIMAL_JSON)) + return p + + +@pytest.fixture +def full_json_file(tmp_path: Path) -> Path: + """Copy the real known_good.json into a temp location.""" + content = KNOWN_GOOD_JSON.read_text(encoding="utf-8") + p = tmp_path / "known_good.json" + p.write_text(content) + return p + + +# --------------------------------------------------------------------------- +# load_known_good – happy path +# --------------------------------------------------------------------------- + + +class TestLoadKnownGood: + def test_returns_known_good_instance(self, minimal_json_file: Path): + result = load_known_good(minimal_json_file) + assert isinstance(result, KnownGood) + + def test_timestamp_is_string(self, minimal_json_file: Path): + result = load_known_good(minimal_json_file) + assert isinstance(result.timestamp, str) + assert result.timestamp == "2026-01-01T00:00:00+00:00Z" + + def test_modules_is_dict_of_dicts(self, minimal_json_file: Path): + result = load_known_good(minimal_json_file) + assert isinstance(result.modules, dict) + for group in result.modules.values(): + assert isinstance(group, dict) + + def test_module_values_are_module_instances(self, minimal_json_file: Path): + result = load_known_good(minimal_json_file) + module = result.modules["target_sw"]["score_baselibs"] + assert isinstance(module, Module) + + def test_module_fields_are_typed(self, minimal_json_file: Path): + m = load_known_good(minimal_json_file).modules["target_sw"]["score_baselibs"] + assert isinstance(m.name, str) + assert isinstance(m.hash, str) + assert isinstance(m.repo, str) + assert m.version is None + assert m.bazel_patches is None + assert isinstance(m.metadata, Metadata) + assert isinstance(m.branch, str) + assert isinstance(m.pin_version, bool) + + def test_module_field_values(self, minimal_json_file: Path): + m = load_known_good(minimal_json_file).modules["target_sw"]["score_baselibs"] + assert m.name == "score_baselibs" + assert m.hash == "abc123" + assert m.repo == "https://github.com/eclipse-score/baselibs.git" + assert m.branch == "main" + assert m.pin_version is False + + +# --------------------------------------------------------------------------- +# load_known_good – real file +# --------------------------------------------------------------------------- + + +@pytest.mark.skipif(not KNOWN_GOOD_JSON.exists(), reason="known_good.json not found") +class TestLoadKnownGoodRealFile: + def test_loads_without_error(self, full_json_file: Path): + load_known_good(full_json_file) + + def test_groups_present(self, full_json_file: Path): + kg = load_known_good(full_json_file) + assert "target_sw" in kg.modules + assert "tooling" in kg.modules + + def test_score_baselibs_fields(self, full_json_file: Path): + m = load_known_good(full_json_file).modules["target_sw"]["score_baselibs"] + assert m.repo == "https://github.com/eclipse-score/baselibs.git" + assert len(m.hash) == 40 # SHA-1 + assert m.bazel_patches is not None + assert isinstance(m.metadata.extra_test_config, list) + assert isinstance(m.metadata.exclude_test_targets, list) + + def test_tooling_module_no_metadata_defaults(self, full_json_file: Path): + m = load_known_good(full_json_file).modules["tooling"]["score_crates"] + assert isinstance(m.metadata, Metadata) + + def test_all_modules_have_repo(self, full_json_file: Path): + kg = load_known_good(full_json_file) + for group in kg.modules.values(): + for m in group.values(): + assert m.repo, f"Module {m.name} is missing a repo" + + def test_owner_repo_property(self, full_json_file: Path): + m = load_known_good(full_json_file).modules["target_sw"]["score_baselibs"] + assert m.owner_repo == "eclipse-score/baselibs" + + +# --------------------------------------------------------------------------- +# Metadata defaults +# --------------------------------------------------------------------------- + + +class TestMetadataDefaults: + def test_defaults_when_metadata_key_absent(self, tmp_path: Path): + data = { + "modules": {"g": {"mod": {"repo": "https://github.com/a/b.git", "hash": "deadbeef"}}}, + "timestamp": "", + } + p = tmp_path / "kg.json" + p.write_text(json.dumps(data)) + m = load_known_good(p).modules["g"]["mod"] + assert m.metadata.code_root_path == "//score/..." + assert m.metadata.extra_test_config == [] + assert m.metadata.exclude_test_targets == [] + assert m.metadata.langs == ["cpp", "rust"] + + def test_metadata_fields_typed(self, tmp_path: Path): + data = { + "modules": { + "g": { + "mod": { + "repo": "https://github.com/a/b.git", + "hash": "deadbeef", + "metadata": { + "code_root_path": "//src/...", + "extra_test_config": ["//flag:val"], + "exclude_test_targets": ["//some:test"], + "langs": ["rust"], + }, + } + } + }, + "timestamp": "", + } + p = tmp_path / "kg.json" + p.write_text(json.dumps(data)) + meta = load_known_good(p).modules["g"]["mod"].metadata + assert isinstance(meta.code_root_path, str) + assert isinstance(meta.extra_test_config, list) + assert isinstance(meta.exclude_test_targets, list) + assert isinstance(meta.langs, list) + assert meta.langs == ["rust"] + + +# --------------------------------------------------------------------------- +# Error handling +# --------------------------------------------------------------------------- + + +class TestLoadKnownGoodErrors: + def test_file_not_found(self, tmp_path: Path): + with pytest.raises(FileNotFoundError): + load_known_good(tmp_path / "nonexistent.json") + + def test_invalid_json_raises_value_error(self, tmp_path: Path): + p = tmp_path / "bad.json" + p.write_text("{invalid json,}") + with pytest.raises(ValueError, match="Invalid JSON"): + load_known_good(p) + + def test_missing_modules_key_raises_value_error(self, tmp_path: Path): + p = tmp_path / "bad.json" + p.write_text(json.dumps({"timestamp": "2026-01-01"})) + with pytest.raises(ValueError, match="modules"): + load_known_good(p) + + def test_hash_and_version_both_set_raises(self, tmp_path: Path): + data = { + "modules": { + "g": { + "mod": { + "repo": "https://github.com/a/b.git", + "hash": "abc", + "version": "1.0.0", + } + } + }, + "timestamp": "", + } + p = tmp_path / "kg.json" + p.write_text(json.dumps(data)) + with pytest.raises(ValueError, match="both 'hash' and 'version'"): + load_known_good(p) + + def test_bazel_patches_are_list(self, minimal_json_file: Path): + data = json.loads(minimal_json_file.read_text()) + data["modules"]["target_sw"]["score_baselibs"]["bazel_patches"] = ["//patch:foo.patch"] + minimal_json_file.write_text(json.dumps(data)) + m = load_known_good(minimal_json_file).modules["target_sw"]["score_baselibs"] + assert m.bazel_patches == ["//patch:foo.patch"] diff --git a/scripts/tooling/tests/test_report.py b/scripts/tooling/tests/test_report.py new file mode 100644 index 00000000000..fcffcfd0d15 --- /dev/null +++ b/scripts/tooling/tests/test_report.py @@ -0,0 +1,297 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +import json +import re +from pathlib import Path + +import pytest + +from lib.known_good import KnownGood, load_known_good +from lib.known_good.module import Metadata, Module +from lib.html_report import generate_report, write_report + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + +KNOWN_GOOD_JSON = Path(__file__).parents[3] / "known_good.json" + + +def _make_known_good(**overrides) -> KnownGood: + """Build a minimal KnownGood with one module.""" + module_data = { + "repo": "https://github.com/eclipse-score/baselibs.git", + "hash": "abc123def456abc123def456abc123def456abc123", + **overrides, + } + module = Module.from_dict("score_baselibs", module_data) + return KnownGood( + modules={"target_sw": {"score_baselibs": module}}, + timestamp="2026-01-01T00:00:00+00:00Z", + ) + + +@pytest.fixture +def minimal_known_good() -> KnownGood: + return _make_known_good() + + +@pytest.fixture +def multi_group_known_good() -> KnownGood: + m1 = Module.from_dict("score_baselibs", { + "repo": "https://github.com/eclipse-score/baselibs.git", + "hash": "aaa", + }) + m2 = Module.from_dict("score_crates", { + "repo": "https://github.com/eclipse-score/score-crates.git", + "hash": "bbb", + }) + return KnownGood( + modules={ + "target_sw": {"score_baselibs": m1}, + "tooling": {"score_crates": m2}, + }, + timestamp="2026-02-01T00:00:00+00:00Z", + ) + + +@pytest.fixture +def real_known_good() -> KnownGood: + return load_known_good(KNOWN_GOOD_JSON) + + +# --------------------------------------------------------------------------- +# generate_report – return type and structure +# --------------------------------------------------------------------------- + + +class TestGenerateReportStructure: + def test_returns_string(self, minimal_known_good): + assert isinstance(generate_report(minimal_known_good), str) + + def test_is_html(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert html.strip().startswith("") + assert "" in html + + def test_contains_title(self, minimal_known_good): + assert "Known Good Status" in generate_report(minimal_known_good) + + def test_contains_timestamp(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "2026-01-01T00:00:00+00:00Z" in html + + def test_contains_module_json(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "score_baselibs" in html + assert "eclipse-score/baselibs" in html + + def test_embedded_json_is_valid(self, minimal_known_good): + html = generate_report(minimal_known_good) + # Extract the JS array assigned to MODULES + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + assert match, "MODULES array not found in HTML" + data = json.loads(match.group(1)) + assert isinstance(data, list) + assert len(data) == 1 + + def test_module_entry_fields(self, minimal_known_good): + html = generate_report(minimal_known_good) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + entry = json.loads(match.group(1))[0] + assert entry["name"] == "score_baselibs" + assert entry["group"] == "target_sw" + assert entry["repo"] == "https://github.com/eclipse-score/baselibs.git" + assert entry["owner_repo"] == "eclipse-score/baselibs" + assert entry["hash"] == "abc123def456abc123def456abc123def456abc123" + assert entry["branch"] == "main" + assert entry["version"] is None + + +# --------------------------------------------------------------------------- +# generate_report – GitHub API integration in HTML +# --------------------------------------------------------------------------- + + +class TestGenerateReportGitHubIntegration: + def test_github_api_url_pattern_present(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "api.github.com" in html + + def test_compare_endpoint_referenced(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "/compare/" in html + + def test_commits_endpoint_referenced(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "/commits/" in html + + def test_ahead_by_field_referenced(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "ahead_by" in html + + def test_token_stored_in_localstorage(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "gh_token" in html + assert "localStorage" in html + + def test_cache_ttl_present(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "CACHE_TTL_MS" in html + + def test_cache_functions_present(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "loadFromCache" in html + assert "saveToCache" in html + + def test_no_unauthenticated_api_calls(self, minimal_known_good): + html = generate_report(minimal_known_good) + # Token is always attached — no conditional empty headers + assert "headers: {}" not in html + assert "authHeaders()" in html + + def test_no_token_shows_requires_pat(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "requires PAT" in html + + def test_token_input_always_present(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "gh-token" in html + + def test_no_cooldown_logic(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "remainingCooldownMs" not in html + assert "startCooldownUI" not in html + + def test_no_oauth_code(self, minimal_known_good): + html = generate_report(minimal_known_good) + assert "CLIENT_ID" not in html + assert "startOAuth" not in html + assert "sha256base64url" not in html + assert "pkce_state" not in html + + +# --------------------------------------------------------------------------- +# generate_report – multi-group +# --------------------------------------------------------------------------- + + +class TestGenerateReportMultiGroup: + def test_all_modules_in_json(self, multi_group_known_good): + html = generate_report(multi_group_known_good) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + data = json.loads(match.group(1)) + names = {e["name"] for e in data} + assert "score_baselibs" in names + assert "score_crates" in names + + def test_both_groups_present(self, multi_group_known_good): + html = generate_report(multi_group_known_good) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + data = json.loads(match.group(1)) + groups = {e["group"] for e in data} + assert groups == {"target_sw", "tooling"} + + def test_filter_buttons_in_html(self, multi_group_known_good): + html = generate_report(multi_group_known_good) + assert "target_sw" in html + assert "tooling" in html + + +# --------------------------------------------------------------------------- +# generate_report – edge cases +# --------------------------------------------------------------------------- + + +class TestGenerateReportEdgeCases: + def test_module_with_no_metadata(self): + module = Module.from_dict("score_crates", { + "repo": "https://github.com/eclipse-score/score-crates.git", + "hash": "deadbeef", + }) + kg = KnownGood(modules={"tooling": {"score_crates": module}}, timestamp="") + html = generate_report(kg) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + entry = json.loads(match.group(1))[0] + assert entry["owner_repo"] == "eclipse-score/score-crates" + + def test_non_github_repo_owner_repo_is_none(self): + module = Module.from_dict("custom_mod", { + "repo": "https://gitlab.com/some/repo.git", + "hash": "abc", + }) + kg = KnownGood(modules={"g": {"custom_mod": module}}, timestamp="") + html = generate_report(kg) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + entry = json.loads(match.group(1))[0] + assert entry["owner_repo"] is None + + def test_empty_modules(self): + kg = KnownGood(modules={}, timestamp="2026-01-01") + html = generate_report(kg) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + assert json.loads(match.group(1)) == [] + + +# --------------------------------------------------------------------------- +# write_report +# --------------------------------------------------------------------------- + + +class TestWriteReport: + def test_creates_file(self, tmp_path, minimal_known_good): + out = tmp_path / "report.html" + write_report(minimal_known_good, out) + assert out.exists() + + def test_file_content_matches_generate(self, tmp_path, minimal_known_good): + out = tmp_path / "report.html" + write_report(minimal_known_good, out) + assert out.read_text(encoding="utf-8") == generate_report(minimal_known_good) + + def test_creates_parent_dirs_via_path(self, tmp_path, minimal_known_good): + out = tmp_path / "sub" / "report.html" + out.parent.mkdir(parents=True) + write_report(minimal_known_good, out) + assert out.exists() + + +# --------------------------------------------------------------------------- +# Integration: real known_good.json +# --------------------------------------------------------------------------- + + +@pytest.mark.skipif(not KNOWN_GOOD_JSON.exists(), reason="known_good.json not found") +class TestReportFromRealFile: + def test_generates_without_error(self, real_known_good): + html = generate_report(real_known_good) + assert len(html) > 1000 + + def test_all_modules_embedded(self, real_known_good): + html = generate_report(real_known_good) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + data = json.loads(match.group(1)) + total = sum(len(g) for g in real_known_good.modules.values()) + assert len(data) == total + + def test_all_entries_have_required_fields(self, real_known_good): + html = generate_report(real_known_good) + match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) + for entry in json.loads(match.group(1)): + assert "name" in entry + assert "group" in entry + assert "repo" in entry + assert "hash" in entry + assert "branch" in entry + assert "owner_repo" in entry diff --git a/scripts/tooling/uv.lock b/scripts/tooling/uv.lock new file mode 100644 index 00000000000..eb6dde4bc51 --- /dev/null +++ b/scripts/tooling/uv.lock @@ -0,0 +1,158 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "tooling" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "jinja2" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "jinja2", specifier = ">=3" }] + +[package.metadata.requires-dev] +dev = [{ name = "pytest", specifier = ">=8" }] From ded9733eefc501618371066f75820d2837ffb40e Mon Sep 17 00:00:00 2001 From: Piotr Korkus Date: Thu, 5 Mar 2026 16:37:38 +0100 Subject: [PATCH 02/13] add bazel targets --- bazel_common/score_python.MODULE.bazel | 10 ++ scripts/tooling/BUILD | 64 +++++++++++ scripts/tooling/pyproject.toml | 1 + scripts/tooling/requirements.in | 1 + scripts/tooling/requirements.txt | 146 +++++++++++++++++++++++++ 5 files changed, 222 insertions(+) create mode 100644 scripts/tooling/BUILD create mode 100644 scripts/tooling/requirements.in create mode 100644 scripts/tooling/requirements.txt diff --git a/bazel_common/score_python.MODULE.bazel b/bazel_common/score_python.MODULE.bazel index 994e9a6af24..79d62fc89ab 100644 --- a/bazel_common/score_python.MODULE.bazel +++ b/bazel_common/score_python.MODULE.bazel @@ -11,6 +11,7 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* bazel_dep(name = "rules_python", version = "1.8.3") +bazel_dep(name = "aspect_rules_py", version = "1.4.0") PYTHON_VERSION = "3.12" @@ -28,3 +29,12 @@ pip.parse( requirements_lock = "//feature_integration_tests/test_cases:requirements.txt.lock", ) use_repo(pip, "pip_score_venv_test") + +pip.parse( + envsubst = ["PIP_INDEX_URL"], + extra_pip_args = ["--index-url=${PIP_INDEX_URL:-https://pypi.org/simple/}"], + hub_name = "ref_int_scripts_env", + python_version = PYTHON_VERSION, + requirements_lock = "//scripts/tooling:requirements.txt", +) +use_repo(pip, "ref_int_scripts_env") diff --git a/scripts/tooling/BUILD b/scripts/tooling/BUILD new file mode 100644 index 00000000000..57e29269f98 --- /dev/null +++ b/scripts/tooling/BUILD @@ -0,0 +1,64 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("@pip_score_venv_test//:requirements.bzl", "all_requirements") +load("@rules_python//python:defs.bzl", "py_binary", "py_library") +load("@rules_python//python:pip.bzl", "compile_pip_requirements") +load("@score_tooling//python_basics:defs.bzl", "score_py_pytest") + +# In order to update the requirements, change the `requirements.in` file and run: +# `bazel run //src:requirements.update`. +# This will update the `requirements.txt` file. +# To upgrade all dependencies to their latest versions, run: +# `bazel run //src:requirements.update -- --upgrade`. +compile_pip_requirements( + name = "requirements", + srcs = [ + "requirements.in", + "@score_tooling//python_basics:requirements.txt", + ], + requirements_txt = "requirements.txt", + tags = [ + "manual", + ], +) + +# Library target +py_library( + name = "lib", + srcs = glob(["lib/**/*.py"]), + visibility = ["//visibility:public"], +) + +# CLI binary target +py_binary( + name = "tooling", + srcs = glob([ + "cli/**/*.py", + ]), + main = "cli/main.py", + visibility = ["//visibility:public"], + deps = [":lib"], +) + +# Tests target +score_py_pytest( + name = "tooling_tests", + srcs = glob(["tests/**/*.py"]), + data = [ + ":lib/assets/report_template.html", + ], + pytest_config = ":pyproject.toml", + deps = [ + ":lib", + ] + all_requirements, +) diff --git a/scripts/tooling/pyproject.toml b/scripts/tooling/pyproject.toml index d416e167cb2..012c3622808 100644 --- a/scripts/tooling/pyproject.toml +++ b/scripts/tooling/pyproject.toml @@ -14,3 +14,4 @@ dev = ["pytest>=8"] [tool.pytest.ini_options] testpaths = ["tests"] +pythonpath = ["."] diff --git a/scripts/tooling/requirements.in b/scripts/tooling/requirements.in new file mode 100644 index 00000000000..aeebe9f5150 --- /dev/null +++ b/scripts/tooling/requirements.in @@ -0,0 +1 @@ +jinja2 >= 3 diff --git a/scripts/tooling/requirements.txt b/scripts/tooling/requirements.txt new file mode 100644 index 00000000000..9b5f4c67b91 --- /dev/null +++ b/scripts/tooling/requirements.txt @@ -0,0 +1,146 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# bazel run //scripts/tooling:requirements.update +# +basedpyright==1.35.0 \ + --hash=sha256:2a7e0bd476623d48499e2b18ff6ed19dc28c51909cf9e1152ad355b5809049ad \ + --hash=sha256:4f4f84023df5a0cd4ee154916ba698596682ac98bacfa22c941ed6aaf07bba4e + # via -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt +iniconfig==2.3.0 \ + --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ + --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 + # via + # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt + # pytest +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via -r scripts/tooling/requirements.in +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via jinja2 +nodejs-wheel-binaries==24.11.1 \ + --hash=sha256:0e14874c3579def458245cdbc3239e37610702b0aa0975c1dc55e2cb80e42102 \ + --hash=sha256:10197b1c9c04d79403501766f76508b0dac101ab34371ef8a46fcf51773497d0 \ + --hash=sha256:376b9ea1c4bc1207878975dfeb604f7aa5668c260c6154dcd2af9d42f7734116 \ + --hash=sha256:413dfffeadfb91edb4d8256545dea797c237bba9b3faefea973cde92d96bb922 \ + --hash=sha256:5ef598101b0fb1c2bf643abb76dfbf6f76f1686198ed17ae46009049ee83c546 \ + --hash=sha256:78bc5bb889313b565df8969bb7423849a9c7fc218bf735ff0ce176b56b3e96f0 \ + --hash=sha256:c2741525c9874b69b3e5a6d6c9179a6fe484ea0c3d5e7b7c01121c8e5d78b7e2 \ + --hash=sha256:c79a7e43869ccecab1cae8183778249cceb14ca2de67b5650b223385682c6239 \ + --hash=sha256:cde41d5e4705266688a8d8071debf4f8a6fcea264c61292782672ee75a6905f9 + # via + # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt + # basedpyright +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt + # pytest +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via + # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt + # pytest +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via + # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt + # pytest +pytest==9.0.1 \ + --hash=sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8 \ + --hash=sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad + # via -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt From afc03a1cc7fcac73ee27bb96dd8113d8b5c7ed9f Mon Sep 17 00:00:00 2001 From: Piotr Korkus Date: Fri, 6 Mar 2026 11:08:10 +0100 Subject: [PATCH 03/13] cleanup scripts --- pyproject.toml | 2 +- .../known_good_to_workspace_metadata.py | 4 +- scripts/known_good/models/__init__.py | 2 +- scripts/known_good/models/known_good.py | 48 ++- scripts/known_good/models/module.py | 69 ++-- .../known_good/override_known_good_repo.py | 20 +- scripts/known_good/requirements.txt | 1 - .../update_module_from_known_good.py | 63 ++-- scripts/known_good/update_module_latest.py | 18 +- scripts/tooling/pyproject.toml | 4 +- scripts/tooling/requirements.in | 1 + scripts/tooling/requirements.txt | 318 ++++++++++++++++-- scripts/tooling/uv.lock | 300 ++++++++++++++++- 13 files changed, 713 insertions(+), 137 deletions(-) delete mode 100644 scripts/known_good/requirements.txt diff --git a/pyproject.toml b/pyproject.toml index 2317e7d70ee..d574c803c77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ select = [ # pyupgrade "UP", ] -ignore = ["F401", "PTH123", "ARG002", "T201"] +ignore = ["F401", "PTH123", "ARG002", "T201", "TC003"] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] diff --git a/scripts/known_good/known_good_to_workspace_metadata.py b/scripts/known_good/known_good_to_workspace_metadata.py index bb933c8dcca..be1b1bae57c 100644 --- a/scripts/known_good/known_good_to_workspace_metadata.py +++ b/scripts/known_good/known_good_to_workspace_metadata.py @@ -38,9 +38,9 @@ def main(): try: known_good = load_known_good(Path(args.known_good)) except FileNotFoundError as e: - raise SystemExit(f"ERROR: {e}") + raise SystemExit(f"ERROR: {e}") from e except ValueError as e: - raise SystemExit(f"ERROR: {e}") + raise SystemExit(f"ERROR: {e}") from e modules = list(known_good.modules.values()) diff --git a/scripts/known_good/models/__init__.py b/scripts/known_good/models/__init__.py index edf7659729d..91ff1c39475 100644 --- a/scripts/known_good/models/__init__.py +++ b/scripts/known_good/models/__init__.py @@ -12,6 +12,6 @@ # ******************************************************************************* """Models for score reference integration tools.""" -from .module import Module, Metadata +from .module import Metadata, Module __all__ = ["Module", "Metadata"] diff --git a/scripts/known_good/models/known_good.py b/scripts/known_good/models/known_good.py index 48c6eaabecc..06d62aa3857 100644 --- a/scripts/known_good/models/known_good.py +++ b/scripts/known_good/models/known_good.py @@ -18,7 +18,7 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict +from typing import Any from .module import Module @@ -30,18 +30,18 @@ class KnownGood: Module structure: modules = {"group1": {"module1": Module}, "group2": {"module2": Module}} """ - modules: Dict[str, Dict[str, Module]] + modules: dict[str, dict[str, Module]] timestamp: str @classmethod - def from_dict(cls, data: Dict[str, Any]) -> KnownGood: + def from_dict(cls, data: dict[str, Any]) -> KnownGood: """Create a KnownGood instance from a dictionary. Expected structure: {"modules": {"group1": {"score_baselibs": {...}}, "group2": {"score_logging": {...}}}} Args: - data: Dictionary containing known_good.json data + data: dictionary containing known_good.json data Returns: KnownGood instance @@ -49,7 +49,7 @@ def from_dict(cls, data: Dict[str, Any]) -> KnownGood: modules_dict = data.get("modules", {}) timestamp = data.get("timestamp", "") - parsed_modules: Dict[str, Dict[str, Module]] = {} + parsed_modules: dict[str, dict[str, Module]] = {} for group_name, group_modules in modules_dict.items(): if isinstance(group_modules, dict): modules_list = Module.parse_modules(group_modules) @@ -57,7 +57,7 @@ def from_dict(cls, data: Dict[str, Any]) -> KnownGood: return cls(modules=parsed_modules, timestamp=timestamp) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Convert KnownGood instance to dictionary for JSON output. Returns: @@ -70,7 +70,7 @@ def to_dict(self) -> Dict[str, Any]: return {"modules": modules_output, "timestamp": self.timestamp} - def write(self, output_path: Path, dry_run: bool = False) -> None: + def write(self, output_path: Path, *, dry_run: bool = False) -> None: """Write known_good data to file or print for dry-run. Args: @@ -89,8 +89,7 @@ def write(self, output_path: Path, dry_run: bool = False) -> None: print(output_json, end="") print("---- END UPDATED JSON ----") else: - with open(output_path, "w", encoding="utf-8") as f: - f.write(output_json) + output_path.write_text(output_json, encoding="utf-8") print(f"Successfully wrote updated known_good.json to {output_path}") @@ -104,22 +103,21 @@ def load_known_good(path: Path) -> KnownGood: KnownGood instance with parsed modules """ - with open(path, "r", encoding="utf-8") as f: - text = f.read() - try: - data = json.loads(text) - except json.JSONDecodeError as e: - lines = text.splitlines() - line = lines[e.lineno - 1] if 0 <= e.lineno - 1 < len(lines) else "" - pointer = " " * (e.colno - 1) + "^" - - hint = "" - if "Expecting value" in e.msg: - hint = "Possible causes: trailing comma, missing value, or extra comma." - - raise ValueError( - f"Invalid JSON at line {e.lineno}, column {e.colno}\n{line}\n{pointer}\n{e.msg}. {hint}" - ) from None + try: + text = path.read_text(encoding="utf-8") + data = json.loads(text) + except json.JSONDecodeError as e: + lines = text.splitlines() + line = lines[e.lineno - 1] if 0 <= e.lineno - 1 < len(lines) else "" + pointer = " " * (e.colno - 1) + "^" + + hint = "" + if "Expecting value" in e.msg: + hint = "Possible causes: trailing comma, missing value, or extra comma." + + raise ValueError( + f"Invalid JSON at line {e.lineno}, column {e.colno}\n{line}\n{pointer}\n{e.msg}. {hint}" + ) from None if not isinstance(data, dict) or not isinstance(data.get("modules"), dict): raise ValueError(f"Invalid known_good.json at {path} (expected object with 'modules' dict)") diff --git a/scripts/known_good/models/module.py b/scripts/known_good/models/module.py index 9fa266f91b9..4b76e3c89dd 100644 --- a/scripts/known_good/models/module.py +++ b/scripts/known_good/models/module.py @@ -16,7 +16,7 @@ import logging from dataclasses import dataclass, field -from typing import Any, Dict, List +from typing import Any from urllib.parse import urlparse @@ -37,7 +37,7 @@ class Metadata: langs: list[str] = field(default_factory=lambda: ["cpp", "rust"]) @classmethod - def from_dict(cls, data: Dict[str, Any]) -> Metadata: + def from_dict(cls, data: dict[str, Any]) -> Metadata: """Create a Metadata instance from a dictionary. Args: @@ -53,7 +53,7 @@ def from_dict(cls, data: Dict[str, Any]) -> Metadata: langs=data.get("langs", ["cpp", "rust"]), ) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Convert Metadata instance to dictionary representation. Returns: @@ -79,30 +79,30 @@ class Module: pin_version: bool = False @classmethod - def from_dict(cls, name: str, module_data: Dict[str, Any]) -> Module: + def from_dict(cls, name: str, module_data: dict[str, Any]) -> Module: """Create a Module instance from a dictionary representation. Args: - name: The module name - module_data: Dictionary containing module configuration with keys: - - repo (str): Repository URL - - hash or commit (str): Commit hash - - version (str, optional): Module version (when present, hash is ignored) - - bazel_patches (list[str], optional): List of patch files for Bazel - - metadata (dict, optional): Metadata configuration - Example: { - "code_root_path": "path/to/code/root", - "extra_test_config": [""], - "exclude_test_targets": [""], - "langs": ["cpp", "rust"] - } - If not present, uses default Metadata values. - - branch (str, optional): Git branch name (default: main) - - pin_version (bool, optional): If true, module hash is not updated - to latest HEAD by update scripts (default: false) + name: The module name + module_data: Dictionary containing module configuration with keys: + - repo (str): Repository URL + - hash or commit (str): Commit hash + - version (str, optional): Module version (when present, hash is ignored) + - bazel_patches (list[str], optional): List of patch files for Bazel + - metadata (dict, optional): Metadata configuration + Example: { + "code_root_path": "path/to/code/root", + "extra_test_config": [""], + "exclude_test_targets": [""], + "langs": ["cpp", "rust"] + } + If not present, uses default Metadata values. + - branch (str, optional): Git branch name (default: main) + - pin_version (bool, optional): If true, module hash is not updated + to latest HEAD by update scripts (default: false) Returns: - Module instance + Module instance """ repo = module_data.get("repo", "") # Support both 'hash' and 'commit' keys @@ -119,17 +119,14 @@ def from_dict(cls, name: str, module_data: Dict[str, Any]) -> Module: # Parse metadata - if not present or is None/empty dict, use defaults metadata_data = module_data.get("metadata") - if metadata_data is not None: - metadata = Metadata.from_dict(metadata_data) - # Enable once we are able to remove '*' in known_good.json - # if any("*" in target for target in metadata.exclude_test_targets): - # raise Exception( - # f"Module {name} has wildcard '*' in exclude_test_targets, which is not allowed. " - # "Please specify explicit test targets to exclude or remove the key if no exclusions are needed." - # ) - else: - # If metadata key is missing, create with defaults - metadata = Metadata() + metadata = Metadata.from_dict(metadata_data) if metadata_data is not None else Metadata() + + # Enable once we are able to remove '*' in known_good.json + # if any("*" in target for target in metadata.exclude_test_targets): + # raise Exception( + # f"Module {name} has wildcard '*' in exclude_test_targets, which is not allowed. " + # "Please specify explicit test targets to exclude or remove the key if no exclusions are needed." + # ) branch = module_data.get("branch", "main") pin_version = module_data.get("pin_version", False) @@ -146,7 +143,7 @@ def from_dict(cls, name: str, module_data: Dict[str, Any]) -> Module: ) @classmethod - def parse_modules(cls, modules_dict: Dict[str, Any]) -> List[Module]: + def parse_modules(cls, modules_dict: dict[str, Any]) -> list[Module]: """Parse modules dictionary into Module dataclass instances. Args: @@ -187,13 +184,13 @@ def owner_repo(self) -> str: return f"{parts[0]}/{parts[1]}" - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Convert Module instance to dictionary representation for JSON output. Returns: Dictionary with module configuration """ - result: Dict[str, Any] = {"repo": self.repo} + result: dict[str, Any] = {"repo": self.repo} if self.version: result["version"] = self.version else: diff --git a/scripts/known_good/override_known_good_repo.py b/scripts/known_good/override_known_good_repo.py index 218d360d0a3..033c3f34eb4 100755 --- a/scripts/known_good/override_known_good_repo.py +++ b/scripts/known_good/override_known_good_repo.py @@ -26,12 +26,10 @@ """ import argparse -import os -import re import datetime as dt -from pathlib import Path -from typing import Dict, List import logging +import re +from pathlib import Path from models import Module from models.known_good import KnownGood, load_known_good @@ -40,7 +38,7 @@ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") -def parse_and_apply_overrides(modules: Dict[str, Module], repo_overrides: List[str]) -> int: +def parse_and_apply_overrides(modules: dict[str, Module], repo_overrides: list[str]) -> int: """ Parse repo override arguments and apply them to modules. @@ -132,7 +130,7 @@ def parse_and_apply_overrides(modules: Dict[str, Module], repo_overrides: List[s return overrides_applied -def apply_overrides(known_good: KnownGood, repo_overrides: List[str]) -> KnownGood: +def apply_overrides(known_good: KnownGood, repo_overrides: list[str]) -> KnownGood: """Apply repository commit overrides to the known_good data. Args: @@ -209,17 +207,17 @@ def main() -> None: if args.verbose: logging.getLogger().setLevel(logging.DEBUG) - known_path = os.path.abspath(args.known) - output_path = os.path.abspath(args.output) + known_path = Path(args.known).resolve() + output_path = Path(args.output).resolve() # Load, update, and output logging.info(f"Loading {known_path}") try: known_good = load_known_good(known_path) except FileNotFoundError as e: - raise SystemExit(f"ERROR: {e}") + raise SystemExit(f"ERROR: {e}") from e except ValueError as e: - raise SystemExit(f"ERROR: {e}") + raise SystemExit(f"ERROR: {e}") from e if not args.module_overrides: parser.error("at least one --module-override is required") @@ -227,7 +225,7 @@ def main() -> None: overrides = args.module_overrides updated_known_good = apply_overrides(known_good, overrides) - updated_known_good.write(Path(output_path), args.dry_run) + updated_known_good.write(Path(output_path), dry_run=args.dry_run) if __name__ == "__main__": diff --git a/scripts/known_good/requirements.txt b/scripts/known_good/requirements.txt deleted file mode 100644 index 1c871cd93f8..00000000000 --- a/scripts/known_good/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -PyGithub>=2.1.1 diff --git a/scripts/known_good/update_module_from_known_good.py b/scripts/known_good/update_module_from_known_good.py index e6c82fe3cb3..d585fe514e6 100755 --- a/scripts/known_good/update_module_from_known_good.py +++ b/scripts/known_good/update_module_from_known_good.py @@ -33,7 +33,7 @@ import os import re from pathlib import Path -from typing import Dict, List, Optional +from typing import Optional from models import Module from models.known_good import load_known_good @@ -42,7 +42,7 @@ logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s") -def generate_git_override_blocks(modules: List[Module], repo_commit_dict: Dict[str, str]) -> List[str]: +def generate_git_override_blocks(modules: list[Module], repo_commit_dict: dict[str, str]) -> list[str]: """Generate bazel_dep and git_override blocks for each module.""" blocks = [] @@ -109,7 +109,7 @@ def generate_git_override_blocks(modules: List[Module], repo_commit_dict: Dict[s return blocks -def generate_local_override_blocks(modules: List[Module]) -> List[str]: +def generate_local_override_blocks(modules: list[Module]) -> list[str]: """Generate bazel_dep and local_path_override blocks for each module.""" blocks = [] @@ -127,7 +127,7 @@ def generate_local_override_blocks(modules: List[Module]) -> List[str]: return blocks -def generate_coverage_blocks(modules: List[Module]) -> List[str]: +def generate_coverage_blocks(modules: list[Module]) -> list[str]: """Generate rust_coverage_report blocks for each module with rust impl.""" blocks = ["""load("@score_tooling//:defs.bzl", "rust_coverage_report")"""] @@ -160,8 +160,8 @@ def generate_coverage_blocks(modules: List[Module]) -> List[str]: def generate_file_content( args: argparse.Namespace, - modules: List[Module], - repo_commit_dict: Dict[str, str], + modules: list[Module], + repo_commit_dict: dict[str, str], timestamp: Optional[str] = None, file_type: str = "module", ) -> str: @@ -206,6 +206,20 @@ def generate_file_content( return header + "\n".join(blocks) +def parse_repo_commit_overrides(repo_overrides: list[str]) -> dict[str, str]: + repo_commit_dict = {} + repo_pattern = re.compile(r"https://[a-zA-Z0-9.-]+/[a-zA-Z0-9._/-]+\.git@[a-fA-F0-9]{7,40}$") + for entry in repo_overrides: + if not repo_pattern.match(entry): + raise SystemExit( + f"Invalid --repo-override format: {entry}\n" + "Expected format: https://github.com/org/repo.git@" + ) + repo_url, commit_hash = entry.split("@", 1) + repo_commit_dict[repo_url] = commit_hash + return repo_commit_dict + + def main() -> None: parser = argparse.ArgumentParser( description="Generate score_modules.MODULE.bazel file(s) from known_good.json", @@ -234,16 +248,19 @@ def main() -> None: parser.add_argument( "--known", default=Path(__file__).parents[2] / "known_good.json", + type=Path, help="Path to known_good.json (default: known_good.json in repo root)", ) parser.add_argument( "--output-dir-modules", default=Path(__file__).parents[2] / "bazel_common", + type=Path, help="Output directory for grouped structure files (default: bazel_common in repo root)", ) parser.add_argument( "--output-dir-coverage", default=Path(__file__).parents[2] / "rust_coverage", + type=Path, help="Output directory for BUILD coverage file (default: rust_coverage in repo root)", ) parser.add_argument( @@ -269,38 +286,28 @@ def main() -> None: if args.verbose: logging.getLogger().setLevel(logging.INFO) - known_path = os.path.abspath(args.known) + known_path = (args.known).resolve() - if not os.path.exists(known_path): + if not known_path.exists(): raise SystemExit(f"known_good.json not found at {known_path}") # Parse repo overrides - repo_commit_dict = {} - if args.repo_override: - repo_pattern = re.compile(r"https://[a-zA-Z0-9.-]+/[a-zA-Z0-9._/-]+\.git@[a-fA-F0-9]{7,40}$") - for entry in args.repo_override: - if not repo_pattern.match(entry): - raise SystemExit( - f"Invalid --repo-override format: {entry}\n" - "Expected format: https://github.com/org/repo.git@" - ) - repo_url, commit_hash = entry.split("@", 1) - repo_commit_dict[repo_url] = commit_hash + repo_commit_dict = parse_repo_commit_overrides(args.repo_override) if args.repo_override else {} # Load known_good.json try: - known_good = load_known_good(Path(known_path)) + known_good = load_known_good(known_path) except FileNotFoundError as e: - raise SystemExit(f"ERROR: {e}") + raise SystemExit(f"ERROR: {e}") from e except ValueError as e: - raise SystemExit(f"ERROR: {e}") + raise SystemExit(f"ERROR: {e}") from e if not known_good.modules: raise SystemExit("No modules found in known_good.json") # Generate files based on structure (flat vs grouped) - output_dir_modules = os.path.abspath(args.output_dir_modules) - os.makedirs(output_dir_modules, exist_ok=True) + output_dir_modules = args.output_dir_modules.resolve() + output_dir_modules.mkdir(parents=True, exist_ok=True) generated_files = [] total_module_count = 0 @@ -315,7 +322,7 @@ def main() -> None: # Determine output filename: score_modules_{group}.MODULE.bazel output_filename = f"score_modules_{group_name}.MODULE.bazel" - output_path_modules = os.path.join(output_dir_modules, output_filename) + output_path_modules = output_dir_modules / output_filename output_path_coverage = args.output_dir_coverage / "BUILD" # Generate file content of MODULE files @@ -330,8 +337,7 @@ def main() -> None: print("---- END GENERATED CONTENT FOR MODULE ----") print(f"\nGenerated {len(modules)} {args.override_type}_override entries for group '{group_name}'") else: - with open(output_path_modules, "w", encoding="utf-8") as f: - f.write(content_module) + output_path_modules.write_text(content_module, encoding="utf-8") generated_files.append(output_path_modules) total_module_count += len(modules) print(f"Generated {output_path_modules} with {len(modules)} {args.override_type}_override entries") @@ -349,8 +355,7 @@ def main() -> None: print("---- END GENERATED CONTENT FOR BUILD ----") print(f"\nGenerated {len(modules)} {args.override_type}_override entries for group '{group_name}'") else: - with open(output_path_coverage, "w", encoding="utf-8") as f: - f.write(content_build) + output_path_coverage.write_text(content_build, encoding="utf-8") generated_files.append(output_path_coverage) print(f"Generated {output_path_coverage}") diff --git a/scripts/known_good/update_module_latest.py b/scripts/known_good/update_module_latest.py index 33e238c4f17..4c1f50247b6 100755 --- a/scripts/known_good/update_module_latest.py +++ b/scripts/known_good/update_module_latest.py @@ -22,8 +22,8 @@ Usage: python tools/update_module_latest.py \ - --known-good score_reference_integration/known_good.json \ - [--branch main] [--output updated_known_good.json] + --known-good score_reference_integration/known_good.json \ + [--branch main] [--output updated_known_good.json] Environment: Optionally set GITHUB_TOKEN to increase rate limits / access private repos. @@ -37,10 +37,10 @@ from __future__ import annotations import argparse -import shutil -import subprocess import json import os +import shutil +import subprocess import sys from pathlib import Path @@ -102,16 +102,18 @@ def parse_args(argv: list[str]) -> argparse.Namespace: p = argparse.ArgumentParser(description="Update module hashes to latest commit on branch") p.add_argument( "--known-good", - default="known_good.json", + default=Path(__file__).parents[2] / "known_good.json", + type=Path, help="Path to known_good.json file (default: known_good.json in CWD)", ) p.add_argument("--branch", default="main", help="Git branch to fetch latest commits from (default: main)") - p.add_argument("--output", help="Optional output path to write updated JSON") + p.add_argument("--output", type=Path, help="Optional output path to write updated JSON") p.add_argument("--fail-fast", action="store_true", help="Stop on first failure instead of continuing") p.add_argument( "--no-gh", action="store_true", - help="Disable GitHub CLI usage even if installed; fall back to HTTP API; GITHUB_TOKEN has to be known in the environment", + help="Disable GitHub CLI usage even if installed; fall back to HTTP API; " + "GITHUB_TOKEN has to be known in the environment", ) return p.parse_args(argv) @@ -119,7 +121,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: def main(argv: list[str]) -> int: args = parse_args(argv) try: - known_good = load_known_good(Path(args.known_good)) + known_good = load_known_good(Path(args.known_good).resolve()) except FileNotFoundError as e: print(f"ERROR: {e}", file=sys.stderr) return 3 diff --git a/scripts/tooling/pyproject.toml b/scripts/tooling/pyproject.toml index 012c3622808..a228f2506cf 100644 --- a/scripts/tooling/pyproject.toml +++ b/scripts/tooling/pyproject.toml @@ -4,13 +4,13 @@ version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" -dependencies = ["jinja2>=3"] +dependencies = ["jinja2>=3", "PyGithub>=2.1.1"] [project.scripts] tooling = "cli.main:main" [dependency-groups] -dev = ["pytest>=8"] +dev = ["pytest>=9"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/scripts/tooling/requirements.in b/scripts/tooling/requirements.in index aeebe9f5150..c6057dcbb85 100644 --- a/scripts/tooling/requirements.in +++ b/scripts/tooling/requirements.in @@ -1 +1,2 @@ jinja2 >= 3 +PyGithub>=2.1.1 diff --git a/scripts/tooling/requirements.txt b/scripts/tooling/requirements.txt index 9b5f4c67b91..b4eff3ee51c 100644 --- a/scripts/tooling/requirements.txt +++ b/scripts/tooling/requirements.txt @@ -7,17 +7,267 @@ basedpyright==1.35.0 \ --hash=sha256:2a7e0bd476623d48499e2b18ff6ed19dc28c51909cf9e1152ad355b5809049ad \ --hash=sha256:4f4f84023df5a0cd4ee154916ba698596682ac98bacfa22c941ed6aaf07bba4e - # via -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt +certifi==2026.2.25 \ + --hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \ + --hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7 +cffi==2.0.0 \ + --hash=sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb \ + --hash=sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b \ + --hash=sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f \ + --hash=sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9 \ + --hash=sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44 \ + --hash=sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2 \ + --hash=sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c \ + --hash=sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75 \ + --hash=sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65 \ + --hash=sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e \ + --hash=sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a \ + --hash=sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e \ + --hash=sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25 \ + --hash=sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a \ + --hash=sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe \ + --hash=sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b \ + --hash=sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91 \ + --hash=sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592 \ + --hash=sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187 \ + --hash=sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c \ + --hash=sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1 \ + --hash=sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94 \ + --hash=sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba \ + --hash=sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb \ + --hash=sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165 \ + --hash=sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529 \ + --hash=sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca \ + --hash=sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c \ + --hash=sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6 \ + --hash=sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c \ + --hash=sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0 \ + --hash=sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743 \ + --hash=sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63 \ + --hash=sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5 \ + --hash=sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5 \ + --hash=sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4 \ + --hash=sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d \ + --hash=sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b \ + --hash=sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93 \ + --hash=sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205 \ + --hash=sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27 \ + --hash=sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512 \ + --hash=sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d \ + --hash=sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c \ + --hash=sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037 \ + --hash=sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26 \ + --hash=sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322 \ + --hash=sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb \ + --hash=sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c \ + --hash=sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8 \ + --hash=sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4 \ + --hash=sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414 \ + --hash=sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9 \ + --hash=sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664 \ + --hash=sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9 \ + --hash=sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775 \ + --hash=sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739 \ + --hash=sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc \ + --hash=sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062 \ + --hash=sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe \ + --hash=sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9 \ + --hash=sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92 \ + --hash=sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5 \ + --hash=sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13 \ + --hash=sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d \ + --hash=sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26 \ + --hash=sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f \ + --hash=sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495 \ + --hash=sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b \ + --hash=sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6 \ + --hash=sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c \ + --hash=sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef \ + --hash=sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5 \ + --hash=sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18 \ + --hash=sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad \ + --hash=sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3 \ + --hash=sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7 \ + --hash=sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5 \ + --hash=sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534 \ + --hash=sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49 \ + --hash=sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2 \ + --hash=sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5 \ + --hash=sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453 \ + --hash=sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf +charset-normalizer==3.4.5 \ + --hash=sha256:014837af6fabf57121b6254fa8ade10dceabc3528b27b721a64bbc7b8b1d4eb4 \ + --hash=sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66 \ + --hash=sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54 \ + --hash=sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05 \ + --hash=sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765 \ + --hash=sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064 \ + --hash=sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819 \ + --hash=sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e \ + --hash=sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412 \ + --hash=sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc \ + --hash=sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e \ + --hash=sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281 \ + --hash=sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af \ + --hash=sha256:14498a429321de554b140013142abe7608f9d8ccc04d7baf2ad60498374aefa2 \ + --hash=sha256:149ec69866c3d6c2fb6f758dbc014ecb09f30b35a5ca90b6a8a2d4e54e18fdfe \ + --hash=sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8 \ + --hash=sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262 \ + --hash=sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac \ + --hash=sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85 \ + --hash=sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c \ + --hash=sha256:1f2da5cbb9becfcd607757a169e38fb82aa5fd86fae6653dea716e7b613fe2cf \ + --hash=sha256:259cd1ca995ad525f638e131dbcc2353a586564c038fc548a3fe450a91882139 \ + --hash=sha256:2820a98460c83663dd8ec015d9ddfd1e4879f12e06bb7d0500f044fb477d2770 \ + --hash=sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d \ + --hash=sha256:2b970382e4a36bed897c19f310f31d7d13489c11b4f468ddfba42d41cddfb918 \ + --hash=sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3 \ + --hash=sha256:30987f4a8ed169983f93e1be8ffeea5214a779e27ed0b059835c7afe96550ad7 \ + --hash=sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39 \ + --hash=sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d \ + --hash=sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990 \ + --hash=sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765 \ + --hash=sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1 \ + --hash=sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa \ + --hash=sha256:4b8551b6e6531e156db71193771c93bda78ffc4d1e6372517fe58ad3b91e4659 \ + --hash=sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d \ + --hash=sha256:50bcbca6603c06a1dcc7b056ed45c37715fb5d2768feb3bcd37d2313c587a5b9 \ + --hash=sha256:530beedcec9b6e027e7a4b6ce26eed36678aa39e17da85e6e03d7bd9e8e9d7c9 \ + --hash=sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2 \ + --hash=sha256:573ef5814c4b7c0d59a7710aa920eaaaef383bd71626aa420fba27b5cab92e8d \ + --hash=sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475 \ + --hash=sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c \ + --hash=sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81 \ + --hash=sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67 \ + --hash=sha256:5fea359734b140d0d6741189fea5478c6091b54ffc69d7ce119e0a05637d8c99 \ + --hash=sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5 \ + --hash=sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694 \ + --hash=sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf \ + --hash=sha256:65b3c403a5b6b8034b655e7385de4f72b7b244869a22b32d4030b99a60593eca \ + --hash=sha256:66dee73039277eb35380d1b82cccc69cc82b13a66f9f4a18da32d573acf02b7c \ + --hash=sha256:708c7acde173eedd4bfa4028484426ba689d2103b28588c513b9db2cd5ecde9c \ + --hash=sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636 \ + --hash=sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f \ + --hash=sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02 \ + --hash=sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497 \ + --hash=sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f \ + --hash=sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2 \ + --hash=sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d \ + --hash=sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873 \ + --hash=sha256:82cc7c2ad42faec8b574351f8bc2a0c049043893853317bd9bb309f5aba6cb5a \ + --hash=sha256:8a28afb04baa55abf26df544e3e5c6534245d3daa5178bc4a8eeb48202060d0e \ + --hash=sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1 \ + --hash=sha256:8ce11cd4d62d11166f2b441e30ace226c19a3899a7cf0796f668fba49a9fb123 \ + --hash=sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550 \ + --hash=sha256:92263f7eca2f4af326cd20de8d16728d2602f7cfea02e790dcde9d83c365d7cc \ + --hash=sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36 \ + --hash=sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644 \ + --hash=sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4 \ + --hash=sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0 \ + --hash=sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e \ + --hash=sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f \ + --hash=sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4 \ + --hash=sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98 \ + --hash=sha256:aa2f963b4da26daf46231d9b9e0e2c9408a751f8f0d0f44d2de56d3caf51d294 \ + --hash=sha256:aa92ec1102eaff840ccd1021478af176a831f1bccb08e526ce844b7ddda85c22 \ + --hash=sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23 \ + --hash=sha256:ae8b03427410731469c4033934cf473426faff3e04b69d2dfb64a4281a3719f8 \ + --hash=sha256:afca7f78067dd27c2b848f1b234623d26b87529296c6c5652168cc1954f2f3b2 \ + --hash=sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362 \ + --hash=sha256:b3e71afc578b98512bfe7bdb822dd6bc57d4b0093b4b6e5487c1e96ad4ace242 \ + --hash=sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4 \ + --hash=sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95 \ + --hash=sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d \ + --hash=sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94 \ + --hash=sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6 \ + --hash=sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2 \ + --hash=sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4 \ + --hash=sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8 \ + --hash=sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e \ + --hash=sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a \ + --hash=sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce \ + --hash=sha256:d29dd9c016f2078b43d0c357511e87eee5b05108f3dd603423cb389b89813969 \ + --hash=sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f \ + --hash=sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923 \ + --hash=sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6 \ + --hash=sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee \ + --hash=sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6 \ + --hash=sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467 \ + --hash=sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f \ + --hash=sha256:e22d1059b951e7ae7c20ef6b06afd10fb95e3c41bf3c4fbc874dba113321c193 \ + --hash=sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7 \ + --hash=sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9 \ + --hash=sha256:e545b51da9f9af5c67815ca0eb40676c0f016d0b0381c86f20451e35696c5f95 \ + --hash=sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763 \ + --hash=sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7 \ + --hash=sha256:ec56a2266f32bc06ed3c3e2a8f58417ce02f7e0356edc89786e52db13c593c98 \ + --hash=sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60 \ + --hash=sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade \ + --hash=sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c \ + --hash=sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2 \ + --hash=sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f \ + --hash=sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a \ + --hash=sha256:fc1c64934b8faf7584924143eb9db4770bbdb16659626e1a1a4d9efbcb68d947 \ + --hash=sha256:ff95a9283de8a457e6b12989de3f9f5193430f375d64297d323a615ea52cbdb3 +cryptography==46.0.5 \ + --hash=sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72 \ + --hash=sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235 \ + --hash=sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9 \ + --hash=sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356 \ + --hash=sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257 \ + --hash=sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad \ + --hash=sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4 \ + --hash=sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c \ + --hash=sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614 \ + --hash=sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed \ + --hash=sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31 \ + --hash=sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229 \ + --hash=sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0 \ + --hash=sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731 \ + --hash=sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b \ + --hash=sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4 \ + --hash=sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4 \ + --hash=sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263 \ + --hash=sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595 \ + --hash=sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1 \ + --hash=sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678 \ + --hash=sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48 \ + --hash=sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76 \ + --hash=sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0 \ + --hash=sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18 \ + --hash=sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d \ + --hash=sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d \ + --hash=sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1 \ + --hash=sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981 \ + --hash=sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7 \ + --hash=sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82 \ + --hash=sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2 \ + --hash=sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4 \ + --hash=sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663 \ + --hash=sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c \ + --hash=sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d \ + --hash=sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a \ + --hash=sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a \ + --hash=sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d \ + --hash=sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b \ + --hash=sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a \ + --hash=sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826 \ + --hash=sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee \ + --hash=sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9 \ + --hash=sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648 \ + --hash=sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da \ + --hash=sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2 \ + --hash=sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2 \ + --hash=sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87 +idna==3.11 \ + --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ + --hash=sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902 iniconfig==2.3.0 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 - # via - # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt - # pytest jinja2==3.1.6 \ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 - # via -r scripts/tooling/requirements.in markupsafe==3.0.3 \ --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ @@ -108,7 +358,6 @@ markupsafe==3.0.3 \ --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 - # via jinja2 nodejs-wheel-binaries==24.11.1 \ --hash=sha256:0e14874c3579def458245cdbc3239e37610702b0aa0975c1dc55e2cb80e42102 \ --hash=sha256:10197b1c9c04d79403501766f76508b0dac101ab34371ef8a46fcf51773497d0 \ @@ -119,28 +368,59 @@ nodejs-wheel-binaries==24.11.1 \ --hash=sha256:c2741525c9874b69b3e5a6d6c9179a6fe484ea0c3d5e7b7c01121c8e5d78b7e2 \ --hash=sha256:c79a7e43869ccecab1cae8183778249cceb14ca2de67b5650b223385682c6239 \ --hash=sha256:cde41d5e4705266688a8d8071debf4f8a6fcea264c61292782672ee75a6905f9 - # via - # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt - # basedpyright packaging==25.0 \ --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f - # via - # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt - # pytest pluggy==1.6.0 \ --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - # via - # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt - # pytest +pycparser==3.0 \ + --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ + --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 +pygithub==2.8.1 \ + --hash=sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0 \ + --hash=sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9 pygments==2.19.2 \ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b - # via - # -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt - # pytest +pyjwt[crypto]==2.11.0 \ + --hash=sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623 \ + --hash=sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469 +pynacl==1.6.2 \ + --hash=sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c \ + --hash=sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574 \ + --hash=sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4 \ + --hash=sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130 \ + --hash=sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b \ + --hash=sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590 \ + --hash=sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444 \ + --hash=sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634 \ + --hash=sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87 \ + --hash=sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa \ + --hash=sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594 \ + --hash=sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0 \ + --hash=sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e \ + --hash=sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c \ + --hash=sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0 \ + --hash=sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c \ + --hash=sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577 \ + --hash=sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145 \ + --hash=sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88 \ + --hash=sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14 \ + --hash=sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6 \ + --hash=sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465 \ + --hash=sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0 \ + --hash=sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2 \ + --hash=sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9 pytest==9.0.1 \ --hash=sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8 \ --hash=sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad - # via -r /home/pko/.cache/bazel/_bazel_pko/2af2ff4db91047a487505aa8f91f8f54/external/score_tooling+/python_basics/requirements.txt +requests==2.32.5 \ + --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ + --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf +typing-extensions==4.15.0 \ + --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ + --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 +urllib3==2.6.3 \ + --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \ + --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4 diff --git a/scripts/tooling/uv.lock b/scripts/tooling/uv.lock index eb6dde4bc51..0294f7b48d8 100644 --- a/scripts/tooling/uv.lock +++ b/scripts/tooling/uv.lock @@ -2,6 +2,129 @@ version = 1 revision = 3 requires-python = ">=3.12" +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, + { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, + { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, + { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, + { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, + { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, + { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, + { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, + { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, + { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, + { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, + { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, + { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, + { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, + { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, + { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, + { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, + { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, + { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -11,6 +134,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "cryptography" +version = "46.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -113,6 +298,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygithub" +version = "2.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyjwt", extra = ["crypto"] }, + { name = "pynacl" }, + { name = "requests" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -122,6 +332,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, + { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, +] + [[package]] name = "pytest" version = "9.0.2" @@ -138,12 +397,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + [[package]] name = "tooling" version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "jinja2" }, + { name = "pygithub" }, ] [package.dev-dependencies] @@ -152,7 +427,28 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "jinja2", specifier = ">=3" }] +requires-dist = [ + { name = "jinja2", specifier = ">=3" }, + { name = "pygithub", specifier = ">=2.1.1" }, +] [package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=8" }] +dev = [{ name = "pytest", specifier = ">=9" }] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] From 774c6517673e0a1360567a79aaea571450dc7224 Mon Sep 17 00:00:00 2001 From: Piotr Korkus Date: Fri, 6 Mar 2026 13:31:43 +0100 Subject: [PATCH 04/13] remove uv and duplicated files --- BUILD | 1 + pyproject.toml | 6 + scripts/tooling/.python-version | 1 - scripts/tooling/BUILD | 2 +- scripts/tooling/README.md | 16 ++ scripts/tooling/pyproject.toml | 17 -- scripts/tooling/uv.lock | 454 -------------------------------- 7 files changed, 24 insertions(+), 473 deletions(-) delete mode 100644 scripts/tooling/.python-version delete mode 100644 scripts/tooling/pyproject.toml delete mode 100644 scripts/tooling/uv.lock diff --git a/BUILD b/BUILD index 12c556345e6..6ef3b7969df 100644 --- a/BUILD +++ b/BUILD @@ -67,4 +67,5 @@ use_format_targets() exports_files([ "MODULE.bazel", + "pyproject.toml", ]) diff --git a/pyproject.toml b/pyproject.toml index d574c803c77..f548dfbe698 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,9 @@ +[tool.pytest] +pythonpath = [ + "scripts/tooling", +] +testpaths = ["scripts/tooling/tests"] + [tool.ruff] # Exclude a variety of commonly ignored directories. exclude = [ diff --git a/scripts/tooling/.python-version b/scripts/tooling/.python-version deleted file mode 100644 index e4fba218358..00000000000 --- a/scripts/tooling/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12 diff --git a/scripts/tooling/BUILD b/scripts/tooling/BUILD index 57e29269f98..e14f0ccbe48 100644 --- a/scripts/tooling/BUILD +++ b/scripts/tooling/BUILD @@ -57,7 +57,7 @@ score_py_pytest( data = [ ":lib/assets/report_template.html", ], - pytest_config = ":pyproject.toml", + pytest_config = "//:pyproject.toml", deps = [ ":lib", ] + all_requirements, diff --git a/scripts/tooling/README.md b/scripts/tooling/README.md index e69de29bb2d..aa3668fa0bb 100644 --- a/scripts/tooling/README.md +++ b/scripts/tooling/README.md @@ -0,0 +1,16 @@ +# Tooling scripts + +## Running tooling CLI + +```bash +bazel run //scripts/tooling -- --help +``` + +## Updating dependencies + +Update a list of requirements in [requirements.in](requirements.in) file and then +regenerate lockfile [requirements.txt](requirements.txt) with: + +```bash +bazel run //scripts/tooling:requirements.update +``` diff --git a/scripts/tooling/pyproject.toml b/scripts/tooling/pyproject.toml deleted file mode 100644 index a228f2506cf..00000000000 --- a/scripts/tooling/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[project] -name = "tooling" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -requires-python = ">=3.12" -dependencies = ["jinja2>=3", "PyGithub>=2.1.1"] - -[project.scripts] -tooling = "cli.main:main" - -[dependency-groups] -dev = ["pytest>=9"] - -[tool.pytest.ini_options] -testpaths = ["tests"] -pythonpath = ["."] diff --git a/scripts/tooling/uv.lock b/scripts/tooling/uv.lock deleted file mode 100644 index 0294f7b48d8..00000000000 --- a/scripts/tooling/uv.lock +++ /dev/null @@ -1,454 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.12" - -[[package]] -name = "certifi" -version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, -] - -[[package]] -name = "cffi" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" }, - { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" }, - { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" }, - { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" }, - { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" }, - { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" }, - { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" }, - { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" }, - { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" }, - { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" }, - { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" }, - { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" }, - { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" }, - { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" }, - { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" }, - { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" }, - { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" }, - { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" }, - { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" }, - { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" }, - { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" }, - { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" }, - { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" }, - { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" }, - { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" }, - { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" }, - { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" }, - { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" }, - { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" }, - { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" }, - { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" }, - { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" }, - { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" }, - { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" }, - { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" }, - { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "cryptography" -version = "46.0.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, - { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, - { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, - { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, - { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, - { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - -[[package]] -name = "packaging" -version = "26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pycparser" -version = "3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, -] - -[[package]] -name = "pygithub" -version = "2.8.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyjwt", extra = ["crypto"] }, - { name = "pynacl" }, - { name = "requests" }, - { name = "typing-extensions" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c1/74/e560bdeffea72ecb26cff27f0fad548bbff5ecc51d6a155311ea7f9e4c4c/pygithub-2.8.1.tar.gz", hash = "sha256:341b7c78521cb07324ff670afd1baa2bf5c286f8d9fd302c1798ba594a5400c9", size = 2246994, upload-time = "2025-09-02T17:41:54.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ba/7049ce39f653f6140aac4beb53a5aaf08b4407b6a3019aae394c1c5244ff/pygithub-2.8.1-py3-none-any.whl", hash = "sha256:23a0a5bca93baef082e03411bf0ce27204c32be8bfa7abc92fe4a3e132936df0", size = 432709, upload-time = "2025-09-02T17:41:52.947Z" }, -] - -[[package]] -name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, -] - -[[package]] -name = "pyjwt" -version = "2.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - -[[package]] -name = "pynacl" -version = "1.6.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, - { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, - { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, - { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, - { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, - { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, - { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, - { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, - { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, - { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, - { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, - { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, - { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, - { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, - { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, - { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, - { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, - { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, -] - -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, -] - -[[package]] -name = "tooling" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "jinja2" }, - { name = "pygithub" }, -] - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, -] - -[package.metadata] -requires-dist = [ - { name = "jinja2", specifier = ">=3" }, - { name = "pygithub", specifier = ">=2.1.1" }, -] - -[package.metadata.requires-dev] -dev = [{ name = "pytest", specifier = ">=9" }] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] From b0e35a773447003acab1c673f92afd2556bf6728 Mon Sep 17 00:00:00 2001 From: Piotr Korkus Date: Fri, 6 Mar 2026 13:40:14 +0100 Subject: [PATCH 05/13] fix imports and deps --- scripts/tooling/BUILD | 7 +++++-- scripts/tooling/cli/main.py | 8 +++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/tooling/BUILD b/scripts/tooling/BUILD index e14f0ccbe48..dc29d16e2ee 100644 --- a/scripts/tooling/BUILD +++ b/scripts/tooling/BUILD @@ -10,7 +10,7 @@ # # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@pip_score_venv_test//:requirements.bzl", "all_requirements") +load("@ref_int_scripts_env//:requirements.bzl", "all_requirements") load("@rules_python//python:defs.bzl", "py_binary", "py_library") load("@rules_python//python:pip.bzl", "compile_pip_requirements") load("@score_tooling//python_basics:defs.bzl", "score_py_pytest") @@ -45,9 +45,12 @@ py_binary( srcs = glob([ "cli/**/*.py", ]), + data = [ + "lib/assets/report_template.html", + ], main = "cli/main.py", visibility = ["//visibility:public"], - deps = [":lib"], + deps = [":lib"] + all_requirements, ) # Tests target diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index 854f68c17d5..7df58a868ff 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -13,8 +13,8 @@ def _find_repo_root() -> Path: def _cmd_html_report(args: argparse.Namespace) -> int: - from lib.html_report import write_report - from lib.known_good.known_good import load_known_good + from scripts.tooling.lib.html_report import write_report + from scripts.tooling.lib.known_good import load_known_good known_good_path = Path(args.known_good) / "known_good.json" try: @@ -39,9 +39,7 @@ def main() -> None: misc_sub = misc_parser.add_subparsers(dest="command", metavar="COMMAND") misc_sub.required = True - html_parser = misc_sub.add_parser( - "html_report", help="Generate an HTML status report from known_good.json" - ) + html_parser = misc_sub.add_parser("html_report", help="Generate an HTML status report from known_good.json") html_parser.add_argument( "--known_good", metavar="PATH", From 3c2f758db5a323cb7f89548b96dde7ca9b76cd34 Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Fri, 6 Mar 2026 12:53:51 +0000 Subject: [PATCH 06/13] Align path handling --- .github/workflows/test_and_docs.yml | 3 ++- scripts/tooling/cli/main.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_and_docs.yml b/.github/workflows/test_and_docs.yml index 4ba7290700f..97003154c02 100644 --- a/.github/workflows/test_and_docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -111,7 +111,8 @@ jobs: --github_user=${{ github.repository_owner }} \ --github_repo=${{ github.event.repository.name }} - cd scripts/tooling && uv run python -m cli.main misc html_report --output _build/integration_status.html + CURRENT=$(realpath .) + bazel run //scripts/tooling -- misc html_report --output ${CURRENT}/_build/status_dashboard.html tar -cf github-pages.tar _build - name: Upload documentation artifact uses: actions/upload-artifact@v4.4.0 diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index 7df58a868ff..c940bcf3bcf 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -24,6 +24,7 @@ def _cmd_html_report(args: argparse.Namespace) -> int: return 1 output = Path(args.output) if args.output else Path("report.html") + output = output.resolve() write_report(known_good, output) print(f"Report written to {output}") return 0 From 1abbc3cee73fbbee3627cf6edf3dd671f9969fb1 Mon Sep 17 00:00:00 2001 From: Piotr Korkus Date: Fri, 6 Mar 2026 14:03:33 +0100 Subject: [PATCH 07/13] readme update and workflow --- .github/workflows/internal_tests.yml | 25 +++++++++++++++++++++++++ README.md | 5 +++++ scripts/tooling/README.md | 16 ++++++++++++++++ scripts/tooling/lib/html_report.py | 4 ++-- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/internal_tests.yml diff --git a/.github/workflows/internal_tests.yml b/.github/workflows/internal_tests.yml new file mode 100644 index 00000000000..2039100b254 --- /dev/null +++ b/.github/workflows/internal_tests.yml @@ -0,0 +1,25 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +name: Internal Tooling Tests +on: + pull_request: + types: [opened, reopened, synchronize] + push: + branches: + - main + +jobs: + internal_tests: + uses: eclipse-score/cicd-workflows/.github/workflows/tests.yml@main + with: + bazel-target: "//scripts/tooling_tests" diff --git a/README.md b/README.md index 5069bbeba85..bdcf8d7cf1f 100644 --- a/README.md +++ b/README.md @@ -171,3 +171,8 @@ local_path_override(module_name = "score_tooling", path = "../tooling") ### Rust Use `scripts/generate_rust_analyzer_support.sh` to generate rust_analyzer settings that will let VS Code work. + +## Internal tooling + +Internal tooling scripts are currently under development to provide user single point of interaction with all +created goods. More detailed readme can be found in scripts: [Tooling README](scripts/tooling/README.md) diff --git a/scripts/tooling/README.md b/scripts/tooling/README.md index aa3668fa0bb..106537454db 100644 --- a/scripts/tooling/README.md +++ b/scripts/tooling/README.md @@ -6,6 +6,22 @@ bazel run //scripts/tooling -- --help ``` +```bash +bazel run //scripts/tooling -- misc --help +``` + +## Creating HTML report + +```bash +bazel run //scripts/tooling -- misc html_report --output /home/pawel/score/repos/reference_integration/report.html +``` + +## Running tests + +```bash +bazel test //scripts/tooling_tests +``` + ## Updating dependencies Update a list of requirements in [requirements.in](requirements.in) file and then diff --git a/scripts/tooling/lib/html_report.py b/scripts/tooling/lib/html_report.py index 4614c4468bf..fc211b94dcf 100644 --- a/scripts/tooling/lib/html_report.py +++ b/scripts/tooling/lib/html_report.py @@ -16,7 +16,7 @@ import json from pathlib import Path -from typing import Any, Dict, List +from typing import Any from jinja2 import Environment, FileSystemLoader, select_autoescape @@ -29,7 +29,7 @@ ) -def _collect_entries(known_good: KnownGood) -> List[Dict[str, Any]]: +def _collect_entries(known_good: KnownGood) -> list[dict[str, Any]]: entries = [] for group_name, group_modules in known_good.modules.items(): for module in group_modules.values(): From 8aed6770fff4e7d283597d20d6eea0bd1ce831b7 Mon Sep 17 00:00:00 2001 From: Piotr Korkus Date: Fri, 6 Mar 2026 14:16:21 +0100 Subject: [PATCH 08/13] handle bazel sandbox in saving output --- scripts/tooling/README.md | 2 +- scripts/tooling/cli/main.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/tooling/README.md b/scripts/tooling/README.md index 106537454db..68466ecebf2 100644 --- a/scripts/tooling/README.md +++ b/scripts/tooling/README.md @@ -13,7 +13,7 @@ bazel run //scripts/tooling -- misc --help ## Creating HTML report ```bash -bazel run //scripts/tooling -- misc html_report --output /home/pawel/score/repos/reference_integration/report.html +bazel run //scripts/tooling -- misc html_report ``` ## Running tests diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index c940bcf3bcf..23c5f162b6f 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -1,8 +1,22 @@ import argparse +import os import sys from pathlib import Path +def _resolve_path_from_bazel(path: Path) -> Path: + """Resolve path relative to user's working directory when running via bazel run. + + Bazel sets BUILD_WORKING_DIRECTORY to the directory where bazel was invoked. + """ + if not path.is_absolute(): + # When running via 'bazel run', resolve relative paths against the user's cwd + build_working_dir = os.environ.get("BUILD_WORKING_DIRECTORY") + if build_working_dir: + return (Path(build_working_dir) / path).resolve() + return path.resolve() + + def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains known_good.json).""" candidate = Path(__file__).resolve() @@ -23,8 +37,7 @@ def _cmd_html_report(args: argparse.Namespace) -> int: print(f"error: {exc}", file=sys.stderr) return 1 - output = Path(args.output) if args.output else Path("report.html") - output = output.resolve() + output = _resolve_path_from_bazel(Path(args.output)) write_report(known_good, output) print(f"Report written to {output}") return 0 From f0129382bec47da47cf1ae12a39123400fc4f91b Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Fri, 6 Mar 2026 13:31:27 +0000 Subject: [PATCH 09/13] Extend logic to use generation time data too --- .github/workflows/test_and_docs.yml | 2 + .../known_good_to_workspace_metadata.py | 4 +- scripts/known_good/models/__init__.py | 2 +- scripts/known_good/models/known_good.py | 48 +++++----- scripts/known_good/models/module.py | 69 +++++++------- .../known_good/override_known_good_repo.py | 20 ++-- .../update_module_from_known_good.py | 63 ++++++------- scripts/known_good/update_module_latest.py | 18 ++-- scripts/tooling/cli/main.py | 8 +- .../tooling/lib/assets/report_template.html | 30 +++++- scripts/tooling/lib/github.py | 91 +++++++++++++++++++ scripts/tooling/lib/html_report.py | 49 ++++++++-- 12 files changed, 276 insertions(+), 128 deletions(-) create mode 100644 scripts/tooling/lib/github.py diff --git a/.github/workflows/test_and_docs.yml b/.github/workflows/test_and_docs.yml index 97003154c02..ad27a1fcb00 100644 --- a/.github/workflows/test_and_docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -30,6 +30,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: ${{ github.ref_name != 'main' && !startsWith(github.ref_name, 'release/') }} +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: test_and_docs: runs-on: ubuntu-22.04 diff --git a/scripts/known_good/known_good_to_workspace_metadata.py b/scripts/known_good/known_good_to_workspace_metadata.py index be1b1bae57c..bb933c8dcca 100644 --- a/scripts/known_good/known_good_to_workspace_metadata.py +++ b/scripts/known_good/known_good_to_workspace_metadata.py @@ -38,9 +38,9 @@ def main(): try: known_good = load_known_good(Path(args.known_good)) except FileNotFoundError as e: - raise SystemExit(f"ERROR: {e}") from e + raise SystemExit(f"ERROR: {e}") except ValueError as e: - raise SystemExit(f"ERROR: {e}") from e + raise SystemExit(f"ERROR: {e}") modules = list(known_good.modules.values()) diff --git a/scripts/known_good/models/__init__.py b/scripts/known_good/models/__init__.py index 91ff1c39475..edf7659729d 100644 --- a/scripts/known_good/models/__init__.py +++ b/scripts/known_good/models/__init__.py @@ -12,6 +12,6 @@ # ******************************************************************************* """Models for score reference integration tools.""" -from .module import Metadata, Module +from .module import Module, Metadata __all__ = ["Module", "Metadata"] diff --git a/scripts/known_good/models/known_good.py b/scripts/known_good/models/known_good.py index 06d62aa3857..48c6eaabecc 100644 --- a/scripts/known_good/models/known_good.py +++ b/scripts/known_good/models/known_good.py @@ -18,7 +18,7 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import Any +from typing import Any, Dict from .module import Module @@ -30,18 +30,18 @@ class KnownGood: Module structure: modules = {"group1": {"module1": Module}, "group2": {"module2": Module}} """ - modules: dict[str, dict[str, Module]] + modules: Dict[str, Dict[str, Module]] timestamp: str @classmethod - def from_dict(cls, data: dict[str, Any]) -> KnownGood: + def from_dict(cls, data: Dict[str, Any]) -> KnownGood: """Create a KnownGood instance from a dictionary. Expected structure: {"modules": {"group1": {"score_baselibs": {...}}, "group2": {"score_logging": {...}}}} Args: - data: dictionary containing known_good.json data + data: Dictionary containing known_good.json data Returns: KnownGood instance @@ -49,7 +49,7 @@ def from_dict(cls, data: dict[str, Any]) -> KnownGood: modules_dict = data.get("modules", {}) timestamp = data.get("timestamp", "") - parsed_modules: dict[str, dict[str, Module]] = {} + parsed_modules: Dict[str, Dict[str, Module]] = {} for group_name, group_modules in modules_dict.items(): if isinstance(group_modules, dict): modules_list = Module.parse_modules(group_modules) @@ -57,7 +57,7 @@ def from_dict(cls, data: dict[str, Any]) -> KnownGood: return cls(modules=parsed_modules, timestamp=timestamp) - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> Dict[str, Any]: """Convert KnownGood instance to dictionary for JSON output. Returns: @@ -70,7 +70,7 @@ def to_dict(self) -> dict[str, Any]: return {"modules": modules_output, "timestamp": self.timestamp} - def write(self, output_path: Path, *, dry_run: bool = False) -> None: + def write(self, output_path: Path, dry_run: bool = False) -> None: """Write known_good data to file or print for dry-run. Args: @@ -89,7 +89,8 @@ def write(self, output_path: Path, *, dry_run: bool = False) -> None: print(output_json, end="") print("---- END UPDATED JSON ----") else: - output_path.write_text(output_json, encoding="utf-8") + with open(output_path, "w", encoding="utf-8") as f: + f.write(output_json) print(f"Successfully wrote updated known_good.json to {output_path}") @@ -103,21 +104,22 @@ def load_known_good(path: Path) -> KnownGood: KnownGood instance with parsed modules """ - try: - text = path.read_text(encoding="utf-8") - data = json.loads(text) - except json.JSONDecodeError as e: - lines = text.splitlines() - line = lines[e.lineno - 1] if 0 <= e.lineno - 1 < len(lines) else "" - pointer = " " * (e.colno - 1) + "^" - - hint = "" - if "Expecting value" in e.msg: - hint = "Possible causes: trailing comma, missing value, or extra comma." - - raise ValueError( - f"Invalid JSON at line {e.lineno}, column {e.colno}\n{line}\n{pointer}\n{e.msg}. {hint}" - ) from None + with open(path, "r", encoding="utf-8") as f: + text = f.read() + try: + data = json.loads(text) + except json.JSONDecodeError as e: + lines = text.splitlines() + line = lines[e.lineno - 1] if 0 <= e.lineno - 1 < len(lines) else "" + pointer = " " * (e.colno - 1) + "^" + + hint = "" + if "Expecting value" in e.msg: + hint = "Possible causes: trailing comma, missing value, or extra comma." + + raise ValueError( + f"Invalid JSON at line {e.lineno}, column {e.colno}\n{line}\n{pointer}\n{e.msg}. {hint}" + ) from None if not isinstance(data, dict) or not isinstance(data.get("modules"), dict): raise ValueError(f"Invalid known_good.json at {path} (expected object with 'modules' dict)") diff --git a/scripts/known_good/models/module.py b/scripts/known_good/models/module.py index 4b76e3c89dd..9fa266f91b9 100644 --- a/scripts/known_good/models/module.py +++ b/scripts/known_good/models/module.py @@ -16,7 +16,7 @@ import logging from dataclasses import dataclass, field -from typing import Any +from typing import Any, Dict, List from urllib.parse import urlparse @@ -37,7 +37,7 @@ class Metadata: langs: list[str] = field(default_factory=lambda: ["cpp", "rust"]) @classmethod - def from_dict(cls, data: dict[str, Any]) -> Metadata: + def from_dict(cls, data: Dict[str, Any]) -> Metadata: """Create a Metadata instance from a dictionary. Args: @@ -53,7 +53,7 @@ def from_dict(cls, data: dict[str, Any]) -> Metadata: langs=data.get("langs", ["cpp", "rust"]), ) - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> Dict[str, Any]: """Convert Metadata instance to dictionary representation. Returns: @@ -79,30 +79,30 @@ class Module: pin_version: bool = False @classmethod - def from_dict(cls, name: str, module_data: dict[str, Any]) -> Module: + def from_dict(cls, name: str, module_data: Dict[str, Any]) -> Module: """Create a Module instance from a dictionary representation. Args: - name: The module name - module_data: Dictionary containing module configuration with keys: - - repo (str): Repository URL - - hash or commit (str): Commit hash - - version (str, optional): Module version (when present, hash is ignored) - - bazel_patches (list[str], optional): List of patch files for Bazel - - metadata (dict, optional): Metadata configuration - Example: { - "code_root_path": "path/to/code/root", - "extra_test_config": [""], - "exclude_test_targets": [""], - "langs": ["cpp", "rust"] - } - If not present, uses default Metadata values. - - branch (str, optional): Git branch name (default: main) - - pin_version (bool, optional): If true, module hash is not updated - to latest HEAD by update scripts (default: false) + name: The module name + module_data: Dictionary containing module configuration with keys: + - repo (str): Repository URL + - hash or commit (str): Commit hash + - version (str, optional): Module version (when present, hash is ignored) + - bazel_patches (list[str], optional): List of patch files for Bazel + - metadata (dict, optional): Metadata configuration + Example: { + "code_root_path": "path/to/code/root", + "extra_test_config": [""], + "exclude_test_targets": [""], + "langs": ["cpp", "rust"] + } + If not present, uses default Metadata values. + - branch (str, optional): Git branch name (default: main) + - pin_version (bool, optional): If true, module hash is not updated + to latest HEAD by update scripts (default: false) Returns: - Module instance + Module instance """ repo = module_data.get("repo", "") # Support both 'hash' and 'commit' keys @@ -119,14 +119,17 @@ def from_dict(cls, name: str, module_data: dict[str, Any]) -> Module: # Parse metadata - if not present or is None/empty dict, use defaults metadata_data = module_data.get("metadata") - metadata = Metadata.from_dict(metadata_data) if metadata_data is not None else Metadata() - - # Enable once we are able to remove '*' in known_good.json - # if any("*" in target for target in metadata.exclude_test_targets): - # raise Exception( - # f"Module {name} has wildcard '*' in exclude_test_targets, which is not allowed. " - # "Please specify explicit test targets to exclude or remove the key if no exclusions are needed." - # ) + if metadata_data is not None: + metadata = Metadata.from_dict(metadata_data) + # Enable once we are able to remove '*' in known_good.json + # if any("*" in target for target in metadata.exclude_test_targets): + # raise Exception( + # f"Module {name} has wildcard '*' in exclude_test_targets, which is not allowed. " + # "Please specify explicit test targets to exclude or remove the key if no exclusions are needed." + # ) + else: + # If metadata key is missing, create with defaults + metadata = Metadata() branch = module_data.get("branch", "main") pin_version = module_data.get("pin_version", False) @@ -143,7 +146,7 @@ def from_dict(cls, name: str, module_data: dict[str, Any]) -> Module: ) @classmethod - def parse_modules(cls, modules_dict: dict[str, Any]) -> list[Module]: + def parse_modules(cls, modules_dict: Dict[str, Any]) -> List[Module]: """Parse modules dictionary into Module dataclass instances. Args: @@ -184,13 +187,13 @@ def owner_repo(self) -> str: return f"{parts[0]}/{parts[1]}" - def to_dict(self) -> dict[str, Any]: + def to_dict(self) -> Dict[str, Any]: """Convert Module instance to dictionary representation for JSON output. Returns: Dictionary with module configuration """ - result: dict[str, Any] = {"repo": self.repo} + result: Dict[str, Any] = {"repo": self.repo} if self.version: result["version"] = self.version else: diff --git a/scripts/known_good/override_known_good_repo.py b/scripts/known_good/override_known_good_repo.py index 033c3f34eb4..218d360d0a3 100755 --- a/scripts/known_good/override_known_good_repo.py +++ b/scripts/known_good/override_known_good_repo.py @@ -26,10 +26,12 @@ """ import argparse -import datetime as dt -import logging +import os import re +import datetime as dt from pathlib import Path +from typing import Dict, List +import logging from models import Module from models.known_good import KnownGood, load_known_good @@ -38,7 +40,7 @@ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") -def parse_and_apply_overrides(modules: dict[str, Module], repo_overrides: list[str]) -> int: +def parse_and_apply_overrides(modules: Dict[str, Module], repo_overrides: List[str]) -> int: """ Parse repo override arguments and apply them to modules. @@ -130,7 +132,7 @@ def parse_and_apply_overrides(modules: dict[str, Module], repo_overrides: list[s return overrides_applied -def apply_overrides(known_good: KnownGood, repo_overrides: list[str]) -> KnownGood: +def apply_overrides(known_good: KnownGood, repo_overrides: List[str]) -> KnownGood: """Apply repository commit overrides to the known_good data. Args: @@ -207,17 +209,17 @@ def main() -> None: if args.verbose: logging.getLogger().setLevel(logging.DEBUG) - known_path = Path(args.known).resolve() - output_path = Path(args.output).resolve() + known_path = os.path.abspath(args.known) + output_path = os.path.abspath(args.output) # Load, update, and output logging.info(f"Loading {known_path}") try: known_good = load_known_good(known_path) except FileNotFoundError as e: - raise SystemExit(f"ERROR: {e}") from e + raise SystemExit(f"ERROR: {e}") except ValueError as e: - raise SystemExit(f"ERROR: {e}") from e + raise SystemExit(f"ERROR: {e}") if not args.module_overrides: parser.error("at least one --module-override is required") @@ -225,7 +227,7 @@ def main() -> None: overrides = args.module_overrides updated_known_good = apply_overrides(known_good, overrides) - updated_known_good.write(Path(output_path), dry_run=args.dry_run) + updated_known_good.write(Path(output_path), args.dry_run) if __name__ == "__main__": diff --git a/scripts/known_good/update_module_from_known_good.py b/scripts/known_good/update_module_from_known_good.py index d585fe514e6..e6c82fe3cb3 100755 --- a/scripts/known_good/update_module_from_known_good.py +++ b/scripts/known_good/update_module_from_known_good.py @@ -33,7 +33,7 @@ import os import re from pathlib import Path -from typing import Optional +from typing import Dict, List, Optional from models import Module from models.known_good import load_known_good @@ -42,7 +42,7 @@ logging.basicConfig(level=logging.WARNING, format="%(levelname)s: %(message)s") -def generate_git_override_blocks(modules: list[Module], repo_commit_dict: dict[str, str]) -> list[str]: +def generate_git_override_blocks(modules: List[Module], repo_commit_dict: Dict[str, str]) -> List[str]: """Generate bazel_dep and git_override blocks for each module.""" blocks = [] @@ -109,7 +109,7 @@ def generate_git_override_blocks(modules: list[Module], repo_commit_dict: dict[s return blocks -def generate_local_override_blocks(modules: list[Module]) -> list[str]: +def generate_local_override_blocks(modules: List[Module]) -> List[str]: """Generate bazel_dep and local_path_override blocks for each module.""" blocks = [] @@ -127,7 +127,7 @@ def generate_local_override_blocks(modules: list[Module]) -> list[str]: return blocks -def generate_coverage_blocks(modules: list[Module]) -> list[str]: +def generate_coverage_blocks(modules: List[Module]) -> List[str]: """Generate rust_coverage_report blocks for each module with rust impl.""" blocks = ["""load("@score_tooling//:defs.bzl", "rust_coverage_report")"""] @@ -160,8 +160,8 @@ def generate_coverage_blocks(modules: list[Module]) -> list[str]: def generate_file_content( args: argparse.Namespace, - modules: list[Module], - repo_commit_dict: dict[str, str], + modules: List[Module], + repo_commit_dict: Dict[str, str], timestamp: Optional[str] = None, file_type: str = "module", ) -> str: @@ -206,20 +206,6 @@ def generate_file_content( return header + "\n".join(blocks) -def parse_repo_commit_overrides(repo_overrides: list[str]) -> dict[str, str]: - repo_commit_dict = {} - repo_pattern = re.compile(r"https://[a-zA-Z0-9.-]+/[a-zA-Z0-9._/-]+\.git@[a-fA-F0-9]{7,40}$") - for entry in repo_overrides: - if not repo_pattern.match(entry): - raise SystemExit( - f"Invalid --repo-override format: {entry}\n" - "Expected format: https://github.com/org/repo.git@" - ) - repo_url, commit_hash = entry.split("@", 1) - repo_commit_dict[repo_url] = commit_hash - return repo_commit_dict - - def main() -> None: parser = argparse.ArgumentParser( description="Generate score_modules.MODULE.bazel file(s) from known_good.json", @@ -248,19 +234,16 @@ def main() -> None: parser.add_argument( "--known", default=Path(__file__).parents[2] / "known_good.json", - type=Path, help="Path to known_good.json (default: known_good.json in repo root)", ) parser.add_argument( "--output-dir-modules", default=Path(__file__).parents[2] / "bazel_common", - type=Path, help="Output directory for grouped structure files (default: bazel_common in repo root)", ) parser.add_argument( "--output-dir-coverage", default=Path(__file__).parents[2] / "rust_coverage", - type=Path, help="Output directory for BUILD coverage file (default: rust_coverage in repo root)", ) parser.add_argument( @@ -286,28 +269,38 @@ def main() -> None: if args.verbose: logging.getLogger().setLevel(logging.INFO) - known_path = (args.known).resolve() + known_path = os.path.abspath(args.known) - if not known_path.exists(): + if not os.path.exists(known_path): raise SystemExit(f"known_good.json not found at {known_path}") # Parse repo overrides - repo_commit_dict = parse_repo_commit_overrides(args.repo_override) if args.repo_override else {} + repo_commit_dict = {} + if args.repo_override: + repo_pattern = re.compile(r"https://[a-zA-Z0-9.-]+/[a-zA-Z0-9._/-]+\.git@[a-fA-F0-9]{7,40}$") + for entry in args.repo_override: + if not repo_pattern.match(entry): + raise SystemExit( + f"Invalid --repo-override format: {entry}\n" + "Expected format: https://github.com/org/repo.git@" + ) + repo_url, commit_hash = entry.split("@", 1) + repo_commit_dict[repo_url] = commit_hash # Load known_good.json try: - known_good = load_known_good(known_path) + known_good = load_known_good(Path(known_path)) except FileNotFoundError as e: - raise SystemExit(f"ERROR: {e}") from e + raise SystemExit(f"ERROR: {e}") except ValueError as e: - raise SystemExit(f"ERROR: {e}") from e + raise SystemExit(f"ERROR: {e}") if not known_good.modules: raise SystemExit("No modules found in known_good.json") # Generate files based on structure (flat vs grouped) - output_dir_modules = args.output_dir_modules.resolve() - output_dir_modules.mkdir(parents=True, exist_ok=True) + output_dir_modules = os.path.abspath(args.output_dir_modules) + os.makedirs(output_dir_modules, exist_ok=True) generated_files = [] total_module_count = 0 @@ -322,7 +315,7 @@ def main() -> None: # Determine output filename: score_modules_{group}.MODULE.bazel output_filename = f"score_modules_{group_name}.MODULE.bazel" - output_path_modules = output_dir_modules / output_filename + output_path_modules = os.path.join(output_dir_modules, output_filename) output_path_coverage = args.output_dir_coverage / "BUILD" # Generate file content of MODULE files @@ -337,7 +330,8 @@ def main() -> None: print("---- END GENERATED CONTENT FOR MODULE ----") print(f"\nGenerated {len(modules)} {args.override_type}_override entries for group '{group_name}'") else: - output_path_modules.write_text(content_module, encoding="utf-8") + with open(output_path_modules, "w", encoding="utf-8") as f: + f.write(content_module) generated_files.append(output_path_modules) total_module_count += len(modules) print(f"Generated {output_path_modules} with {len(modules)} {args.override_type}_override entries") @@ -355,7 +349,8 @@ def main() -> None: print("---- END GENERATED CONTENT FOR BUILD ----") print(f"\nGenerated {len(modules)} {args.override_type}_override entries for group '{group_name}'") else: - output_path_coverage.write_text(content_build, encoding="utf-8") + with open(output_path_coverage, "w", encoding="utf-8") as f: + f.write(content_build) generated_files.append(output_path_coverage) print(f"Generated {output_path_coverage}") diff --git a/scripts/known_good/update_module_latest.py b/scripts/known_good/update_module_latest.py index 4c1f50247b6..33e238c4f17 100755 --- a/scripts/known_good/update_module_latest.py +++ b/scripts/known_good/update_module_latest.py @@ -22,8 +22,8 @@ Usage: python tools/update_module_latest.py \ - --known-good score_reference_integration/known_good.json \ - [--branch main] [--output updated_known_good.json] + --known-good score_reference_integration/known_good.json \ + [--branch main] [--output updated_known_good.json] Environment: Optionally set GITHUB_TOKEN to increase rate limits / access private repos. @@ -37,10 +37,10 @@ from __future__ import annotations import argparse -import json -import os import shutil import subprocess +import json +import os import sys from pathlib import Path @@ -102,18 +102,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace: p = argparse.ArgumentParser(description="Update module hashes to latest commit on branch") p.add_argument( "--known-good", - default=Path(__file__).parents[2] / "known_good.json", - type=Path, + default="known_good.json", help="Path to known_good.json file (default: known_good.json in CWD)", ) p.add_argument("--branch", default="main", help="Git branch to fetch latest commits from (default: main)") - p.add_argument("--output", type=Path, help="Optional output path to write updated JSON") + p.add_argument("--output", help="Optional output path to write updated JSON") p.add_argument("--fail-fast", action="store_true", help="Stop on first failure instead of continuing") p.add_argument( "--no-gh", action="store_true", - help="Disable GitHub CLI usage even if installed; fall back to HTTP API; " - "GITHUB_TOKEN has to be known in the environment", + help="Disable GitHub CLI usage even if installed; fall back to HTTP API; GITHUB_TOKEN has to be known in the environment", ) return p.parse_args(argv) @@ -121,7 +119,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: def main(argv: list[str]) -> int: args = parse_args(argv) try: - known_good = load_known_good(Path(args.known_good).resolve()) + known_good = load_known_good(Path(args.known_good)) except FileNotFoundError as e: print(f"ERROR: {e}", file=sys.stderr) return 3 diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index 23c5f162b6f..214e72db371 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -37,9 +37,13 @@ def _cmd_html_report(args: argparse.Namespace) -> int: print(f"error: {exc}", file=sys.stderr) return 1 + token = os.environ.get("GITHUB_TOKEN") output = _resolve_path_from_bazel(Path(args.output)) - write_report(known_good, output) - print(f"Report written to {output}") + write_report(known_good, output, token=token) + if token: + print(f"Report written to {output} (current hashes fetched from GitHub)") + else: + print(f"Report written to {output} (set GITHUB_TOKEN to embed current hashes)") return 0 diff --git a/scripts/tooling/lib/assets/report_template.html b/scripts/tooling/lib/assets/report_template.html index fb6e2be84fc..02380cfedfd 100644 --- a/scripts/tooling/lib/assets/report_template.html +++ b/scripts/tooling/lib/assets/report_template.html @@ -273,10 +273,11 @@

Known Good Status

- 🔒 A GitHub personal access token (PAT) is required to fetch latest modules status. + 🔒 Add a GitHub personal access token (PAT) for live status — exact commit counts fetched directly from GitHub. Enter it in the token field above, or create one here. - Without a token, cards are shown but status is unavailable.Your PAT is not send anywhere, it's only keept in local cache of this page + Without a token, up-to-date / behind status is still shown from data embedded at report generation time. + Your PAT is not sent anywhere — it is only kept in the local cache of this page.
@@ -499,9 +500,28 @@

Known Good Status

const statusEl = document.getElementById(`status-${mod.name}`); if (!getToken()) { - statusEl.className = 'status pin'; - statusEl.textContent = '⊘ unavailable — requires PAT'; - counters.error++; + // Use pre-baked compare data from generation time (if available) + if (mod.current_hash !== null && mod.behind_by !== null) { + const latestRow = document.getElementById(`latest-${mod.name}`); + const latestHashEl = document.getElementById(`latest-hash-${mod.name}`); + const shortLatest = mod.current_hash.slice(0, 10); + const latestUrl = mod.owner_repo + ? `https://github.com/${mod.owner_repo}/commit/${mod.current_hash}` + : '#'; + latestHashEl.innerHTML = `${shortLatest}`; + latestRow.classList.add('visible'); + + const behind = mod.behind_by; + statusEl.className = `status ${statusClass(behind)}`; + statusEl.textContent = statusLabel(behind, mod.compare_status); + if (behind === 0) counters.ok++; + else if (behind > 20) counters.danger++; + else counters.behind++; + } else { + statusEl.className = 'status pin'; + statusEl.textContent = '⊘ unavailable — requires PAT'; + counters.error++; + } updateSummary(); return; } diff --git a/scripts/tooling/lib/github.py b/scripts/tooling/lib/github.py new file mode 100644 index 00000000000..dd2b8742093 --- /dev/null +++ b/scripts/tooling/lib/github.py @@ -0,0 +1,91 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +"""GitHub REST API helpers. + +Lightweight wrappers around the GitHub API using only stdlib (urllib). +No third-party dependencies are required. +""" + +from __future__ import annotations + +import json +import logging +import urllib.error +import urllib.request +from dataclasses import dataclass +from typing import Optional + +_LOG = logging.getLogger(__name__) + +_API_BASE = "https://api.github.com" + + +def _make_request(url: str, token: Optional[str]) -> urllib.request.Request: + req = urllib.request.Request(url) + req.add_header("Accept", "application/vnd.github+json") + req.add_header("X-GitHub-Api-Version", "2022-11-28") + if token: + req.add_header("Authorization", f"Bearer {token}") + return req + + +@dataclass +class CompareResult: + """Result of comparing a pinned commit against a branch HEAD.""" + + ahead_by: int + """How many commits the branch HEAD is ahead of the pinned hash (i.e. commits behind).""" + status: str + """GitHub compare status: ``"identical"``, ``"ahead"``, ``"behind"``, or ``"diverged"``.""" + head_sha: str + """Current HEAD SHA of the target branch.""" + + +def fetch_compare( + owner_repo: str, + base_hash: str, + branch: str, + token: Optional[str] = None, +) -> Optional[CompareResult]: + """Compare *base_hash* against the HEAD of *branch* in *owner_repo*. + + Returns a :class:`CompareResult` with the number of commits the pinned hash + is behind and the current HEAD SHA, or ``None`` on any error. + + Args: + owner_repo: GitHub ``owner/repo`` slug (e.g. ``"eclipse-score/baselibs"``). + base_hash: The pinned commit SHA to compare from. + branch: Branch name to compare against. + token: Optional GitHub PAT or ``GITHUB_TOKEN``. + Without a token requests are unauthenticated (60 req/h rate limit). + """ + url = f"{_API_BASE}/repos/{owner_repo}/compare/{base_hash}...{branch}" + req = _make_request(url, token) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + data = json.loads(resp.read()) + commits = data.get("commits", []) + head_sha = commits[-1]["sha"] if commits else base_hash + return CompareResult( + ahead_by=data.get("ahead_by", 0), + status=data.get("status", ""), + head_sha=head_sha, + ) + except urllib.error.HTTPError as exc: + _LOG.debug( + "GitHub compare HTTP %s for %s %s...%s: %s", + exc.code, owner_repo, base_hash[:10], branch, exc.reason, + ) + except OSError as exc: + _LOG.debug("GitHub compare network error for %s: %s", owner_repo, exc) + return None diff --git a/scripts/tooling/lib/html_report.py b/scripts/tooling/lib/html_report.py index fc211b94dcf..fa268fa8601 100644 --- a/scripts/tooling/lib/html_report.py +++ b/scripts/tooling/lib/html_report.py @@ -15,13 +15,17 @@ from __future__ import annotations import json +import logging from pathlib import Path -from typing import Any +from typing import Any, Optional from jinja2 import Environment, FileSystemLoader, select_autoescape +from .github import fetch_compare from .known_good import KnownGood +_LOG = logging.getLogger(__name__) + _TEMPLATE_DIR = Path(__file__).parent / "assets" _ENV = Environment( loader=FileSystemLoader(_TEMPLATE_DIR), @@ -46,22 +50,48 @@ def _collect_entries(known_good: KnownGood) -> list[dict[str, Any]]: "hash": module.hash, "version": module.version, "branch": module.branch, + "current_hash": None, + "behind_by": None, + "compare_status": None, } ) return entries -def generate_report(known_good: KnownGood) -> str: +def _enrich_with_compare_data( + entries: list[dict[str, Any]], + token: str, +) -> None: + """Fetch compare data for each GitHub module and store it in the entry.""" + for entry in entries: + if not entry.get("owner_repo") or not entry.get("hash") or entry.get("version"): + continue + result = fetch_compare(entry["owner_repo"], entry["hash"], entry["branch"], token) + if result: + entry["current_hash"] = result.head_sha + entry["behind_by"] = result.ahead_by + entry["compare_status"] = result.status + else: + _LOG.warning( + "Could not fetch compare data for %s@%s", entry["owner_repo"], entry["branch"] + ) + + +def generate_report(known_good: KnownGood, token: Optional[str] = None) -> str: """Return a self-contained HTML report string for *known_good*. - The page embeds module data as JSON and uses client-side JavaScript to - query the GitHub compare API, showing how many commits behind each module - is relative to its target branch HEAD. A GitHub personal access token - (PAT) must be supplied via the token input in the header. Without a token - the grid is still rendered but each card shows an "unavailable / requires - PAT" badge instead of live status. + When *token* is provided (e.g. from the ``GITHUB_TOKEN`` environment + variable), the compare API is called at generation time for each module. + The report then shows exact commit-behind counts and current HEAD hashes + with no PAT required from the viewer. Adding a PAT in the browser + triggers a live re-check. + + Without *token* the report still renders; each card shows + "unavailable — requires PAT" until the viewer enters their own token. """ entries = _collect_entries(known_good) + if token: + _enrich_with_compare_data(entries, token) tmpl = _ENV.get_template("report_template.html") return tmpl.render( modules_json=json.dumps(entries, indent=2), @@ -72,6 +102,7 @@ def generate_report(known_good: KnownGood) -> str: def write_report( known_good: KnownGood, output_path: Path, + token: Optional[str] = None, ) -> None: """Write the HTML report to *output_path*.""" - Path(output_path).write_text(generate_report(known_good), encoding="utf-8") + Path(output_path).write_text(generate_report(known_good, token), encoding="utf-8") From 85b4d62b71735aab4d7c0521c88c27510390d979 Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Fri, 6 Mar 2026 13:46:39 +0000 Subject: [PATCH 10/13] Make entrypoint cleaner --- scripts/tooling/BUILD | 4 +- scripts/tooling/cli/main.py | 69 +------------------ scripts/tooling/cli/misc/__init__.py | 10 +++ .../misc}/assets/report_template.html | 0 scripts/tooling/cli/misc/html_report.py | 62 +++++++++++++++++ scripts/tooling/lib/html_report.py | 36 +++++----- scripts/tooling/tests/test_report.py | 65 ++++++++--------- 7 files changed, 129 insertions(+), 117 deletions(-) create mode 100644 scripts/tooling/cli/misc/__init__.py rename scripts/tooling/{lib => cli/misc}/assets/report_template.html (100%) create mode 100644 scripts/tooling/cli/misc/html_report.py diff --git a/scripts/tooling/BUILD b/scripts/tooling/BUILD index dc29d16e2ee..5fd5b5723f9 100644 --- a/scripts/tooling/BUILD +++ b/scripts/tooling/BUILD @@ -46,7 +46,7 @@ py_binary( "cli/**/*.py", ]), data = [ - "lib/assets/report_template.html", + "cli/misc/assets/report_template.html", ], main = "cli/main.py", visibility = ["//visibility:public"], @@ -58,7 +58,7 @@ score_py_pytest( name = "tooling_tests", srcs = glob(["tests/**/*.py"]), data = [ - ":lib/assets/report_template.html", + ":cli/misc/assets/report_template.html", ], pytest_config = "//:pyproject.toml", deps = [ diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index 214e72db371..d574415c418 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -1,50 +1,5 @@ import argparse -import os import sys -from pathlib import Path - - -def _resolve_path_from_bazel(path: Path) -> Path: - """Resolve path relative to user's working directory when running via bazel run. - - Bazel sets BUILD_WORKING_DIRECTORY to the directory where bazel was invoked. - """ - if not path.is_absolute(): - # When running via 'bazel run', resolve relative paths against the user's cwd - build_working_dir = os.environ.get("BUILD_WORKING_DIRECTORY") - if build_working_dir: - return (Path(build_working_dir) / path).resolve() - return path.resolve() - - -def _find_repo_root() -> Path: - """Walk up from this file to find the repo root (contains known_good.json).""" - candidate = Path(__file__).resolve() - for parent in candidate.parents: - if (parent / "known_good.json").exists(): - return parent - return Path.cwd() - - -def _cmd_html_report(args: argparse.Namespace) -> int: - from scripts.tooling.lib.html_report import write_report - from scripts.tooling.lib.known_good import load_known_good - - known_good_path = Path(args.known_good) / "known_good.json" - try: - known_good = load_known_good(known_good_path) - except (FileNotFoundError, ValueError) as exc: - print(f"error: {exc}", file=sys.stderr) - return 1 - - token = os.environ.get("GITHUB_TOKEN") - output = _resolve_path_from_bazel(Path(args.output)) - write_report(known_good, output, token=token) - if token: - print(f"Report written to {output} (current hashes fetched from GitHub)") - else: - print(f"Report written to {output} (set GITHUB_TOKEN to embed current hashes)") - return 0 def main() -> None: @@ -52,29 +7,11 @@ def main() -> None: subparsers = parser.add_subparsers(dest="group", metavar="GROUP") subparsers.required = True - # --- misc group --- - misc_parser = subparsers.add_parser("misc", help="Miscellaneous utilities") - misc_sub = misc_parser.add_subparsers(dest="command", metavar="COMMAND") - misc_sub.required = True - - html_parser = misc_sub.add_parser("html_report", help="Generate an HTML status report from known_good.json") - html_parser.add_argument( - "--known_good", - metavar="PATH", - default=str(_find_repo_root()), - help="Directory containing known_good.json (default: repo root)", - ) - html_parser.add_argument( - "--output", - metavar="FILE", - default="report.html", - help="Output HTML file path (default: report.html)", - ) + from scripts.tooling.cli.misc import register as _register_misc + _register_misc(subparsers) args = parser.parse_args() - - if args.group == "misc" and args.command == "html_report": - sys.exit(_cmd_html_report(args)) + sys.exit(args.func(args)) if __name__ == "__main__": diff --git a/scripts/tooling/cli/misc/__init__.py b/scripts/tooling/cli/misc/__init__.py new file mode 100644 index 00000000000..506f8d26292 --- /dev/null +++ b/scripts/tooling/cli/misc/__init__.py @@ -0,0 +1,10 @@ +import argparse + + +def register(subparsers: argparse._SubParsersAction) -> None: + misc_parser = subparsers.add_parser("misc", help="Miscellaneous utilities") + misc_sub = misc_parser.add_subparsers(dest="command", metavar="COMMAND") + misc_sub.required = True + + from scripts.tooling.cli.misc.html_report import register as _register_html_report + _register_html_report(misc_sub) diff --git a/scripts/tooling/lib/assets/report_template.html b/scripts/tooling/cli/misc/assets/report_template.html similarity index 100% rename from scripts/tooling/lib/assets/report_template.html rename to scripts/tooling/cli/misc/assets/report_template.html diff --git a/scripts/tooling/cli/misc/html_report.py b/scripts/tooling/cli/misc/html_report.py new file mode 100644 index 00000000000..9dd341377f9 --- /dev/null +++ b/scripts/tooling/cli/misc/html_report.py @@ -0,0 +1,62 @@ +import argparse +import os +import sys +from pathlib import Path + +TEMPLATE_DIR = Path(__file__).parent / "assets" + + +def _find_repo_root() -> Path: + candidate = Path(__file__).resolve() + for parent in candidate.parents: + if (parent / "known_good.json").exists(): + return parent + return Path.cwd() + + +def _resolve_path_from_bazel(path: Path) -> Path: + if not path.is_absolute(): + build_working_dir = os.environ.get("BUILD_WORKING_DIRECTORY") + if build_working_dir: + return (Path(build_working_dir) / path).resolve() + return path.resolve() + + +def register(subparsers: argparse._SubParsersAction) -> None: + parser = subparsers.add_parser( + "html_report", help="Generate an HTML status report from known_good.json" + ) + parser.add_argument( + "--known_good", + metavar="PATH", + default=str(_find_repo_root()), + help="Directory containing known_good.json (default: repo root)", + ) + parser.add_argument( + "--output", + metavar="FILE", + default="report.html", + help="Output HTML file path (default: report.html)", + ) + parser.set_defaults(func=_run) + + +def _run(args: argparse.Namespace) -> int: + from scripts.tooling.lib.html_report import write_report + from scripts.tooling.lib.known_good import load_known_good + + known_good_path = Path(args.known_good) / "known_good.json" + try: + known_good = load_known_good(known_good_path) + except (FileNotFoundError, ValueError) as exc: + print(f"error: {exc}", file=sys.stderr) + return 1 + + token = os.environ.get("GITHUB_TOKEN") + output = _resolve_path_from_bazel(Path(args.output)) + write_report(known_good, output, TEMPLATE_DIR, token=token) + if token: + print(f"Report written to {output} (current hashes fetched from GitHub)") + else: + print(f"Report written to {output} (set GITHUB_TOKEN to embed current hashes)") + return 0 diff --git a/scripts/tooling/lib/html_report.py b/scripts/tooling/lib/html_report.py index fa268fa8601..f9932c42a34 100644 --- a/scripts/tooling/lib/html_report.py +++ b/scripts/tooling/lib/html_report.py @@ -26,12 +26,6 @@ _LOG = logging.getLogger(__name__) -_TEMPLATE_DIR = Path(__file__).parent / "assets" -_ENV = Environment( - loader=FileSystemLoader(_TEMPLATE_DIR), - autoescape=select_autoescape(["html"]), -) - def _collect_entries(known_good: KnownGood) -> list[dict[str, Any]]: entries = [] @@ -77,22 +71,29 @@ def _enrich_with_compare_data( ) -def generate_report(known_good: KnownGood, token: Optional[str] = None) -> str: +def generate_report( + known_good: KnownGood, + template_dir: Path, + token: Optional[str] = None, +) -> str: """Return a self-contained HTML report string for *known_good*. - When *token* is provided (e.g. from the ``GITHUB_TOKEN`` environment - variable), the compare API is called at generation time for each module. - The report then shows exact commit-behind counts and current HEAD hashes - with no PAT required from the viewer. Adding a PAT in the browser - triggers a live re-check. - - Without *token* the report still renders; each card shows - "unavailable — requires PAT" until the viewer enters their own token. + Args: + known_good: Parsed known_good.json data. + template_dir: Directory containing ``report_template.html``. + token: Optional GitHub PAT / ``GITHUB_TOKEN``. When provided the + compare API is called at generation time, embedding exact + commit-behind counts and current HEAD hashes so the viewer + needs no PAT. """ entries = _collect_entries(known_good) if token: _enrich_with_compare_data(entries, token) - tmpl = _ENV.get_template("report_template.html") + env = Environment( + loader=FileSystemLoader(template_dir), + autoescape=select_autoescape(["html"]), + ) + tmpl = env.get_template("report_template.html") return tmpl.render( modules_json=json.dumps(entries, indent=2), timestamp=known_good.timestamp, @@ -102,7 +103,8 @@ def generate_report(known_good: KnownGood, token: Optional[str] = None) -> str: def write_report( known_good: KnownGood, output_path: Path, + template_dir: Path, token: Optional[str] = None, ) -> None: """Write the HTML report to *output_path*.""" - Path(output_path).write_text(generate_report(known_good, token), encoding="utf-8") + Path(output_path).write_text(generate_report(known_good, template_dir, token), encoding="utf-8") diff --git a/scripts/tooling/tests/test_report.py b/scripts/tooling/tests/test_report.py index fcffcfd0d15..69313608fa8 100644 --- a/scripts/tooling/tests/test_report.py +++ b/scripts/tooling/tests/test_report.py @@ -16,6 +16,7 @@ import pytest +from cli.misc.html_report import TEMPLATE_DIR from lib.known_good import KnownGood, load_known_good from lib.known_good.module import Metadata, Module from lib.html_report import generate_report, write_report @@ -78,27 +79,27 @@ def real_known_good() -> KnownGood: class TestGenerateReportStructure: def test_returns_string(self, minimal_known_good): - assert isinstance(generate_report(minimal_known_good), str) + assert isinstance(generate_report(minimal_known_good, TEMPLATE_DIR), str) def test_is_html(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert html.strip().startswith("") assert "" in html def test_contains_title(self, minimal_known_good): - assert "Known Good Status" in generate_report(minimal_known_good) + assert "Known Good Status" in generate_report(minimal_known_good, TEMPLATE_DIR) def test_contains_timestamp(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "2026-01-01T00:00:00+00:00Z" in html def test_contains_module_json(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "score_baselibs" in html assert "eclipse-score/baselibs" in html def test_embedded_json_is_valid(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) # Extract the JS array assigned to MODULES match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) assert match, "MODULES array not found in HTML" @@ -107,7 +108,7 @@ def test_embedded_json_is_valid(self, minimal_known_good): assert len(data) == 1 def test_module_entry_fields(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) entry = json.loads(match.group(1))[0] assert entry["name"] == "score_baselibs" @@ -126,56 +127,56 @@ def test_module_entry_fields(self, minimal_known_good): class TestGenerateReportGitHubIntegration: def test_github_api_url_pattern_present(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "api.github.com" in html def test_compare_endpoint_referenced(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "/compare/" in html def test_commits_endpoint_referenced(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "/commits/" in html def test_ahead_by_field_referenced(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "ahead_by" in html def test_token_stored_in_localstorage(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "gh_token" in html assert "localStorage" in html def test_cache_ttl_present(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "CACHE_TTL_MS" in html def test_cache_functions_present(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "loadFromCache" in html assert "saveToCache" in html def test_no_unauthenticated_api_calls(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) # Token is always attached — no conditional empty headers assert "headers: {}" not in html assert "authHeaders()" in html def test_no_token_shows_requires_pat(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "requires PAT" in html def test_token_input_always_present(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "gh-token" in html def test_no_cooldown_logic(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "remainingCooldownMs" not in html assert "startCooldownUI" not in html def test_no_oauth_code(self, minimal_known_good): - html = generate_report(minimal_known_good) + html = generate_report(minimal_known_good, TEMPLATE_DIR) assert "CLIENT_ID" not in html assert "startOAuth" not in html assert "sha256base64url" not in html @@ -189,7 +190,7 @@ def test_no_oauth_code(self, minimal_known_good): class TestGenerateReportMultiGroup: def test_all_modules_in_json(self, multi_group_known_good): - html = generate_report(multi_group_known_good) + html = generate_report(multi_group_known_good, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) data = json.loads(match.group(1)) names = {e["name"] for e in data} @@ -197,14 +198,14 @@ def test_all_modules_in_json(self, multi_group_known_good): assert "score_crates" in names def test_both_groups_present(self, multi_group_known_good): - html = generate_report(multi_group_known_good) + html = generate_report(multi_group_known_good, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) data = json.loads(match.group(1)) groups = {e["group"] for e in data} assert groups == {"target_sw", "tooling"} def test_filter_buttons_in_html(self, multi_group_known_good): - html = generate_report(multi_group_known_good) + html = generate_report(multi_group_known_good, TEMPLATE_DIR) assert "target_sw" in html assert "tooling" in html @@ -221,7 +222,7 @@ def test_module_with_no_metadata(self): "hash": "deadbeef", }) kg = KnownGood(modules={"tooling": {"score_crates": module}}, timestamp="") - html = generate_report(kg) + html = generate_report(kg, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) entry = json.loads(match.group(1))[0] assert entry["owner_repo"] == "eclipse-score/score-crates" @@ -232,14 +233,14 @@ def test_non_github_repo_owner_repo_is_none(self): "hash": "abc", }) kg = KnownGood(modules={"g": {"custom_mod": module}}, timestamp="") - html = generate_report(kg) + html = generate_report(kg, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) entry = json.loads(match.group(1))[0] assert entry["owner_repo"] is None def test_empty_modules(self): kg = KnownGood(modules={}, timestamp="2026-01-01") - html = generate_report(kg) + html = generate_report(kg, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) assert json.loads(match.group(1)) == [] @@ -252,18 +253,18 @@ def test_empty_modules(self): class TestWriteReport: def test_creates_file(self, tmp_path, minimal_known_good): out = tmp_path / "report.html" - write_report(minimal_known_good, out) + write_report(minimal_known_good, out, TEMPLATE_DIR) assert out.exists() def test_file_content_matches_generate(self, tmp_path, minimal_known_good): out = tmp_path / "report.html" - write_report(minimal_known_good, out) - assert out.read_text(encoding="utf-8") == generate_report(minimal_known_good) + write_report(minimal_known_good, out, TEMPLATE_DIR) + assert out.read_text(encoding="utf-8") == generate_report(minimal_known_good, TEMPLATE_DIR) def test_creates_parent_dirs_via_path(self, tmp_path, minimal_known_good): out = tmp_path / "sub" / "report.html" out.parent.mkdir(parents=True) - write_report(minimal_known_good, out) + write_report(minimal_known_good, out, TEMPLATE_DIR) assert out.exists() @@ -275,18 +276,18 @@ def test_creates_parent_dirs_via_path(self, tmp_path, minimal_known_good): @pytest.mark.skipif(not KNOWN_GOOD_JSON.exists(), reason="known_good.json not found") class TestReportFromRealFile: def test_generates_without_error(self, real_known_good): - html = generate_report(real_known_good) + html = generate_report(real_known_good, TEMPLATE_DIR) assert len(html) > 1000 def test_all_modules_embedded(self, real_known_good): - html = generate_report(real_known_good) + html = generate_report(real_known_good, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) data = json.loads(match.group(1)) total = sum(len(g) for g in real_known_good.modules.values()) assert len(data) == total def test_all_entries_have_required_fields(self, real_known_good): - html = generate_report(real_known_good) + html = generate_report(real_known_good, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) for entry in json.loads(match.group(1)): assert "name" in entry From 76d1d94a23a28fed320f42b5d98701023b97293e Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Fri, 6 Mar 2026 13:56:36 +0000 Subject: [PATCH 11/13] Align formatting --- .github/workflows/internal_tests.yml | 1 - scripts/tooling/cli/main.py | 1 + scripts/tooling/cli/misc/__init__.py | 1 + scripts/tooling/cli/misc/html_report.py | 4 +- scripts/tooling/lib/github.py | 6 ++- scripts/tooling/lib/html_report.py | 4 +- scripts/tooling/lib/known_good/known_good.py | 10 +---- scripts/tooling/tests/test_report.py | 44 +++++++++++++------- 8 files changed, 39 insertions(+), 32 deletions(-) diff --git a/.github/workflows/internal_tests.yml b/.github/workflows/internal_tests.yml index 2039100b254..16c1b797811 100644 --- a/.github/workflows/internal_tests.yml +++ b/.github/workflows/internal_tests.yml @@ -17,7 +17,6 @@ on: push: branches: - main - jobs: internal_tests: uses: eclipse-score/cicd-workflows/.github/workflows/tests.yml@main diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index d574415c418..a8e218abf67 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -8,6 +8,7 @@ def main() -> None: subparsers.required = True from scripts.tooling.cli.misc import register as _register_misc + _register_misc(subparsers) args = parser.parse_args() diff --git a/scripts/tooling/cli/misc/__init__.py b/scripts/tooling/cli/misc/__init__.py index 506f8d26292..17cf2488d75 100644 --- a/scripts/tooling/cli/misc/__init__.py +++ b/scripts/tooling/cli/misc/__init__.py @@ -7,4 +7,5 @@ def register(subparsers: argparse._SubParsersAction) -> None: misc_sub.required = True from scripts.tooling.cli.misc.html_report import register as _register_html_report + _register_html_report(misc_sub) diff --git a/scripts/tooling/cli/misc/html_report.py b/scripts/tooling/cli/misc/html_report.py index 9dd341377f9..819b837e642 100644 --- a/scripts/tooling/cli/misc/html_report.py +++ b/scripts/tooling/cli/misc/html_report.py @@ -23,9 +23,7 @@ def _resolve_path_from_bazel(path: Path) -> Path: def register(subparsers: argparse._SubParsersAction) -> None: - parser = subparsers.add_parser( - "html_report", help="Generate an HTML status report from known_good.json" - ) + parser = subparsers.add_parser("html_report", help="Generate an HTML status report from known_good.json") parser.add_argument( "--known_good", metavar="PATH", diff --git a/scripts/tooling/lib/github.py b/scripts/tooling/lib/github.py index dd2b8742093..7c07f25038a 100644 --- a/scripts/tooling/lib/github.py +++ b/scripts/tooling/lib/github.py @@ -84,7 +84,11 @@ def fetch_compare( except urllib.error.HTTPError as exc: _LOG.debug( "GitHub compare HTTP %s for %s %s...%s: %s", - exc.code, owner_repo, base_hash[:10], branch, exc.reason, + exc.code, + owner_repo, + base_hash[:10], + branch, + exc.reason, ) except OSError as exc: _LOG.debug("GitHub compare network error for %s: %s", owner_repo, exc) diff --git a/scripts/tooling/lib/html_report.py b/scripts/tooling/lib/html_report.py index f9932c42a34..58d9a3eea2e 100644 --- a/scripts/tooling/lib/html_report.py +++ b/scripts/tooling/lib/html_report.py @@ -66,9 +66,7 @@ def _enrich_with_compare_data( entry["behind_by"] = result.ahead_by entry["compare_status"] = result.status else: - _LOG.warning( - "Could not fetch compare data for %s@%s", entry["owner_repo"], entry["branch"] - ) + _LOG.warning("Could not fetch compare data for %s@%s", entry["owner_repo"], entry["branch"]) def generate_report( diff --git a/scripts/tooling/lib/known_good/known_good.py b/scripts/tooling/lib/known_good/known_good.py index dda189366e0..1d6bdfe0f5b 100644 --- a/scripts/tooling/lib/known_good/known_good.py +++ b/scripts/tooling/lib/known_good/known_good.py @@ -61,18 +61,12 @@ def load_known_good(path: Path) -> KnownGood: lines = text.splitlines() line = lines[e.lineno - 1] if 0 <= e.lineno - 1 < len(lines) else "" pointer = " " * (e.colno - 1) + "^" - hint = ( - "Possible causes: trailing comma, missing value, or extra comma." - if "Expecting value" in e.msg - else "" - ) + hint = "Possible causes: trailing comma, missing value, or extra comma." if "Expecting value" in e.msg else "" raise ValueError( f"Invalid JSON at line {e.lineno}, column {e.colno}\n{line}\n{pointer}\n{e.msg}. {hint}" ) from None if not isinstance(data, dict) or not isinstance(data.get("modules"), dict): - raise ValueError( - f"Invalid known_good.json at {path}: expected object with 'modules' dict" - ) + raise ValueError(f"Invalid known_good.json at {path}: expected object with 'modules' dict") return KnownGood.from_dict(data) diff --git a/scripts/tooling/tests/test_report.py b/scripts/tooling/tests/test_report.py index 69313608fa8..2a3651743dc 100644 --- a/scripts/tooling/tests/test_report.py +++ b/scripts/tooling/tests/test_report.py @@ -50,14 +50,20 @@ def minimal_known_good() -> KnownGood: @pytest.fixture def multi_group_known_good() -> KnownGood: - m1 = Module.from_dict("score_baselibs", { - "repo": "https://github.com/eclipse-score/baselibs.git", - "hash": "aaa", - }) - m2 = Module.from_dict("score_crates", { - "repo": "https://github.com/eclipse-score/score-crates.git", - "hash": "bbb", - }) + m1 = Module.from_dict( + "score_baselibs", + { + "repo": "https://github.com/eclipse-score/baselibs.git", + "hash": "aaa", + }, + ) + m2 = Module.from_dict( + "score_crates", + { + "repo": "https://github.com/eclipse-score/score-crates.git", + "hash": "bbb", + }, + ) return KnownGood( modules={ "target_sw": {"score_baselibs": m1}, @@ -217,10 +223,13 @@ def test_filter_buttons_in_html(self, multi_group_known_good): class TestGenerateReportEdgeCases: def test_module_with_no_metadata(self): - module = Module.from_dict("score_crates", { - "repo": "https://github.com/eclipse-score/score-crates.git", - "hash": "deadbeef", - }) + module = Module.from_dict( + "score_crates", + { + "repo": "https://github.com/eclipse-score/score-crates.git", + "hash": "deadbeef", + }, + ) kg = KnownGood(modules={"tooling": {"score_crates": module}}, timestamp="") html = generate_report(kg, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) @@ -228,10 +237,13 @@ def test_module_with_no_metadata(self): assert entry["owner_repo"] == "eclipse-score/score-crates" def test_non_github_repo_owner_repo_is_none(self): - module = Module.from_dict("custom_mod", { - "repo": "https://gitlab.com/some/repo.git", - "hash": "abc", - }) + module = Module.from_dict( + "custom_mod", + { + "repo": "https://gitlab.com/some/repo.git", + "hash": "abc", + }, + ) kg = KnownGood(modules={"g": {"custom_mod": module}}, timestamp="") html = generate_report(kg, TEMPLATE_DIR) match = re.search(r"const MODULES\s*=\s*(\[.*?\]);", html, re.DOTALL) From f24eb4241476315a783a2dde860fc90db5e1cb87 Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Fri, 6 Mar 2026 14:04:53 +0000 Subject: [PATCH 12/13] Cleanup generation --- .github/workflows/internal_tests.yml | 2 +- MODULE.bazel.lock | 21 +++-- bazel_common/score_python.MODULE.bazel | 2 +- docs/index.rst | 5 ++ pyproject.toml | 1 + scripts/tooling/cli/__init__.py | 12 +++ scripts/tooling/cli/main.py | 12 +++ scripts/tooling/cli/misc/__init__.py | 12 +++ scripts/tooling/cli/misc/html_report.py | 86 ++++++++++++++++++- scripts/tooling/lib/html_report.py | 108 ------------------------ scripts/tooling/tests/test_report.py | 3 +- 11 files changed, 141 insertions(+), 123 deletions(-) delete mode 100644 scripts/tooling/lib/html_report.py diff --git a/.github/workflows/internal_tests.yml b/.github/workflows/internal_tests.yml index 16c1b797811..ebac8a31db6 100644 --- a/.github/workflows/internal_tests.yml +++ b/.github/workflows/internal_tests.yml @@ -21,4 +21,4 @@ jobs: internal_tests: uses: eclipse-score/cicd-workflows/.github/workflows/tests.yml@main with: - bazel-target: "//scripts/tooling_tests" + bazel-target: "test //scripts/tooling_tests" diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 991e8543a17..d3c9febd033 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -59,7 +59,8 @@ "https://bcr.bazel.build/modules/aspect_rules_lint/2.0.0/source.json": "3c3a55b5b424100feca2fd656dcdcd8a0c9fd3304ce609ce71a4d6d46d00a03c", "https://bcr.bazel.build/modules/aspect_rules_py/1.0.0/MODULE.bazel": "8eb29876512d3242af50a424300bec5c5f8957b455963df5f618cb7fd4e8ae19", "https://bcr.bazel.build/modules/aspect_rules_py/1.4.0/MODULE.bazel": "6fd29b93207a31445d5d3ab9d9882fd5511e43c95e8e82e7492872663720fd44", - "https://bcr.bazel.build/modules/aspect_rules_py/1.4.0/source.json": "fb1ba946478fb6dbb26d49307d756b0fd2ff88be339af23c39c0397d59143d2c", + "https://bcr.bazel.build/modules/aspect_rules_py/1.5.2/MODULE.bazel": "7e34964847c5ddf391a927a765d8b26147df5b25242f2da4fa3c4a77671383bb", + "https://bcr.bazel.build/modules/aspect_rules_py/1.5.2/source.json": "020d2bd6b07210cee97226ee2215d80c4cf91e455a6702c9af1e5788d072e673", "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.0/MODULE.bazel": "d0045b5eabb012be550a609589b3e5e47eba682344b19cfd9365d4d896ed07df", "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.0/source.json": "5593e3f1cd0dd5147f7748e163307fd5c2e1077913d6945b58739ad8d770a290", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.5/MODULE.bazel": "5a3c8013c3ba9ebc0a65efda40e4376b869e1260873c98020504feed55244ce8", @@ -583,6 +584,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_lint/2.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.4.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_py/1.5.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_rules_ts/3.6.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_tools_telemetry/0.2.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "not found", @@ -1141,8 +1143,8 @@ }, "@@aspect_rules_py+//py:extensions.bzl%py_tools": { "general": { - "bzlTransitiveDigest": "dAWBz6nfg2GIP7kchPavcBdWoHNQ67aXhXo4MPJJqNk=", - "usagesDigest": "NC1b49l5tenTBVWEUGzzC0j5Kg1GH+l5lBw5JRCldIU=", + "bzlTransitiveDigest": "i1RUlZ7o0KqC5rka4jpWVnev/OpAD7TaATTPe15qmRw=", + "usagesDigest": "NF8rrFAtM0OxyfflUbQNlgnkIH3YSHU4jCm3dhvpGY4=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -1190,25 +1192,25 @@ } }, "rules_py_tools.darwin_amd64": { - "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:repo.bzl%prebuilt_tool_repo", "attributes": { "platform": "darwin_amd64" } }, "rules_py_tools.darwin_arm64": { - "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:repo.bzl%prebuilt_tool_repo", "attributes": { "platform": "darwin_arm64" } }, "rules_py_tools.linux_amd64": { - "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:repo.bzl%prebuilt_tool_repo", "attributes": { "platform": "linux_amd64" } }, "rules_py_tools.linux_arm64": { - "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:tools.bzl%prebuilt_tool_repo", + "repoRuleId": "@@aspect_rules_py+//py/private/toolchain:repo.bzl%prebuilt_tool_repo", "attributes": { "platform": "linux_arm64" } @@ -1241,6 +1243,11 @@ "aspect_bazel_lib", "aspect_bazel_lib+" ], + [ + "aspect_rules_py+", + "bazel_skylib", + "bazel_skylib+" + ], [ "aspect_rules_py+", "bazel_tools", diff --git a/bazel_common/score_python.MODULE.bazel b/bazel_common/score_python.MODULE.bazel index 79d62fc89ab..08ceadcd674 100644 --- a/bazel_common/score_python.MODULE.bazel +++ b/bazel_common/score_python.MODULE.bazel @@ -11,7 +11,7 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* bazel_dep(name = "rules_python", version = "1.8.3") -bazel_dep(name = "aspect_rules_py", version = "1.4.0") +bazel_dep(name = "aspect_rules_py", version = "1.5.2") PYTHON_VERSION = "3.12" diff --git a/docs/index.rst b/docs/index.rst index 188e48d8a8f..c99a0f4638b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,6 +30,11 @@ Newest Release Notes newest_release_note = max(all_release_notes, key=lambda s: int(re.search(r'v(\d+)', s["id"]).group(1))) results = [newest_release_note] +Current Integration Status Overview +----------------------------------- + +`View dashboard (points always to main for now) `_ + Explore the documentation ------------------------- .. toctree:: diff --git a/pyproject.toml b/pyproject.toml index f548dfbe698..6b8b4dba378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,6 @@ [tool.pytest] pythonpath = [ + ".", "scripts/tooling", ] testpaths = ["scripts/tooling/tests"] diff --git a/scripts/tooling/cli/__init__.py b/scripts/tooling/cli/__init__.py index e69de29bb2d..ca5de742e9c 100644 --- a/scripts/tooling/cli/__init__.py +++ b/scripts/tooling/cli/__init__.py @@ -0,0 +1,12 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* diff --git a/scripts/tooling/cli/main.py b/scripts/tooling/cli/main.py index a8e218abf67..f66e6a5c8cb 100644 --- a/scripts/tooling/cli/main.py +++ b/scripts/tooling/cli/main.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* import argparse import sys diff --git a/scripts/tooling/cli/misc/__init__.py b/scripts/tooling/cli/misc/__init__.py index 17cf2488d75..01ca28786ab 100644 --- a/scripts/tooling/cli/misc/__init__.py +++ b/scripts/tooling/cli/misc/__init__.py @@ -1,3 +1,15 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* import argparse diff --git a/scripts/tooling/cli/misc/html_report.py b/scripts/tooling/cli/misc/html_report.py index 819b837e642..5fa83771e8a 100644 --- a/scripts/tooling/cli/misc/html_report.py +++ b/scripts/tooling/cli/misc/html_report.py @@ -1,7 +1,31 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +from __future__ import annotations + import argparse +import json +import logging import os import sys from pathlib import Path +from typing import Any, Optional + +from jinja2 import Environment, FileSystemLoader, select_autoescape + +from scripts.tooling.lib.github import fetch_compare +from scripts.tooling.lib.known_good import KnownGood, load_known_good + +_LOG = logging.getLogger(__name__) TEMPLATE_DIR = Path(__file__).parent / "assets" @@ -22,6 +46,63 @@ def _resolve_path_from_bazel(path: Path) -> Path: return path.resolve() +def _collect_entries(known_good: KnownGood) -> list[dict[str, Any]]: + entries = [] + for group_name, group_modules in known_good.modules.items(): + for module in group_modules.values(): + try: + owner_repo = module.owner_repo + except ValueError: + owner_repo = None + entries.append( + { + "name": module.name, + "group": group_name, + "repo": module.repo, + "owner_repo": owner_repo, + "hash": module.hash, + "version": module.version, + "branch": module.branch, + "current_hash": None, + "behind_by": None, + "compare_status": None, + } + ) + return entries + + +def _enrich_with_compare_data(entries: list[dict[str, Any]], token: str) -> None: + for entry in entries: + if not entry.get("owner_repo") or not entry.get("hash") or entry.get("version"): + continue + result = fetch_compare(entry["owner_repo"], entry["hash"], entry["branch"], token) + if result: + entry["current_hash"] = result.head_sha + entry["behind_by"] = result.ahead_by + entry["compare_status"] = result.status + else: + _LOG.warning("Could not fetch compare data for %s@%s", entry["owner_repo"], entry["branch"]) + + +def generate_report(known_good: KnownGood, token: Optional[str] = None) -> str: + entries = _collect_entries(known_good) + if token: + _enrich_with_compare_data(entries, token) + env = Environment( + loader=FileSystemLoader(TEMPLATE_DIR), + autoescape=select_autoescape(["html"]), + ) + tmpl = env.get_template("report_template.html") + return tmpl.render( + modules_json=json.dumps(entries, indent=2), + timestamp=known_good.timestamp, + ) + + +def write_report(known_good: KnownGood, output_path: Path, token: Optional[str] = None) -> None: + Path(output_path).write_text(generate_report(known_good, token), encoding="utf-8") + + def register(subparsers: argparse._SubParsersAction) -> None: parser = subparsers.add_parser("html_report", help="Generate an HTML status report from known_good.json") parser.add_argument( @@ -40,9 +121,6 @@ def register(subparsers: argparse._SubParsersAction) -> None: def _run(args: argparse.Namespace) -> int: - from scripts.tooling.lib.html_report import write_report - from scripts.tooling.lib.known_good import load_known_good - known_good_path = Path(args.known_good) / "known_good.json" try: known_good = load_known_good(known_good_path) @@ -52,7 +130,7 @@ def _run(args: argparse.Namespace) -> int: token = os.environ.get("GITHUB_TOKEN") output = _resolve_path_from_bazel(Path(args.output)) - write_report(known_good, output, TEMPLATE_DIR, token=token) + write_report(known_good, output, token=token) if token: print(f"Report written to {output} (current hashes fetched from GitHub)") else: diff --git a/scripts/tooling/lib/html_report.py b/scripts/tooling/lib/html_report.py deleted file mode 100644 index 58d9a3eea2e..00000000000 --- a/scripts/tooling/lib/html_report.py +++ /dev/null @@ -1,108 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -"""HTML report generator for known_good.json status.""" - -from __future__ import annotations - -import json -import logging -from pathlib import Path -from typing import Any, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape - -from .github import fetch_compare -from .known_good import KnownGood - -_LOG = logging.getLogger(__name__) - - -def _collect_entries(known_good: KnownGood) -> list[dict[str, Any]]: - entries = [] - for group_name, group_modules in known_good.modules.items(): - for module in group_modules.values(): - try: - owner_repo = module.owner_repo - except ValueError: - owner_repo = None - entries.append( - { - "name": module.name, - "group": group_name, - "repo": module.repo, - "owner_repo": owner_repo, - "hash": module.hash, - "version": module.version, - "branch": module.branch, - "current_hash": None, - "behind_by": None, - "compare_status": None, - } - ) - return entries - - -def _enrich_with_compare_data( - entries: list[dict[str, Any]], - token: str, -) -> None: - """Fetch compare data for each GitHub module and store it in the entry.""" - for entry in entries: - if not entry.get("owner_repo") or not entry.get("hash") or entry.get("version"): - continue - result = fetch_compare(entry["owner_repo"], entry["hash"], entry["branch"], token) - if result: - entry["current_hash"] = result.head_sha - entry["behind_by"] = result.ahead_by - entry["compare_status"] = result.status - else: - _LOG.warning("Could not fetch compare data for %s@%s", entry["owner_repo"], entry["branch"]) - - -def generate_report( - known_good: KnownGood, - template_dir: Path, - token: Optional[str] = None, -) -> str: - """Return a self-contained HTML report string for *known_good*. - - Args: - known_good: Parsed known_good.json data. - template_dir: Directory containing ``report_template.html``. - token: Optional GitHub PAT / ``GITHUB_TOKEN``. When provided the - compare API is called at generation time, embedding exact - commit-behind counts and current HEAD hashes so the viewer - needs no PAT. - """ - entries = _collect_entries(known_good) - if token: - _enrich_with_compare_data(entries, token) - env = Environment( - loader=FileSystemLoader(template_dir), - autoescape=select_autoescape(["html"]), - ) - tmpl = env.get_template("report_template.html") - return tmpl.render( - modules_json=json.dumps(entries, indent=2), - timestamp=known_good.timestamp, - ) - - -def write_report( - known_good: KnownGood, - output_path: Path, - template_dir: Path, - token: Optional[str] = None, -) -> None: - """Write the HTML report to *output_path*.""" - Path(output_path).write_text(generate_report(known_good, template_dir, token), encoding="utf-8") diff --git a/scripts/tooling/tests/test_report.py b/scripts/tooling/tests/test_report.py index 2a3651743dc..5b9a0201778 100644 --- a/scripts/tooling/tests/test_report.py +++ b/scripts/tooling/tests/test_report.py @@ -16,10 +16,9 @@ import pytest -from cli.misc.html_report import TEMPLATE_DIR +from cli.misc.html_report import TEMPLATE_DIR, generate_report, write_report from lib.known_good import KnownGood, load_known_good from lib.known_good.module import Metadata, Module -from lib.html_report import generate_report, write_report # --------------------------------------------------------------------------- From 80355ee172c9f2eb94693e26348347137e4bd08e Mon Sep 17 00:00:00 2001 From: Pawel Rutka Date: Fri, 6 Mar 2026 14:27:48 +0000 Subject: [PATCH 13/13] Fix bazel --- .github/workflows/internal_tests.yml | 2 +- scripts/tooling/BUILD | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/internal_tests.yml b/.github/workflows/internal_tests.yml index ebac8a31db6..c1d0f518313 100644 --- a/.github/workflows/internal_tests.yml +++ b/.github/workflows/internal_tests.yml @@ -21,4 +21,4 @@ jobs: internal_tests: uses: eclipse-score/cicd-workflows/.github/workflows/tests.yml@main with: - bazel-target: "test //scripts/tooling_tests" + bazel-target: "test //scripts/tooling:tooling_tests" diff --git a/scripts/tooling/BUILD b/scripts/tooling/BUILD index 5fd5b5723f9..40748469520 100644 --- a/scripts/tooling/BUILD +++ b/scripts/tooling/BUILD @@ -39,18 +39,23 @@ py_library( visibility = ["//visibility:public"], ) +# CLI library target (shared between binary and tests) +py_library( + name = "cli", + srcs = glob(["cli/**/*.py"]), + data = [ + ":cli/misc/assets/report_template.html", + ], + deps = [":lib"] + all_requirements, +) + # CLI binary target py_binary( name = "tooling", - srcs = glob([ - "cli/**/*.py", - ]), - data = [ - "cli/misc/assets/report_template.html", - ], + srcs = ["cli/main.py"], main = "cli/main.py", visibility = ["//visibility:public"], - deps = [":lib"] + all_requirements, + deps = [":cli"], ) # Tests target @@ -62,6 +67,7 @@ score_py_pytest( ], pytest_config = "//:pyproject.toml", deps = [ + ":cli", ":lib", ] + all_requirements, )