From a83d4b5a4f150f9c6272bc9ca3c3bb560e72f797 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 4 Aug 2025 14:33:22 -0500 Subject: [PATCH 01/20] Add test for matrix generation --- .github/workflows/test.yml | 57 +++ .gitignore | 4 + .tool-versions | 2 + pyproject.toml | 19 + src/launch_github.py | 121 ++++++ test/__init__.py | 0 test/integration/__init__.py | 0 test/integration/conftest.py | 112 ++++++ .../test_reusable_github_matrix_tg.py | 148 ++++++++ test/lib/__init__.py | 0 uv.lock | 357 ++++++++++++++++++ 11 files changed, 820 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 pyproject.toml create mode 100644 src/launch_github.py create mode 100644 test/__init__.py create mode 100644 test/integration/__init__.py create mode 100644 test/integration/conftest.py create mode 100644 test/integration/test_reusable_github_matrix_tg.py create mode 100644 test/lib/__init__.py create mode 100644 uv.lock diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..114590d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,57 @@ +name: Run Tests + +on: + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + build: + name: Run Tests + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ "3.13" ] + steps: + - uses: actions/checkout@8edcb1bdb4e267140fa742c62e395cd74f332709 + + - name: Restore cached asdf tools + uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 + id: cache + with: + path: ~/.asdf + key: ${{ runner.os }}-tool-versions-${{ hashFiles('.tool-versions') }} + + - name: asdf install + uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 + + - name: Cache asdf tools + uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 + id: save-cache + if: ${{ steps.cache.outputs.cache-hit != 'true' }} + with: + path: ~/.asdf + key: ${{ runner.os }}-tool-versions-${{ hashFiles('.tool-versions') }} + + - name: Set up Python ${{ matrix.python-version }} + uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e + id: app-token + with: + app-id: ${{ vars.APP_ID }} + owner: ${{ vars.APP_OWNER }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Test with pytest + env: + LAUNCH_WORKFLOWS_REF_TO_TEST: ${{ github.ref_name }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + uv run pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e74b64f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.pytest_cache +__pycache__ +*.py[cod] +.venv \ No newline at end of file diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..0690d78 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +uv system 0.8.4 +git system 2.50.1 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2452e55 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "launch-workflows-testing" +version = "0.0.0" +description = "Tests for the launch-workflows project" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "gitpython>=3.1.45", + "pygithub>=2.7.0", + "pytest>=8.4.1", +] + +[tool.pytest.ini_options] +pythonpath = "src" +minversion = "8.0" +addopts = "-ra" +testpaths = [ + "test" +] diff --git a/src/launch_github.py b/src/launch_github.py new file mode 100644 index 0000000..e6414d1 --- /dev/null +++ b/src/launch_github.py @@ -0,0 +1,121 @@ +import logging +import os +import tempfile +import zipfile +from enum import StrEnum +from pathlib import Path +from time import sleep + +import requests +from github import Auth, Consts, Github +from github.Repository import Repository +from github.Workflow import Workflow +from github.WorkflowRun import WorkflowRun + +logger = logging.getLogger(__name__) + + +class WorkflowRunStatus(StrEnum): + COMPLETED = "completed" + FAILED = "failed" + + +class WorkflowRunConclusion(StrEnum): + SUCCESS = "success" + FAILURE = "failure" + CANCELLED = "cancelled" + SKIPPED = "skipped" + TIMED_OUT = "timed_out" + + +def read_github_token() -> str: + try: + return os.environ["GITHUB_TOKEN"] + except KeyError: + raise RuntimeError( + "ERROR: The GITHUB_TOKEN environment variable is not set. You must set this environment variable." + ) + + +def get_github_instance(token: str | None = None, timeout: int | None = None) -> Github: + if timeout is None: + timeout = Consts.DEFAULT_TIMEOUT + if not token: + token = read_github_token() + auth = Auth.Token(token) + return Github(auth=auth, timeout=timeout) + + +def populate_readme_file( + repository: Repository, branch: str = "main", content: str = "# README" +): + repository.create_file( + path="README.md", + message="Add README", + content=content, + branch=branch, + ) + + +def wait_for_workflow_run_create(workflow: Workflow, branch: str, timeout=60): + """ + Wait for a workflow run to be created. + """ + for _ in range(timeout): + runs = list(workflow.get_runs(branch=branch)) + if runs: + return runs[0] + sleep(1) + raise TimeoutError("A workflow run did not appear within the timeout period.") + + +def wait_for_workflow_run_completion( + workflow_run: WorkflowRun, timeout=60 +) -> WorkflowRunStatus: + """ + Wait for a workflow run to be completed. + """ + for _ in range(timeout): + workflow_run.update() + status = workflow_run.status + if status in ( + WorkflowRunStatus.COMPLETED, + WorkflowRunStatus.FAILED, + ): + return status + sleep(1) + breakpoint() + raise TimeoutError("Workflow run did not complete within the timeout period.") + + +def get_workflow_run_logs( + workflow_run: WorkflowRun, drop_log_timestamps: bool = False +) -> str: + """ + Retrieve the logs of a workflow run. + """ + logs_url = workflow_run.logs_url + response = requests.get( + logs_url, + headers={"Authorization": f"Bearer {read_github_token()}"}, + ) + response.raise_for_status() + + with tempfile.TemporaryDirectory() as temp_dir: + zip_path = Path(temp_dir).joinpath("logs.zip") + with open(zip_path, "wb") as f: + f.write(response.content) + extract_dir = Path(temp_dir).joinpath("logs") + extract_dir.mkdir(exist_ok=True) + + with zipfile.ZipFile(zip_path, "r") as zip_ref: + zip_ref.extractall(extract_dir) + + # Return contents of the text file at the top of the extract path + text_file = list(extract_dir.glob("*.txt"))[0] + contents = text_file.read_text(encoding="utf-8") + + if drop_log_timestamps: + contents = "\n".join([line[29:] for line in contents.splitlines()]) + + return contents diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/__init__.py b/test/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/conftest.py b/test/integration/conftest.py new file mode 100644 index 0000000..ac032c3 --- /dev/null +++ b/test/integration/conftest.py @@ -0,0 +1,112 @@ +import os +import pathlib +import random +import string +from time import sleep +from typing import Generator + +import pytest +from git.repo import Repo +from github.Organization import Organization +from github.Repository import Repository + +from src.launch_github import get_github_instance + + +@pytest.fixture(scope="session") +def test_organization_name(): + yield "chris-testing-org" + + +@pytest.fixture(scope="session") +def github_instance(): + return get_github_instance() + + +@pytest.fixture(scope="function") +def organization( + github_instance, test_organization_name +) -> Generator[Organization, None, None]: + return github_instance.get_organization(test_organization_name) + + +@pytest.fixture(scope="function") +def repo_name() -> Generator[str, None, None]: + suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8)) + return f"test-repo-{suffix}" + + +@pytest.fixture(scope="function") +def organization_repo(organization, repo_name) -> Generator[Repository, None, None]: + repo = organization.create_repo( + name=repo_name, + description="Test Repository for Integration Tests", + private=False, + visibility="public", + auto_init=False, + ) + + yield repo + + repo.delete() + + +@pytest.fixture(scope="function") +def local_repo(tmp_path) -> Generator[pathlib.Path, None, None]: + """ + Fixture to provide a local repository path for testing. + This creates a temporary directory that can be used as a local repository. + """ + repo_path = tmp_path / "test_repo" + repo_path.mkdir() + + # Initialize a git repository in the temporary directory + os.system(f"git init {repo_path}") + + yield repo_path + + # Cleanup: remove the temporary directory after the test + if repo_path.exists(): + os.system(f"rm -rf {repo_path}") + + +@pytest.fixture(scope="function") +def temporary_repository( + test_organization_name, organization, repo_name, tmp_path +) -> Generator[tuple[Repository, Repo], None, None]: + """ + Fixture to create a temporary GitHub repository for testing. + This repository will be created in the test organization and deleted after the test. + """ + github_repo = organization.create_repo( + name=repo_name, + description="Temporary Repository for Testing", + private=False, + visibility="public", + auto_init=False, + ) + + max_tries = 10 + found = False + attempt = 0 + while not found and attempt < max_tries: + for repo in organization.get_repos(): + if repo.name == repo_name: + found = True + break + sleep(1) + attempt += 1 + + if not found: + raise RuntimeError( + f"Repository {repo_name} not found on GitHub after {max_tries} attempts!" + ) + + local_repo = Repo.clone_from( + f"https://github.com/{test_organization_name}/{repo_name}.git", + tmp_path.joinpath(repo_name), + ) + + yield github_repo, local_repo + + github_repo.delete() diff --git a/test/integration/test_reusable_github_matrix_tg.py b/test/integration/test_reusable_github_matrix_tg.py new file mode 100644 index 0000000..9cdf873 --- /dev/null +++ b/test/integration/test_reusable_github_matrix_tg.py @@ -0,0 +1,148 @@ +import os +from time import sleep + +from github.Repository import Repository + +from src.launch_github import ( + WorkflowRunConclusion, + WorkflowRunStatus, + get_workflow_run_logs, + populate_readme_file, + wait_for_workflow_run_completion, + wait_for_workflow_run_create, +) + +LAUNCH_WORKFLOWS_REF_TO_TEST = os.environ.get("LAUNCH_WORKFLOWS_REF_TO_TEST", "main") + + +def populate_workflow_file(repository: Repository): + content = f""" +name: GitHub Matrix Workflow + +on: + pull_request: + +jobs: + build-matrix: + permissions: + contents: read + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-github-matrix-tg.yml@{LAUNCH_WORKFLOWS_REF_TO_TEST} + with: + platform_environment: sandbox +""" + repository.create_file( + ".github/workflows/github_matrix.yml", + content=content, + branch="main", + message="Add workflow file for reusable GitHub matrix", + ) + + +def populate_platform_folder( + repository: Repository, + environment: str, + region: str, + instance: str, + branch: str = "main", +): + repository.create_file( + path=f"platform/{environment}/{region}/{instance}/test.txt", + message=f"Add test environment file for {environment} in {region}/{instance}", + content="Test Environment File", + branch=branch, + ) + + +def test_reusable_github_matrix_tg(temporary_repository): + """ + Test the reusable GitHub matrix template with a temporary repository. + """ + github_repo, _ = temporary_repository + + assert github_repo.name.startswith("test-repo-") + assert github_repo.private is False + assert github_repo.visibility == "public" + + populate_workflow_file(repository=github_repo) + + github_repo.create_git_ref( + "refs/heads/test/matrix-happy-path", github_repo.get_branch("main").commit.sha + ) + sleep(1) + populate_readme_file(github_repo, branch="test/matrix-happy-path") + populate_platform_folder( + repository=github_repo, + environment="sandbox", + region="us-east-1", + instance="000", + branch="test/matrix-happy-path", + ) + + github_repo.create_git_ref( + "refs/heads/test/matrix-missing-platform-folder", + github_repo.get_branch("main").commit.sha, + ) + sleep(1) + populate_readme_file(github_repo, branch="test/matrix-missing-platform-folder") + + github_repo.create_pull( + base="main", + head="test/matrix-happy-path", + title="Test Reusable GitHub Matrix Template - Happy Path", + body="This is a test pull request to validate the reusable GitHub matrix template with a happy path scenario.", + ) + github_repo.create_pull( + base="main", + head="test/matrix-missing-platform-folder", + title="Test Reusable GitHub Matrix Template - Missing Platform Folder", + body="This is a test pull request to validate the reusable GitHub matrix template with a missing platform folder. A failure is expected.", + ) + + workflow = github_repo.get_workflow(id_or_file_name="github_matrix.yml") + + run_happy_path = wait_for_workflow_run_create( + workflow, branch="test/matrix-happy-path" + ) + status_happy_path = wait_for_workflow_run_completion(run_happy_path) + if status_happy_path != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for happy path did not complete successfully: {status_happy_path}" + ) + if run_happy_path.conclusion != WorkflowRunConclusion.SUCCESS: + raise AssertionError( + f"Workflow run for happy path did not succeed as expected: {run_happy_path.conclusion}" + ) + + run_missing_platform_folder = wait_for_workflow_run_create( + workflow, branch="test/matrix-missing-platform-folder" + ) + status_missing_platform_folder = wait_for_workflow_run_completion( + run_missing_platform_folder + ) + if status_missing_platform_folder != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for missing platform folder did not complete as expected: {status_missing_platform_folder}" + ) + if run_missing_platform_folder.conclusion != WorkflowRunConclusion.FAILURE: + raise AssertionError( + f"Workflow run for missing platform folder did not fail as expected: {run_missing_platform_folder.conclusion}" + ) + + logs_happy_path = get_workflow_run_logs(run_happy_path, drop_log_timestamps=True) + logs_missing_platform_folder = get_workflow_run_logs(run_missing_platform_folder) + + happy_path_expected_lines = [ + "Generated the following environment matrix:", + "{", + ' "terragrunt_environment": [', + " {", + ' "environment": "sandbox",', + ' "region": "us-east-1",', + ' "instance": "000"', + " }", + " ]", + "}", + ] + + assert all(line in logs_happy_path for line in happy_path_expected_lines) + assert "FileNotFoundError" in logs_missing_platform_folder diff --git a/test/lib/__init__.py b/test/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..69ab2da --- /dev/null +++ b/uv.lock @@ -0,0 +1,357 @@ +version = 1 +requires-python = ">=3.11" + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "cryptography" +version = "45.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092 }, + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926 }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235 }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785 }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050 }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379 }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355 }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087 }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873 }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651 }, + { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050 }, + { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224 }, + { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143 }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780 }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091 }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711 }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299 }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558 }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020 }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759 }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991 }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189 }, + { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769 }, + { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016 }, + { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878 }, + { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447 }, + { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778 }, + { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627 }, + { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593 }, + { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106 }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "launch-workflows-testing" +version = "0.0.0" +source = { virtual = "." } +dependencies = [ + { name = "gitpython" }, + { name = "pygithub" }, + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [ + { name = "gitpython", specifier = ">=3.1.45" }, + { name = "pygithub", specifier = ">=2.7.0" }, + { name = "pytest", specifier = ">=8.4.1" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pygithub" +version = "2.7.0" +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/6a/a7/403e04aa96e2d94e1518d518d69718c2ba978c8d3ffa4ab3b101b94dbafa/pygithub-2.7.0.tar.gz", hash = "sha256:7cd6eafabb09b5369afba3586d86b1f1ad6f1326d2ff01bc47bb26615dce4cbb", size = 3707928 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/76/d768dd31322173b3956692b75471ac37bf3759c7abb603152f6a9b6594a8/pygithub-2.7.0-py3-none-any.whl", hash = "sha256:40ecbfe26dc55cc34ab4b0ffa1d455e6f816ef9a2bc8d6f5ad18ce572f163700", size = 416514 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pynacl" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920 }, + { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722 }, + { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087 }, + { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678 }, + { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660 }, + { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824 }, + { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912 }, + { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624 }, + { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +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/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, +] From f39b9988c062c94b0b2a98879144c918308bd837 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 4 Aug 2025 14:37:22 -0500 Subject: [PATCH 02/20] Reverse .tool-versions order --- .tool-versions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index 0690d78..5a04056 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -uv system 0.8.4 -git system 2.50.1 +uv 0.8.4 system +git 2.50.1 system From b31eb4969bba2385335dc573ddaa90db7c904e5b Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 4 Aug 2025 14:42:49 -0500 Subject: [PATCH 03/20] Drop git requirement from .tool-versions --- .tool-versions | 1 - 1 file changed, 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 5a04056..f5eecfb 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1 @@ uv 0.8.4 system -git 2.50.1 system From a09f5c25097cae9aef375c3a0efb7893b236d971 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 4 Aug 2025 14:43:24 -0500 Subject: [PATCH 04/20] Drop git requirement from .tool-versions --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index f5eecfb..9c24ff1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -uv 0.8.4 system +uv 0.8.4 From 2f90d03805203a06323e2331f34838071e8ea272 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 4 Aug 2025 15:07:42 -0500 Subject: [PATCH 05/20] Sleep on failure so I can try to see what's going on --- test/integration/test_reusable_github_matrix_tg.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/integration/test_reusable_github_matrix_tg.py b/test/integration/test_reusable_github_matrix_tg.py index 9cdf873..5fa4a3e 100644 --- a/test/integration/test_reusable_github_matrix_tg.py +++ b/test/integration/test_reusable_github_matrix_tg.py @@ -105,10 +105,12 @@ def test_reusable_github_matrix_tg(temporary_repository): ) status_happy_path = wait_for_workflow_run_completion(run_happy_path) if status_happy_path != WorkflowRunStatus.COMPLETED: + sleep(60) raise AssertionError( f"Workflow run for happy path did not complete successfully: {status_happy_path}" ) if run_happy_path.conclusion != WorkflowRunConclusion.SUCCESS: + sleep(60) raise AssertionError( f"Workflow run for happy path did not succeed as expected: {run_happy_path.conclusion}" ) @@ -120,10 +122,12 @@ def test_reusable_github_matrix_tg(temporary_repository): run_missing_platform_folder ) if status_missing_platform_folder != WorkflowRunStatus.COMPLETED: + sleep(60) raise AssertionError( f"Workflow run for missing platform folder did not complete as expected: {status_missing_platform_folder}" ) if run_missing_platform_folder.conclusion != WorkflowRunConclusion.FAILURE: + sleep(60) raise AssertionError( f"Workflow run for missing platform folder did not fail as expected: {run_missing_platform_folder.conclusion}" ) From 1e2e0294de31c4ce72aa113d1c4483dea97fb504 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 4 Aug 2025 15:11:39 -0500 Subject: [PATCH 06/20] Use head_ref --- .github/workflows/test.yml | 2 +- test/integration/test_reusable_github_matrix_tg.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 114590d..e1e9bbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,7 +51,7 @@ jobs: - name: Test with pytest env: - LAUNCH_WORKFLOWS_REF_TO_TEST: ${{ github.ref_name }} + LAUNCH_WORKFLOWS_REF_TO_TEST: ${{ github.head_ref }} GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} run: | uv run pytest diff --git a/test/integration/test_reusable_github_matrix_tg.py b/test/integration/test_reusable_github_matrix_tg.py index 5fa4a3e..9cdf873 100644 --- a/test/integration/test_reusable_github_matrix_tg.py +++ b/test/integration/test_reusable_github_matrix_tg.py @@ -105,12 +105,10 @@ def test_reusable_github_matrix_tg(temporary_repository): ) status_happy_path = wait_for_workflow_run_completion(run_happy_path) if status_happy_path != WorkflowRunStatus.COMPLETED: - sleep(60) raise AssertionError( f"Workflow run for happy path did not complete successfully: {status_happy_path}" ) if run_happy_path.conclusion != WorkflowRunConclusion.SUCCESS: - sleep(60) raise AssertionError( f"Workflow run for happy path did not succeed as expected: {run_happy_path.conclusion}" ) @@ -122,12 +120,10 @@ def test_reusable_github_matrix_tg(temporary_repository): run_missing_platform_folder ) if status_missing_platform_folder != WorkflowRunStatus.COMPLETED: - sleep(60) raise AssertionError( f"Workflow run for missing platform folder did not complete as expected: {status_missing_platform_folder}" ) if run_missing_platform_folder.conclusion != WorkflowRunConclusion.FAILURE: - sleep(60) raise AssertionError( f"Workflow run for missing platform folder did not fail as expected: {run_missing_platform_folder.conclusion}" ) From f66c73be347847d64409728d7c11e8ea64a7a1e0 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:03:58 -0500 Subject: [PATCH 07/20] Add automated test for PR labeling --- .../test_reusable_pr_label_by_branch.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 test/integration/test_reusable_pr_label_by_branch.py diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/test_reusable_pr_label_by_branch.py new file mode 100644 index 0000000..ee992bf --- /dev/null +++ b/test/integration/test_reusable_pr_label_by_branch.py @@ -0,0 +1,158 @@ +import os +from time import sleep + +from github.Repository import Repository + +from src.launch_github import ( + WorkflowRunConclusion, + WorkflowRunStatus, + get_workflow_run_logs, + populate_readme_file, + wait_for_workflow_run_completion, + wait_for_workflow_run_create, +) + +LAUNCH_WORKFLOWS_REF_TO_TEST = os.environ.get("LAUNCH_WORKFLOWS_REF_TO_TEST", "main") + + +def populate_release_drafter_config(repository: Repository): + content = """ +--- +name-template: "$RESOLVED_VERSION" +tag-template: "$RESOLVED_VERSION" +template: | + # Changelog + + $CHANGES + + --- + + See details of [all code changes](https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION) since previous release. + +categories: + - title: ":warning: Breaking Changes" + labels: + - "major" + - title: "🚀 Features" + labels: + - "minor" + - title: "🔧 Fixes" + collapse-after: 3 + labels: + - "patch" + +autolabeler: + - label: "major" + branch: + - '/(patch|bug|fix|feature)!\/.+/' + - label: "minor" + branch: + - '/feature\/.+/' + - label: "patch" + branch: + - '/(patch|bug|fix)\/.+/' + +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" + +version-resolver: + major: + labels: + - "major" + minor: + labels: + - "minor" + patch: + labels: + - "patch" + - "dependencies" + default: patch + +""" + + repository.create_file( + ".github/release-drafter.yml", + content=content, + branch="main", + message="Add Release Drafter configuration", + ) + + +def populate_workflow_file(repository: Repository): + content = f""" +name: Label Pull Request + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + check: + name: "Label Pull Request" + permissions: + contents: read + issues: write + pull-requests: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-label-by-branch.yml@{LAUNCH_WORKFLOWS_REF_TO_TEST} + secrets: inherit # pragma: allowlist secret +""" + + repository.create_file( + ".github/workflows/pull-request-label.yml", + content=content, + branch="main", + message="Add workflow file for reusable PR label by branch", + ) + + +def test_reusable_pr_label_by_branch(temporary_repository): + github_repo, _ = temporary_repository + + # Set up initial state on main branch + populate_workflow_file(repository=github_repo) + populate_release_drafter_config(repository=github_repo) + + branch_name_label_map = { + "first_pr_always_major_regardless_of_name": ["major"], + "bug/something": ["patch"], + "fix/something": ["patch"], + "patch/something": ["patch"], + "feature/something": ["minor"], + "bug!/breaking": ["major"], + "fix!/breaking": ["major"], + "patch!/breaking": ["major"], + "feature!/breaking": ["major"], + "unexpected/prefix": [], + } + + for branch_name, expected_labels in branch_name_label_map.items(): + github_repo.create_git_ref( + f"refs/heads/{branch_name}", github_repo.get_branch("main").commit.sha + ) + sleep(1) + populate_readme_file(github_repo, branch=branch_name) + pull_request = github_repo.create_pull( + base="main", + head=branch_name, + title=f"Test PR Label for {branch_name} - Happy Path", + body=f"This is a test pull request to validate the autolabeler applies the {expected_labels} label to the branch name {branch_name}.", + ) + + workflow = github_repo.get_workflow(id_or_file_name="pull-request-label.yml") + workflow_run = wait_for_workflow_run_create( + workflow=workflow, branch=branch_name + ) + status = wait_for_workflow_run_completion(workflow_run=workflow_run) + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for {branch_name} did not complete successfully: {status}" + ) + if workflow_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs(workflow_run, drop_log_timestamps=True) + raise AssertionError( + f"Workflow run for {branch_name} did not succeed as expected: {workflow_run.conclusion}\nLogs:\n{logs}" + ) + pr_labels = [label.name for label in pull_request.get_labels()] + if expected_labels != pr_labels: + raise AssertionError( + f"Expected labels '{expected_labels}' didn't match pull request: {pr_labels} for branch {branch_name}" + ) From 6b429c91f1a4bf95caa94e2671eaa9dffc794419 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:07:54 -0500 Subject: [PATCH 08/20] Fix escape characters in release-drafter configuration --- test/integration/test_reusable_pr_label_by_branch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/test_reusable_pr_label_by_branch.py index ee992bf..d7b75c0 100644 --- a/test/integration/test_reusable_pr_label_by_branch.py +++ b/test/integration/test_reusable_pr_label_by_branch.py @@ -44,13 +44,13 @@ def populate_release_drafter_config(repository: Repository): autolabeler: - label: "major" branch: - - '/(patch|bug|fix|feature)!\/.+/' + - '/(patch|bug|fix|feature)!\\/.+/' - label: "minor" branch: - - '/feature\/.+/' + - '/feature\\/.+/' - label: "patch" branch: - - '/(patch|bug|fix)\/.+/' + - '/(patch|bug|fix)\\/.+/' change-template: "- $TITLE @$AUTHOR (#$NUMBER)" From 19cf8d9e4205bd6825ef93457b7d3f79bf5842d2 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:27:38 -0500 Subject: [PATCH 09/20] Sleep to give the main branch time to be created --- test/integration/test_reusable_pr_label_by_branch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/test_reusable_pr_label_by_branch.py index d7b75c0..62cb1d5 100644 --- a/test/integration/test_reusable_pr_label_by_branch.py +++ b/test/integration/test_reusable_pr_label_by_branch.py @@ -123,6 +123,7 @@ def test_reusable_pr_label_by_branch(temporary_repository): "feature!/breaking": ["major"], "unexpected/prefix": [], } + sleep(1) for branch_name, expected_labels in branch_name_label_map.items(): github_repo.create_git_ref( From 099838814a343de49d43bb575ea9df541e0eec67 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:29:54 -0500 Subject: [PATCH 10/20] Debugging race? --- test/integration/test_reusable_pr_label_by_branch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/test_reusable_pr_label_by_branch.py index 62cb1d5..375f087 100644 --- a/test/integration/test_reusable_pr_label_by_branch.py +++ b/test/integration/test_reusable_pr_label_by_branch.py @@ -154,6 +154,7 @@ def test_reusable_pr_label_by_branch(temporary_repository): ) pr_labels = [label.name for label in pull_request.get_labels()] if expected_labels != pr_labels: + sleep(90) raise AssertionError( f"Expected labels '{expected_labels}' didn't match pull request: {pr_labels} for branch {branch_name}" ) From fd7e1deffa8f5eb7efff41a336ef291e2e3b5f9c Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:37:03 -0500 Subject: [PATCH 11/20] Fixture should provide better repo description --- test/integration/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index ac032c3..fccb884 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -37,10 +37,12 @@ def repo_name() -> Generator[str, None, None]: @pytest.fixture(scope="function") -def organization_repo(organization, repo_name) -> Generator[Repository, None, None]: +def organization_repo( + request, organization, repo_name +) -> Generator[Repository, None, None]: repo = organization.create_repo( name=repo_name, - description="Test Repository for Integration Tests", + description=f"Test Repository for Integration Test: {request.node.name}", private=False, visibility="public", auto_init=False, From e573311e570bf86fe3f3dd343effe84fed4539c7 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:37:17 -0500 Subject: [PATCH 12/20] Get main branch sha once --- test/integration/test_reusable_pr_label_by_branch.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/test_reusable_pr_label_by_branch.py index 375f087..58082fc 100644 --- a/test/integration/test_reusable_pr_label_by_branch.py +++ b/test/integration/test_reusable_pr_label_by_branch.py @@ -125,10 +125,10 @@ def test_reusable_pr_label_by_branch(temporary_repository): } sleep(1) + main_commit_sha = github_repo.get_branch("main").commit.sha + for branch_name, expected_labels in branch_name_label_map.items(): - github_repo.create_git_ref( - f"refs/heads/{branch_name}", github_repo.get_branch("main").commit.sha - ) + github_repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=main_commit_sha) sleep(1) populate_readme_file(github_repo, branch=branch_name) pull_request = github_repo.create_pull( From f044c710f730f966dc2360872a6f16f59e903eeb Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 6 Aug 2025 14:38:34 -0500 Subject: [PATCH 13/20] Fixture consolidation --- test/integration/conftest.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index fccb884..e560441 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -36,23 +36,6 @@ def repo_name() -> Generator[str, None, None]: return f"test-repo-{suffix}" -@pytest.fixture(scope="function") -def organization_repo( - request, organization, repo_name -) -> Generator[Repository, None, None]: - repo = organization.create_repo( - name=repo_name, - description=f"Test Repository for Integration Test: {request.node.name}", - private=False, - visibility="public", - auto_init=False, - ) - - yield repo - - repo.delete() - - @pytest.fixture(scope="function") def local_repo(tmp_path) -> Generator[pathlib.Path, None, None]: """ @@ -74,7 +57,7 @@ def local_repo(tmp_path) -> Generator[pathlib.Path, None, None]: @pytest.fixture(scope="function") def temporary_repository( - test_organization_name, organization, repo_name, tmp_path + request, test_organization_name, organization, repo_name, tmp_path ) -> Generator[tuple[Repository, Repo], None, None]: """ Fixture to create a temporary GitHub repository for testing. @@ -82,7 +65,7 @@ def temporary_repository( """ github_repo = organization.create_repo( name=repo_name, - description="Temporary Repository for Testing", + description=f"Test Repository for Integration Test: {request.node.name}", private=False, visibility="public", auto_init=False, From 3b5e7265ea93fc8e16f12e6b5eed5ddc104388a7 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Thu, 7 Aug 2025 14:33:27 -0500 Subject: [PATCH 14/20] Cleanup PR naming --- test/integration/test_reusable_pr_label_by_branch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/test_reusable_pr_label_by_branch.py index 58082fc..6ec51ec 100644 --- a/test/integration/test_reusable_pr_label_by_branch.py +++ b/test/integration/test_reusable_pr_label_by_branch.py @@ -134,7 +134,7 @@ def test_reusable_pr_label_by_branch(temporary_repository): pull_request = github_repo.create_pull( base="main", head=branch_name, - title=f"Test PR Label for {branch_name} - Happy Path", + title=f"Test PR Label for {branch_name}", body=f"This is a test pull request to validate the autolabeler applies the {expected_labels} label to the branch name {branch_name}.", ) From 6fbd8a9e9e3b22caea6a95f9dc358c4ece6ad0b6 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 9 Feb 2026 14:30:47 -0600 Subject: [PATCH 15/20] wip: nttdtest --- src/__pycache__/launch_github.cpython-313.pyc | Bin 5714 -> 5714 bytes test/__pycache__/__init__.cpython-313.pyc | Bin 171 -> 171 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 183 -> 183 bytes .../conftest.cpython-313-pytest-8.4.1.pyc | Bin 4151 -> 4263 bytes ...raft_on_merge.cpython-313-pytest-8.4.1.pyc | Bin 28049 -> 28049 bytes test/integration/conftest.py | 2 +- test/integration/github-matrix-tg/__init__.py | 0 .../test_reusable_github_matrix_tg.py | 0 .../pr-label-by-branch/__init__.py | 0 .../test_reusable_pr_label_by_branch.py | 0 .../release-draft-on-merge/__init__.py | 0 .../test_reusable_release_draft_on_merge.py | 581 ++++++++++++++++++ 12 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 test/integration/github-matrix-tg/__init__.py rename test/integration/{ => github-matrix-tg}/test_reusable_github_matrix_tg.py (100%) create mode 100644 test/integration/pr-label-by-branch/__init__.py rename test/integration/{ => pr-label-by-branch}/test_reusable_pr_label_by_branch.py (100%) create mode 100644 test/integration/release-draft-on-merge/__init__.py create mode 100644 test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py diff --git a/src/__pycache__/launch_github.cpython-313.pyc b/src/__pycache__/launch_github.cpython-313.pyc index 68145741ad49edde029d4788718d5a4e218eaa99..10982c38ad773f5d9f63568354e7bec49242a9a0 100644 GIT binary patch delta 21 bcmcblb4iEiGcPX}0}#w}=*ry4<0b|GNaqF$ delta 21 bcmcblb4iEiGcPX}0}z}r*`BeH$4v|XO6&$y diff --git a/test/__pycache__/__init__.cpython-313.pyc b/test/__pycache__/__init__.cpython-313.pyc index b8baf2d42ddf33f479dd48a2556c8b1b39e2be22..1188860ccec91aebed9f8450ba24f76896f48546 100644 GIT binary patch delta 20 acmZ3@xSEmYGcPX}0}#w}=*pbPGampq00oHv delta 20 acmZ3@xSEmYGcPX}0}z}r*`6_xXFdQpy9Lt# diff --git a/test/integration/__pycache__/__init__.cpython-313.pyc b/test/integration/__pycache__/__init__.cpython-313.pyc index b5c53256942c780fd9dd1665b5efe210119aa9f2..118bcb96e118d686dcf9c1c3d5d79c157c28a3af 100644 GIT binary patch delta 20 acmdnaxSf&bGcPX}0}#w}=*pbPvl;+6)CH{o delta 20 acmdnaxSf&bGcPX}0}z}r*`6_xXEgvij|KMt diff --git a/test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc b/test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc index f95c14413512b677df00a4ba494002e7c408ab0e..fb69b01271c29d27bf593a5f7d2e2396d1714a71 100644 GIT binary patch delta 618 zcmdn4uw0S%GcPX}0}zNfc4c;M=!ctS&%FVYWGptVR4l z-IF)7>kEOEapmMEC+5V16c-t8{=$BlnbBnO3GM&^kcCCEAVL#F=ug(*F%mNbGQl2D zKmefs3V5U#JvVprG%|{SOuxlmkXVwDlbKWmiq#_3$qIaJ!C>>iUI7_;i@CU@2yF5# zmH?pQB9O_qSW8L^a#D*tLAsE_4xge^^Qi{{_o~bX$nUHR*Dsvh&Scafc7y$9wa>_q{hWKdX<-8d+AJpgk=;`tE~u ztF3F8&7zN{RKUnJ*{46qb%qg-tmwzIrtO}Mw|oeaI(HKuL|#1x@1B4~h}hOoSqU4Z zCk2E7>5`ZHOy8J!O$^L0vN|16jme~ejL?3FWR zI<6KN1GgPFBIm?~IkA@ghQ)67jvA>JC!(7BJfT?mFm5?vLvo?sYv%eo{UVmly^@>< z(-P(+EQlL(@naoBwLkl3V2fw-6W!^(n&)(C9vSI}w!{4}XhKD{GI3SB+*f+?4U}lA zCmfV=YV08yq;i_!op$KLj%2-i&fM3N_^1=Q8*qSCik>VB&)zIlMmo`|Lygjk=-ER< G{=gd)#(a_h diff --git a/test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-313-pytest-8.4.1.pyc b/test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-313-pytest-8.4.1.pyc index e4efb6e41649c03ce96093ceb723c71811a775f3..7aa8de5aa4a28155d24e33ef0635e22c3b767d66 100644 GIT binary patch delta 23 dcmbPun{nc8MxM{Syj%=GaKxc2b0be{E&yLE2c!T1 delta 23 dcmbPun{nc8MxM{Syj%=Gur*|H#zvmjTmWCt2f+XU diff --git a/test/integration/conftest.py b/test/integration/conftest.py index e560441..37a7d56 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -15,7 +15,7 @@ @pytest.fixture(scope="session") def test_organization_name(): - yield "chris-testing-org" + yield os.environ.get("TESTING_ORGANIZATION_NAME", "nttdtest") @pytest.fixture(scope="session") diff --git a/test/integration/github-matrix-tg/__init__.py b/test/integration/github-matrix-tg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/test_reusable_github_matrix_tg.py b/test/integration/github-matrix-tg/test_reusable_github_matrix_tg.py similarity index 100% rename from test/integration/test_reusable_github_matrix_tg.py rename to test/integration/github-matrix-tg/test_reusable_github_matrix_tg.py diff --git a/test/integration/pr-label-by-branch/__init__.py b/test/integration/pr-label-by-branch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/test_reusable_pr_label_by_branch.py b/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py similarity index 100% rename from test/integration/test_reusable_pr_label_by_branch.py rename to test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py diff --git a/test/integration/release-draft-on-merge/__init__.py b/test/integration/release-draft-on-merge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py b/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py new file mode 100644 index 0000000..b5beaa1 --- /dev/null +++ b/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py @@ -0,0 +1,581 @@ +import os + +import pytest + +from src.launch_github import ( + WorkflowRunConclusion, + WorkflowRunStatus, + branch_created, + get_workflow_run_logs, + populate_file, + workflow_run_completed, + workflow_run_created, +) + +LAUNCH_WORKFLOWS_REF_TO_TEST = os.environ.get("LAUNCH_WORKFLOWS_REF_TO_TEST", "main") +RELEASE_DRAFTER_CONFIG_CONTENTS = """ +--- +name-template: "$RESOLVED_VERSION" +tag-template: "$RESOLVED_VERSION" +template: | + # Changelog + + $CHANGES + + --- + + See details of [all code changes](https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$RESOLVED_VERSION) since previous release. + +categories: + - title: ":warning: Breaking Changes" + labels: + - "major" + - title: "🚀 Features" + labels: + - "minor" + - title: "🔧 Fixes" + collapse-after: 3 + labels: + - "patch" + +autolabeler: + - label: "major" + branch: + - '/(patch|bug|fix|feature)!\\/.+/' + - label: "minor" + branch: + - '/feature\\/.+/' + - label: "patch" + branch: + - '/(patch|bug|fix)\\/.+/' + +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" + +version-resolver: + major: + labels: + - "major" + minor: + labels: + - "minor" + patch: + labels: + - "patch" + - "dependencies" + default: patch +""" +PR_WORKFLOW_CONTENTS = f""" +name: Label Pull Request + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + check: + name: "Label Pull Request" + permissions: + contents: read + issues: write + pull-requests: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-label-by-branch.yml@{LAUNCH_WORKFLOWS_REF_TO_TEST} + secrets: inherit # pragma: allowlist secret +""" +RELEASE_WORKFLOW_CONTENTS = f""" +name: Draft Release + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + draft-release: + name: "Draft Release on Merge" + permissions: + contents: write + pull-requests: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-release-draft-on-merge.yml@{LAUNCH_WORKFLOWS_REF_TO_TEST} + secrets: inherit # pragma: allowlist secret +""" + + +@pytest.mark.parametrize( + "branch_name, expected_tag", + [ + ("fix/patch", "0.1.0"), + ("feature/minor", "0.1.0"), + ("patch!/major", "0.1.0"), + ], +) +def test_reusable_release_draft_empty_repository( + temporary_repository, branch_name, expected_tag +): + """In a brand new repository without any releases, the first release + _always_ has a zero major version per the SemVer spec. + + For more info: + - https://semver.org/spec/v2.0.0.html + - https://github.com/release-drafter/release-drafter/issues/1391 + """ + + with branch_created(temporary_repository, "main") as main: + populate_file( + repository=temporary_repository, + path=".github/release-drafter.yml", + content=RELEASE_DRAFTER_CONFIG_CONTENTS, + branch=main.name, + commit_message="Add release drafter configuration file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/pull-request-label.yml", + content=PR_WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable PR label workflow file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/release-draft.yml", + content=RELEASE_WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable release draft workflow file", + ) + + with branch_created( + temporary_repository, branch_name, origin_branch=main.name + ) as branch: + populate_file( + repository=temporary_repository, + path="test.txt", + content="Test file", + branch=branch.name, + ) + + pull_request = temporary_repository.create_pull( + base="main", + head=branch_name, + title=f"Test PR Label for {branch_name}", + body=f"This is a test pull request to validate the workflow drafts a release for {expected_tag}.", + ) + label_workflow = temporary_repository.get_workflow( + id_or_file_name="pull-request-label.yml" + ) + + with workflow_run_created(label_workflow, branch=branch.name) as label_run: + with workflow_run_completed(label_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for {branch_name} did not complete successfully: {status}" + ) + if label_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs( + label_run, drop_log_timestamps=True + ) + raise AssertionError( + f"Workflow run for {branch_name} did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" + ) + pr_labels = [label.name for label in pull_request.get_labels()] + assert ( + pr_labels + ), "Expected at least one label to be applied to the pull request!" + + pull_request.merge() + + release_workflow = temporary_repository.get_workflow( + id_or_file_name="release-draft.yml" + ) + + with workflow_run_created( + release_workflow, branch=main.name + ) as drafter_run: + with workflow_run_completed(drafter_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Release drafter workflow run did not complete successfully: {status}" + ) + if drafter_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs( + drafter_run, drop_log_timestamps=True + ) + raise AssertionError( + f"Release drafter workflow run did not succeed as expected: {drafter_run.conclusion}\nLogs:\n{logs}" + ) + release = [ + release for release in temporary_repository.get_releases() + ][0] + assert release.title == expected_tag + # Perform the release + release.update_release( + name=release.title, message=release.body, draft=False + ) + tags = [tag for tag in temporary_repository.get_tags()] + assert tags[0].name == expected_tag + + +@pytest.mark.parametrize( + "branch_name, expected_tag", + [ + ("fix/patch", "0.1.0"), + ("feature/minor", "0.1.0"), + ("patch!/major", "0.1.0"), + ], +) +@pytest.mark.parametrize("tag_already_exists", [True, False]) +def test_reusable_release_draft_repo_without_exising_release( + temporary_repository, branch_name, expected_tag, tag_already_exists +): + """ + When a release is drafted into a repository without an existing release, + the first release drafted will always reflect 0.1.0. If a tag 0.1.0 exists, + the release will attempt to point at that existing tag and will not create + a new tag. This behavior is slightly counterintuitive, but is documented + here and in the markdown files for our release workflows, and users are + encouraged to edit the drafted release in the case of the draft workflow, + or encouraged to create a release before running the release-on-merge + workflow. + """ + + with branch_created(temporary_repository, "main") as main: + populate_file( + repository=temporary_repository, + path=".github/release-drafter.yml", + content=RELEASE_DRAFTER_CONFIG_CONTENTS, + branch=main.name, + commit_message="Add release drafter configuration file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/pull-request-label.yml", + content=PR_WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable PR label workflow file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/release-draft.yml", + content=RELEASE_WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable release draft workflow file", + skip_ci=True, + ) + + if tag_already_exists: + temporary_repository.create_git_ref( + ref="refs/tags/0.1.0", sha=main.commit.sha + ) + + with branch_created( + temporary_repository, branch_name, origin_branch=main.name + ) as branch: + populate_file( + repository=temporary_repository, + path="test.txt", + content="Test file", + branch=branch.name, + ) + + pull_request = temporary_repository.create_pull( + base="main", + head=branch_name, + title=f"Test PR Label for {branch_name}", + body=f"This is a test pull request to validate the workflow drafts a release for {expected_tag}.", + ) + label_workflow = temporary_repository.get_workflow( + id_or_file_name="pull-request-label.yml" + ) + + with workflow_run_created(label_workflow, branch=branch.name) as label_run: + with workflow_run_completed(label_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for {branch_name} did not complete successfully: {status}" + ) + if label_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs( + label_run, drop_log_timestamps=True + ) + raise AssertionError( + f"Workflow run for {branch_name} did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" + ) + pr_labels = [label.name for label in pull_request.get_labels()] + assert ( + pr_labels + ), "Expected at least one label to be applied to the pull request!" + + pull_request.merge() + + release_workflow = temporary_repository.get_workflow( + id_or_file_name="release-draft.yml" + ) + + with workflow_run_created( + release_workflow, branch=main.name + ) as drafter_run: + with workflow_run_completed(drafter_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Release drafter workflow run did not complete successfully: {status}" + ) + if drafter_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs( + drafter_run, drop_log_timestamps=True + ) + raise AssertionError( + f"Release drafter workflow run did not succeed as expected: {drafter_run.conclusion}\nLogs:\n{logs}" + ) + release = [ + release for release in temporary_repository.get_releases() + ][0] + assert release.title == expected_tag + # Perform the release + release.update_release( + name=release.title, message=release.body, draft=False + ) + tags = [tag for tag in temporary_repository.get_tags()] + assert tags[0].name == expected_tag + + +@pytest.mark.parametrize( + "branch_name, expected_tag", + [ + ("fix/patch", "0.1.1"), + ("feature/minor", "0.2.0"), + ("patch!/major", "1.0.0"), + ], +) +def test_reusable_release_draft_repo_with_exising_release( + temporary_repository, branch_name, expected_tag +): + """ + First PR tag defaults to major, so this isn't working the way I'd originally hoped. + + Maybe we change up the testing strategy for the release workflows entirely. Set + up repositories in states where they're likely to need release-drafter workflows installed: + + - Brand new repo, no existing pull requests, tags, or releases + - Existing repo with pull requests and tags -> go through draft/update/publish and then merge another PR + - Existing repo with pull requests, tags, and releases + """ + + with branch_created(temporary_repository, "main") as main: + populate_file( + repository=temporary_repository, + path=".github/release-drafter.yml", + content=RELEASE_DRAFTER_CONFIG_CONTENTS, + branch=main.name, + commit_message="Add release drafter configuration file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/pull-request-label.yml", + content=PR_WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable PR label workflow file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/release-draft.yml", + content=RELEASE_WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable release draft workflow file", + skip_ci=True, + ) + + # Create the 0.1.0 release ahead of time + temporary_repository.create_git_release( + tag="0.1.0", + name="Release 0.1.0", + message="This is the first release.", + draft=False, + prerelease=False, + generate_release_notes=True, + target_commitish=main, + ) + + with branch_created( + temporary_repository, branch_name, origin_branch=main.name + ) as branch: + populate_file( + repository=temporary_repository, + path="test.txt", + content="Test file", + branch=branch.name, + ) + + pull_request = temporary_repository.create_pull( + base="main", + head=branch_name, + title=f"Test PR Label for {branch_name}", + body=f"This is a test pull request to validate the workflow drafts a release for {expected_tag}.", + ) + label_workflow = temporary_repository.get_workflow( + id_or_file_name="pull-request-label.yml" + ) + + with workflow_run_created(label_workflow, branch=branch.name) as label_run: + with workflow_run_completed(label_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for {branch_name} did not complete successfully: {status}" + ) + if label_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs( + label_run, drop_log_timestamps=True + ) + raise AssertionError( + f"Workflow run for {branch_name} did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" + ) + pr_labels = [label.name for label in pull_request.get_labels()] + assert ( + pr_labels + ), "Expected at least one label to be applied to the pull request!" + + pull_request.merge() + + release_workflow = temporary_repository.get_workflow( + id_or_file_name="release-draft.yml" + ) + + with workflow_run_created( + release_workflow, branch=main.name + ) as drafter_run: + with workflow_run_completed(drafter_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Release drafter workflow run did not complete successfully: {status}" + ) + if drafter_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs( + drafter_run, drop_log_timestamps=True + ) + raise AssertionError( + f"Release drafter workflow run did not succeed as expected: {drafter_run.conclusion}\nLogs:\n{logs}" + ) + release = [ + release for release in temporary_repository.get_releases() + ][0] + assert release.title == expected_tag or breakpoint() + # Perform the release + release.update_release( + name=release.title, message=release.body, draft=False + ) + tags = [tag for tag in temporary_repository.get_tags()] + assert tags[0].name == expected_tag + + +@pytest.mark.parametrize( + "branch_name, initial_release_target_tag, update_to_tag", + [ + ("fix/patch", "0.1.0", "1.0.2"), + ("feature/minor", "0.1.0", "1.1.0"), + ("patch!/major", "0.1.0", "2.0.0"), + ], +) +def test_reusable_release_draft_existing_repo_with_prs_and_tags( + temporary_repository, branch_name, initial_release_target_tag, update_to_tag +): + """ + Test the reusable release drafter workflow on an existing repository with pull requests and tags. + + There have been no releases made to this repository, so the drafted release will be created with the "wrong" + tag associated to it. The release will need to be updated by someone (in this case, the test code but typically a human) + to reflect the next desired version before the release is published. + """ + + with branch_created(temporary_repository, "main") as main: + temporary_repository.create_git_ref(ref="refs/tags/1.0.0", sha=main.commit.sha) + + with branch_created(temporary_repository, "branch-one", "main") as branch_one: + populate_file( + repository=temporary_repository, + path="test_one.txt", + content="Test file one", + branch=branch_one.name, + ) + pull_request_one = temporary_repository.create_pull( + base="main", + head=branch_one.name, + title="Test PR One", + body="This is a test pull request for branch one.", + ) + pull_request_one.merge() + temporary_repository.create_git_ref( + ref="refs/tags/1.0.1", + sha=temporary_repository.get_branch( # get the latest sha off main + "main" + ).commit.sha, + ) + + temporary_repository.get_git_ref("heads/branch-one").delete() + + # Install the workflows + + with branch_created(temporary_repository, branch_name, "main") as branch_two: + populate_file( + repository=temporary_repository, + path=".github/release-drafter.yml", + content=RELEASE_DRAFTER_CONFIG_CONTENTS, + branch=branch_two.name, + commit_message="Add release drafter configuration file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/pull-request-label.yml", + content=PR_WORKFLOW_CONTENTS, + branch=branch_two.name, + commit_message="Add reusable PR label workflow file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/release-draft.yml", + content=RELEASE_WORKFLOW_CONTENTS, + branch=branch_two.name, + commit_message="Add reusable release draft workflow file", + ) + pull_request_two = temporary_repository.create_pull( + base="main", + head=branch_two.name, + title="Install workflows", + body="Install release workflows", + ) + pull_request_two.merge() + + temporary_repository.get_git_ref(f"heads/{branch_name}").delete() + + # Confirm the release target + release_workflow = temporary_repository.get_workflow( + id_or_file_name="release-draft.yml" + ) + + with workflow_run_created(release_workflow, branch=main.name) as release_run: + with workflow_run_completed(release_run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Release workflow run did not complete successfully: {status}" + ) + if release_run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs(release_run, drop_log_timestamps=True) + raise AssertionError( + f"Release workflow run did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" + ) + + release = [release for release in temporary_repository.get_releases()][0] + assert release.tag_name == initial_release_target_tag + + # Human (or computer) updates the release to the next expected tag + release.update_release( + name=update_to_tag, + tag_name=update_to_tag, + message=release.body, + draft=False, + ) + + # Tag should have been created + expected_tag_names = ["1.0.0", "1.0.1", update_to_tag] + found_tag_names = [tag.name for tag in temporary_repository.get_tags()] + + assert all(tag in found_tag_names for tag in expected_tag_names) or breakpoint() From 639bcee1781011d4ca19dd3221e75e28a47cddc8 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 9 Feb 2026 15:39:52 -0600 Subject: [PATCH 16/20] wip: testing fix for pr label --- .../workflows/reusable-pr-label-by-branch.yml | 6 +- .../workflows/reusable-release-on-merge.yml | 5 - .gitignore | 2 +- docs/reusable-release-draft-on-merge.md | 13 + docs/reusable-release-on-merge.md | 13 + pyproject.toml | 3 +- src/__pycache__/launch_github.cpython-313.pyc | Bin 5714 -> 10486 bytes src/launch_github.py | 140 ++++- .../conftest.cpython-313-pytest-8.4.1.pyc | Bin 4263 -> 4077 bytes test/integration/conftest.py | 10 +- .../test_reusable_github_matrix_tg.py | 204 +++--- .../test_reusable_pr_label_by_branch.py | 127 ++-- .../test_reusable_release_draft_on_merge.py | 6 +- .../test_reusable_release_draft_on_merge.py | 581 ------------------ uv.lock | 327 +++++----- 15 files changed, 515 insertions(+), 922 deletions(-) delete mode 100644 test/integration/test_reusable_release_draft_on_merge.py diff --git a/.github/workflows/reusable-pr-label-by-branch.yml b/.github/workflows/reusable-pr-label-by-branch.yml index 8fe3f41..fbe1f86 100644 --- a/.github/workflows/reusable-pr-label-by-branch.yml +++ b/.github/workflows/reusable-pr-label-by-branch.yml @@ -22,7 +22,7 @@ jobs: jq -e 'if map(select(.name | contains ("major"))) == [] then null else "Label major exists" end' labels.json || gh label create major --color "b60205" label-pr: name: Label Pull Request - needs: [ configure-labels ] + needs: [configure-labels] permissions: contents: read pull-requests: write @@ -50,7 +50,7 @@ jobs: echo "updates_configuration=false" >> $GITHUB_OUTPUT fi if [[ $PR_NUMBER -eq "1" ]]; then - if git cat-file -e ${{ github.event.repository.default_branch }}:.github/release-drafter.yml 2>/dev/null; then + if git cat-file -e origin/${{ github.event.repository.default_branch }}:.github/release-drafter.yml 2>/dev/null; then # First PR, but we have a release-drafter.yml file on the main branch so we can let it proceed as normal. echo "auto_label=true" >> $GITHUB_OUTPUT else @@ -63,7 +63,7 @@ jobs: # Not the first PR, so we can attempt to auto-label it. echo "auto_label=true" >> $GITHUB_OUTPUT fi - + rm first-time-comment.md - uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 if: steps.prerequisite-check.outputs.auto_label == 'true' diff --git a/.github/workflows/reusable-release-on-merge.yml b/.github/workflows/reusable-release-on-merge.yml index d4c2ec9..766a9d8 100644 --- a/.github/workflows/reusable-release-on-merge.yml +++ b/.github/workflows/reusable-release-on-merge.yml @@ -23,11 +23,6 @@ on: description: 'Whether to disable the autolabeler. Autolabeler should remain disabled if you label the pull requests with another workflow. Default: true' required: false default: true - run_post_release_workflow: - type: boolean - description: 'Trigger a post-release workflow at .github/workflows/release-post-actions.yml within your repository that receives a "version" input with the version that was just released. This workflow can do whatever you need, but generally is used to e.g. release a package to a package regsitry. Default: false' - required: false - default: false outputs: release_version: description: 'The version of the release that was created by this workflow. This is the tag name of the release.' diff --git a/.gitignore b/.gitignore index e74b64f..0a67612 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .pytest_cache __pycache__ *.py[cod] -.venv \ No newline at end of file +.venv diff --git a/docs/reusable-release-draft-on-merge.md b/docs/reusable-release-draft-on-merge.md index a4f9706..9a7031d 100644 --- a/docs/reusable-release-draft-on-merge.md +++ b/docs/reusable-release-draft-on-merge.md @@ -54,3 +54,16 @@ jobs: ``` Be sure you replace `ref` with an appropriate ref to this repository. + +If this workflow runs in a repository without a prior Release being published, the first release will always be drafted with version 0.1.0. This is expected behavior and conforms to the SemVer spec, which permits a major version zero. From the SemVer documentation: + +> How should I deal with revisions in the 0.y.z initial development phase? +> The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release. +> +> How do I know when to release 1.0.0? +> If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backward compatibility, you should probably already be 1.0.0. +> +> Doesn’t this discourage rapid development and fast iteration? +> Major version zero is all about rapid development. If you’re changing the API every day you should either still be in version 0.y.z or on a separate development branch working on the next major version. + +Given that this workflow only drafts the release, you may manually edit the drafted release to rename and re-tag it with the version of your choosing if you wanted your first release to reflect 1.0.0 prior to publishing. If you are integrating this workflow into a repository that has tags, but no releases, you will need to perform this manual step to make sure your first true release follows your existing tags. If you integrate this workflow into a repository that has prior releases, the next drafted release will take the prior releases into account and will increment its version according to the tags found on the pull request that initiates the workflow run. diff --git a/docs/reusable-release-on-merge.md b/docs/reusable-release-on-merge.md index 6991b6f..a5864d3 100644 --- a/docs/reusable-release-on-merge.md +++ b/docs/reusable-release-on-merge.md @@ -51,3 +51,16 @@ jobs: ``` Be sure you replace `ref` with an appropriate ref to this repository. + +If this workflow runs in a repository without a prior Release being published, the first release will always be drafted with version 0.1.0. This is expected behavior and conforms to the SemVer spec, which permits a major version zero. From the SemVer documentation: + +> How should I deal with revisions in the 0.y.z initial development phase? +> The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release. +> +> How do I know when to release 1.0.0? +> If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backward compatibility, you should probably already be 1.0.0. +> +> Doesn’t this discourage rapid development and fast iteration? +> Major version zero is all about rapid development. If you’re changing the API every day you should either still be in version 0.y.z or on a separate development branch working on the next major version. + +Since this workflow publishes the release when a PR is merged (rather than leaving it in a drafted state), the first PR merge will generally result in version 0.1.0 being published. Do not integrate this workflow into a repository that contains tags but no releases, you must first create a Release with the last tagged version in order for the next PR merge to provide the correct version bump. If you integrate this workflow into a repository that has prior releases, the next release will take the prior releases into account and will increment its version according to the tags found on the pull request that initiates the workflow run. diff --git a/pyproject.toml b/pyproject.toml index 2452e55..ff67e0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,12 +8,13 @@ dependencies = [ "gitpython>=3.1.45", "pygithub>=2.7.0", "pytest>=8.4.1", + "pytest-xdist>=3.8.0", ] [tool.pytest.ini_options] pythonpath = "src" minversion = "8.0" -addopts = "-ra" +addopts = "-ra -n auto" testpaths = [ "test" ] diff --git a/src/__pycache__/launch_github.cpython-313.pyc b/src/__pycache__/launch_github.cpython-313.pyc index 10982c38ad773f5d9f63568354e7bec49242a9a0..6822733449a7c94fe5043f0ad591bd9d4f48a054 100644 GIT binary patch literal 10486 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 DV$S@7 delta 1489 zcmZ8h&u<$=6yB+K*FV<3vTkcc>N3XH_DQvU-)4xmb$TW&2}q78_mo{&&U6wy;F@n-Bm#mavB=KJ2veDmI$ zeRAce)00P`P(Z-v*4tmyp9Tft5gQyo-kx&=GDykr_Rs#|@QHQ=*>ep(8jYqbH3RZkI%Su!m<6AK`e)80AuAeW=Hc)rgk; zgQ;KEi0joI$hSI8bj8aR=O(@-yJ4Db&Etx>4m~=d(yXULRQk2&6VgXj@q_UY&IM*v z{M%Rt;d5axI`rwnUTEO6c^Kq0-4V+qK@Y^~WFG>{S^!u$^fMDF;tvI*XwryU%1FSwDxaCIf2&H*zsQpS!wPDwh_P z@|UibfW?8#D5+$vn=SCMXb-%?a5fWOMPsw7X0usfGTi=}>6B}A=VoWMTyNP4-kg7g0_#SZ0?vMWNTW!o$m55|s0i^`bvE{5@7%%$9O}Yq%rNwP>}?mMd*Q-NAg*TdGDb(0i(;a2b~{7(S#=)!Ha8?*J?B zh6U?48rDV`vs7+aH5(iZySBUC{~e*)_~?5lT`$75daDL6u?0S2ri#WDO}sb2I>z;~ z?Hlz~;DSU4Yr<0k*BC2zdx*CmZ%^>i$K#2FpOKL+dFK6|5D3upspOWzI6o>}#LZ3M zcgK~P5*+Xg$S#TNu^r&O?Rr?&S#ttw7+@*nxpc)<%WCjb{qLH&zS*W#?KI3e*v4q~ znX{jvIq(oNLiUC5zTn>%BIx`oB>6A?NQnF Generator[WorkflowRun, None, None]: + """ + Context manager to wait for a workflow run to be created. + """ + run = None + found = False + start_time = time() + while time() - start_time < timeout and not found: + try: + workflow_runs = list(workflow.get_runs(branch=branch)) + if workflow_runs: + logger.debug(f"Workflow run found for branch '{branch}'.") + found = True + run = workflow_runs[0] + else: + sleep(1) + except GithubException as ghe: + logger.error(f"Error while checking for workflow runs: {ghe}") + if found: + yield run + else: + raise TimeoutError( + f"Workflow run did not appear for branch '{branch}' within the timeout period." + ) + + +@contextmanager +def workflow_run_completed( + workflow_run: WorkflowRun, timeout: int = 120 +) -> Generator[WorkflowRunStatus, None, None]: + """ + Context manager to wait for a workflow run to complete. + """ + status = None + found = False + start_time = time() + while time() - start_time < timeout and not found: + workflow_run.update() + if workflow_run.status in ( + WorkflowRunStatus.COMPLETED, + WorkflowRunStatus.FAILED, + ): + found = True + status = workflow_run.status + else: + sleep(1) + if found: + yield status + else: + raise TimeoutError("Workflow run did not complete within the timeout period.") + + +@contextmanager +def branch_created( + github_repo: Repository, + branch_name: str, + origin_branch: str | None = None, + timeout: int = 60, +) -> Generator[Branch, None, None]: + """ + Context manager to create a branch and wait for it to be usable. + """ + start_time = time() + if origin_branch is None: + # We're creating main, which has to have a file in it, so create a README.md + populate_file( + repository=github_repo, + path="README.md", + content=f"# README for {github_repo.name}\n\nThis is the main branch.", + ) + else: + # Create the branch from the specified origin branch + origin_found = False + while not origin_found and time() - start_time < timeout: + try: + origin_branch = github_repo.get_branch(origin_branch) + origin_found = True + except GithubException as ghe: + if ghe.status == 404: + sleep(1) + else: + raise ghe + if not origin_found: + raise TimeoutError( + f"Origin branch '{origin_branch}' not found in repository '{github_repo.name}'." + ) + + github_repo.create_git_ref( + f"refs/heads/{branch_name}", origin_branch.commit.sha + ) + found = False + while time() - start_time < timeout and not found: + try: + branch = github_repo.get_branch(branch_name) + found = True + except GithubException as ghe: + if ghe.status == 404: + continue + else: + raise ghe + if found: + yield branch + else: + raise TimeoutError( + f"Branch '{branch_name}' did not appear within the timeout period." + ) diff --git a/test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc b/test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc index fb69b01271c29d27bf593a5f7d2e2396d1714a71..7b39faf1ac1c41830c7c841771d060b6505457ab 100644 GIT binary patch delta 304 zcmZ3k_*S0pGcPX}0}w=bc4a={+{h=*%A*YAeqI11lo=Ez8?sL1DPx$*s=$!WV9uPz zIQa-`J*yQ^^5tYVHXR8bu?b}pGA?k*UuKd2SeEd1A5#4fUkU1gE@Su6y$T+Ydi@t}$l zkUYep=VZ27nNNn1(QUE=zbs?qJ0xm$ZLO@nXfl(4H{*ejDDiQ`74ghjoONjsg delta 494 zcmaDWzg&^;GcPX}0}zNfc4c;QZRC??9O3?%uCSwmTZ1SfCc6j3l`geWs*g3x(f5m6>gWeihU6&ONUgM=q{aY}J1 zFr@RFGp8|X3T&?8a%5Dj63!?oDJZtm*H6zZ$tX?IOU}>Lzs0Cu#i9ok@YCd)ypP*V zwTKmH Generator[str, None, None]: suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8)) - return f"test-repo-{suffix}" + yield f"test-repo-{suffix}" @pytest.fixture(scope="function") @@ -87,11 +87,5 @@ def temporary_repository( f"Repository {repo_name} not found on GitHub after {max_tries} attempts!" ) - local_repo = Repo.clone_from( - f"https://github.com/{test_organization_name}/{repo_name}.git", - tmp_path.joinpath(repo_name), - ) - - yield github_repo, local_repo - + yield github_repo github_repo.delete() diff --git a/test/integration/github-matrix-tg/test_reusable_github_matrix_tg.py b/test/integration/github-matrix-tg/test_reusable_github_matrix_tg.py index 9cdf873..0205510 100644 --- a/test/integration/github-matrix-tg/test_reusable_github_matrix_tg.py +++ b/test/integration/github-matrix-tg/test_reusable_github_matrix_tg.py @@ -1,22 +1,19 @@ import os -from time import sleep from github.Repository import Repository from src.launch_github import ( WorkflowRunConclusion, WorkflowRunStatus, + branch_created, get_workflow_run_logs, - populate_readme_file, - wait_for_workflow_run_completion, - wait_for_workflow_run_create, + populate_file, + workflow_run_completed, + workflow_run_created, ) LAUNCH_WORKFLOWS_REF_TO_TEST = os.environ.get("LAUNCH_WORKFLOWS_REF_TO_TEST", "main") - - -def populate_workflow_file(repository: Repository): - content = f""" +WORKFLOW_FILE_CONTENTS = f""" name: GitHub Matrix Workflow on: @@ -30,12 +27,6 @@ def populate_workflow_file(repository: Repository): with: platform_environment: sandbox """ - repository.create_file( - ".github/workflows/github_matrix.yml", - content=content, - branch="main", - message="Add workflow file for reusable GitHub matrix", - ) def populate_platform_folder( @@ -45,11 +36,12 @@ def populate_platform_folder( instance: str, branch: str = "main", ): - repository.create_file( + populate_file( + repository=repository, path=f"platform/{environment}/{region}/{instance}/test.txt", - message=f"Add test environment file for {environment} in {region}/{instance}", content="Test Environment File", branch=branch, + commit_message=f"Add test environment file for {environment} in {region}/{instance}", ) @@ -57,92 +49,110 @@ def test_reusable_github_matrix_tg(temporary_repository): """ Test the reusable GitHub matrix template with a temporary repository. """ - github_repo, _ = temporary_repository - - assert github_repo.name.startswith("test-repo-") - assert github_repo.private is False - assert github_repo.visibility == "public" - - populate_workflow_file(repository=github_repo) - - github_repo.create_git_ref( - "refs/heads/test/matrix-happy-path", github_repo.get_branch("main").commit.sha - ) - sleep(1) - populate_readme_file(github_repo, branch="test/matrix-happy-path") - populate_platform_folder( - repository=github_repo, - environment="sandbox", - region="us-east-1", - instance="000", - branch="test/matrix-happy-path", - ) + with branch_created(temporary_repository, "main") as main: + populate_file( + repository=temporary_repository, + path=".github/workflows/github_matrix.yml", + content=WORKFLOW_FILE_CONTENTS, + branch=main.name, + commit_message="Add reusable GitHub matrix workflow file", + ) + with branch_created( + temporary_repository, "test/matrix-happy-path", origin_branch=main.name + ) as pr_branch: + populate_platform_folder( + repository=temporary_repository, + environment="sandbox", + region="us-east-1", + instance="000", + branch=pr_branch.name, + ) + + temporary_repository.create_pull( + base=main.name, + head=pr_branch.name, + title="Test Reusable GitHub Matrix Template", + body="This is a test pull request to validate the reusable GitHub matrix template with a happy path scenario.", + ) - github_repo.create_git_ref( - "refs/heads/test/matrix-missing-platform-folder", - github_repo.get_branch("main").commit.sha, - ) - sleep(1) - populate_readme_file(github_repo, branch="test/matrix-missing-platform-folder") - - github_repo.create_pull( - base="main", - head="test/matrix-happy-path", - title="Test Reusable GitHub Matrix Template - Happy Path", - body="This is a test pull request to validate the reusable GitHub matrix template with a happy path scenario.", - ) - github_repo.create_pull( - base="main", - head="test/matrix-missing-platform-folder", - title="Test Reusable GitHub Matrix Template - Missing Platform Folder", - body="This is a test pull request to validate the reusable GitHub matrix template with a missing platform folder. A failure is expected.", - ) + workflow = temporary_repository.get_workflow( + id_or_file_name="github_matrix.yml" + ) - workflow = github_repo.get_workflow(id_or_file_name="github_matrix.yml") + with workflow_run_created(workflow, branch=pr_branch.name) as run: + with workflow_run_completed(run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for happy path did not complete successfully: {status}" + ) + if run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs(run, drop_log_timestamps=True) + raise AssertionError( + f"Workflow run for happy path did not succeed as expected: {run.conclusion}\nLogs:\n{logs}" + ) + run_logs = get_workflow_run_logs(run, drop_log_timestamps=True) + expected_lines = [ + "Generated the following environment matrix:", + "{", + ' "terragrunt_environment": [', + " {", + ' "environment": "sandbox",', + ' "region": "us-east-1",', + ' "instance": "000"', + " }", + " ]", + "}", + ] + assert all(line in run_logs for line in expected_lines) + + +def test_reusable_github_matrix_tg_no_platform_folder(temporary_repository): + """ + Test the reusable GitHub matrix template with a temporary repository that has no platform folder. + """ - run_happy_path = wait_for_workflow_run_create( - workflow, branch="test/matrix-happy-path" - ) - status_happy_path = wait_for_workflow_run_completion(run_happy_path) - if status_happy_path != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Workflow run for happy path did not complete successfully: {status_happy_path}" + with branch_created(temporary_repository, "main") as main: + populate_file( + repository=temporary_repository, + path=".github/workflows/github_matrix.yml", + content=WORKFLOW_FILE_CONTENTS, + branch=main.name, + commit_message="Add reusable GitHub matrix workflow file", ) - if run_happy_path.conclusion != WorkflowRunConclusion.SUCCESS: - raise AssertionError( - f"Workflow run for happy path did not succeed as expected: {run_happy_path.conclusion}" + with branch_created( + temporary_repository, + "test/matrix-no-platform-folder", + origin_branch=main.name, + ) as pr_branch: + populate_file( + repository=temporary_repository, + path="test.txt", + content="Nothing to see here.", + branch=pr_branch.name, + ) + + temporary_repository.create_pull( + base=main.name, + head=pr_branch.name, + title="Test Reusable GitHub Matrix Template - No Platform Folder", + body="This is a test pull request to validate the reusable GitHub matrix template with no platform folder. A failure is expected.", ) - run_missing_platform_folder = wait_for_workflow_run_create( - workflow, branch="test/matrix-missing-platform-folder" - ) - status_missing_platform_folder = wait_for_workflow_run_completion( - run_missing_platform_folder - ) - if status_missing_platform_folder != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Workflow run for missing platform folder did not complete as expected: {status_missing_platform_folder}" - ) - if run_missing_platform_folder.conclusion != WorkflowRunConclusion.FAILURE: - raise AssertionError( - f"Workflow run for missing platform folder did not fail as expected: {run_missing_platform_folder.conclusion}" + workflow = temporary_repository.get_workflow( + id_or_file_name="github_matrix.yml" ) - logs_happy_path = get_workflow_run_logs(run_happy_path, drop_log_timestamps=True) - logs_missing_platform_folder = get_workflow_run_logs(run_missing_platform_folder) - - happy_path_expected_lines = [ - "Generated the following environment matrix:", - "{", - ' "terragrunt_environment": [', - " {", - ' "environment": "sandbox",', - ' "region": "us-east-1",', - ' "instance": "000"', - " }", - " ]", - "}", - ] - - assert all(line in logs_happy_path for line in happy_path_expected_lines) - assert "FileNotFoundError" in logs_missing_platform_folder + with workflow_run_created(workflow, branch=pr_branch.name, timeout=180) as run: + with workflow_run_completed(run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for no platform folder did not complete as expected: {status}" + ) + + if run.conclusion != WorkflowRunConclusion.FAILURE: + raise AssertionError( + f"Workflow run for no platform folder did not fail as expected: {run.conclusion}" + ) + + run_logs = get_workflow_run_logs(run, drop_log_timestamps=True) + assert "FileNotFoundError" in run_logs diff --git a/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py b/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py index 6ec51ec..5093922 100644 --- a/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py +++ b/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py @@ -1,22 +1,17 @@ import os -from time import sleep - -from github.Repository import Repository from src.launch_github import ( WorkflowRunConclusion, WorkflowRunStatus, + branch_created, get_workflow_run_logs, - populate_readme_file, - wait_for_workflow_run_completion, - wait_for_workflow_run_create, + populate_file, + workflow_run_completed, + workflow_run_created, ) LAUNCH_WORKFLOWS_REF_TO_TEST = os.environ.get("LAUNCH_WORKFLOWS_REF_TO_TEST", "main") - - -def populate_release_drafter_config(repository: Repository): - content = """ +RELEASE_DRAFTER_CONFIG_CONTENTS = """ --- name-template: "$RESOLVED_VERSION" tag-template: "$RESOLVED_VERSION" @@ -27,7 +22,7 @@ def populate_release_drafter_config(repository: Repository): --- - See details of [all code changes](https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION) since previous release. + See details of [all code changes](https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$RESOLVED_VERSION) since previous release. categories: - title: ":warning: Breaking Changes" @@ -66,19 +61,8 @@ def populate_release_drafter_config(repository: Repository): - "patch" - "dependencies" default: patch - """ - - repository.create_file( - ".github/release-drafter.yml", - content=content, - branch="main", - message="Add Release Drafter configuration", - ) - - -def populate_workflow_file(repository: Repository): - content = f""" +WORKFLOW_CONTENTS = f""" name: Label Pull Request on: @@ -96,21 +80,8 @@ def populate_workflow_file(repository: Repository): secrets: inherit # pragma: allowlist secret """ - repository.create_file( - ".github/workflows/pull-request-label.yml", - content=content, - branch="main", - message="Add workflow file for reusable PR label by branch", - ) - def test_reusable_pr_label_by_branch(temporary_repository): - github_repo, _ = temporary_repository - - # Set up initial state on main branch - populate_workflow_file(repository=github_repo) - populate_release_drafter_config(repository=github_repo) - branch_name_label_map = { "first_pr_always_major_regardless_of_name": ["major"], "bug/something": ["patch"], @@ -123,38 +94,56 @@ def test_reusable_pr_label_by_branch(temporary_repository): "feature!/breaking": ["major"], "unexpected/prefix": [], } - sleep(1) - - main_commit_sha = github_repo.get_branch("main").commit.sha - - for branch_name, expected_labels in branch_name_label_map.items(): - github_repo.create_git_ref(ref=f"refs/heads/{branch_name}", sha=main_commit_sha) - sleep(1) - populate_readme_file(github_repo, branch=branch_name) - pull_request = github_repo.create_pull( - base="main", - head=branch_name, - title=f"Test PR Label for {branch_name}", - body=f"This is a test pull request to validate the autolabeler applies the {expected_labels} label to the branch name {branch_name}.", - ) - workflow = github_repo.get_workflow(id_or_file_name="pull-request-label.yml") - workflow_run = wait_for_workflow_run_create( - workflow=workflow, branch=branch_name + with branch_created(temporary_repository, "main") as main: + populate_file( + repository=temporary_repository, + path=".github/release-drafter.yml", + content=RELEASE_DRAFTER_CONFIG_CONTENTS, + branch=main.name, + commit_message="Add release drafter configuration file", + ) + populate_file( + repository=temporary_repository, + path=".github/workflows/pull-request-label.yml", + content=WORKFLOW_CONTENTS, + branch=main.name, + commit_message="Add reusable PR label workflow file", ) - status = wait_for_workflow_run_completion(workflow_run=workflow_run) - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Workflow run for {branch_name} did not complete successfully: {status}" - ) - if workflow_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs(workflow_run, drop_log_timestamps=True) - raise AssertionError( - f"Workflow run for {branch_name} did not succeed as expected: {workflow_run.conclusion}\nLogs:\n{logs}" - ) - pr_labels = [label.name for label in pull_request.get_labels()] - if expected_labels != pr_labels: - sleep(90) - raise AssertionError( - f"Expected labels '{expected_labels}' didn't match pull request: {pr_labels} for branch {branch_name}" - ) + for branch_name, expected_labels in branch_name_label_map.items(): + with branch_created( + temporary_repository, branch_name, origin_branch=main.name + ) as branch: + populate_file( + repository=temporary_repository, + path="test.txt", + content="Test file", + branch=branch.name, + ) + + pull_request = temporary_repository.create_pull( + base="main", + head=branch_name, + title=f"Test PR Label for {branch_name}", + body=f"This is a test pull request to validate the autolabeler applies the {expected_labels} label to the branch name {branch_name}.", + ) + workflow = temporary_repository.get_workflow( + id_or_file_name="pull-request-label.yml" + ) + + with workflow_run_created(workflow, branch=branch.name) as run: + with workflow_run_completed(run) as status: + if status != WorkflowRunStatus.COMPLETED: + raise AssertionError( + f"Workflow run for {branch_name} did not complete successfully: {status}" + ) + if run.conclusion != WorkflowRunConclusion.SUCCESS: + logs = get_workflow_run_logs(run, drop_log_timestamps=True) + raise AssertionError( + f"Workflow run for {branch_name} did not succeed as expected: {run.conclusion}\nLogs:\n{logs}" + ) + pr_labels = [label.name for label in pull_request.get_labels()] + if expected_labels != pr_labels: + raise AssertionError( + f"Expected labels '{expected_labels}' didn't match pull request: {pr_labels} for branch {branch_name}" + ) diff --git a/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py b/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py index b5beaa1..ecbe8a2 100644 --- a/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py +++ b/test/integration/release-draft-on-merge/test_reusable_release_draft_on_merge.py @@ -347,7 +347,7 @@ def test_reusable_release_draft_repo_without_exising_release( ("patch!/major", "1.0.0"), ], ) -def test_reusable_release_draft_repo_with_exising_release( +def test_reusable_release_draft_repo_with_existing_release( temporary_repository, branch_name, expected_tag ): """ @@ -560,7 +560,7 @@ def test_reusable_release_draft_existing_repo_with_prs_and_tags( if release_run.conclusion != WorkflowRunConclusion.SUCCESS: logs = get_workflow_run_logs(release_run, drop_log_timestamps=True) raise AssertionError( - f"Release workflow run did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" + f"Release workflow run did not succeed as expected: {release_run.conclusion}\nLogs:\n{logs}" ) release = [release for release in temporary_repository.get_releases()][0] @@ -578,4 +578,4 @@ def test_reusable_release_draft_existing_repo_with_prs_and_tags( expected_tag_names = ["1.0.0", "1.0.1", update_to_tag] found_tag_names = [tag.name for tag in temporary_repository.get_tags()] - assert all(tag in found_tag_names for tag in expected_tag_names) or breakpoint() + assert all(tag in found_tag_names for tag in expected_tag_names) diff --git a/test/integration/test_reusable_release_draft_on_merge.py b/test/integration/test_reusable_release_draft_on_merge.py deleted file mode 100644 index b5beaa1..0000000 --- a/test/integration/test_reusable_release_draft_on_merge.py +++ /dev/null @@ -1,581 +0,0 @@ -import os - -import pytest - -from src.launch_github import ( - WorkflowRunConclusion, - WorkflowRunStatus, - branch_created, - get_workflow_run_logs, - populate_file, - workflow_run_completed, - workflow_run_created, -) - -LAUNCH_WORKFLOWS_REF_TO_TEST = os.environ.get("LAUNCH_WORKFLOWS_REF_TO_TEST", "main") -RELEASE_DRAFTER_CONFIG_CONTENTS = """ ---- -name-template: "$RESOLVED_VERSION" -tag-template: "$RESOLVED_VERSION" -template: | - # Changelog - - $CHANGES - - --- - - See details of [all code changes](https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$RESOLVED_VERSION) since previous release. - -categories: - - title: ":warning: Breaking Changes" - labels: - - "major" - - title: "🚀 Features" - labels: - - "minor" - - title: "🔧 Fixes" - collapse-after: 3 - labels: - - "patch" - -autolabeler: - - label: "major" - branch: - - '/(patch|bug|fix|feature)!\\/.+/' - - label: "minor" - branch: - - '/feature\\/.+/' - - label: "patch" - branch: - - '/(patch|bug|fix)\\/.+/' - -change-template: "- $TITLE @$AUTHOR (#$NUMBER)" - -version-resolver: - major: - labels: - - "major" - minor: - labels: - - "minor" - patch: - labels: - - "patch" - - "dependencies" - default: patch -""" -PR_WORKFLOW_CONTENTS = f""" -name: Label Pull Request - -on: - pull_request: - types: [opened, reopened, synchronize] - -jobs: - check: - name: "Label Pull Request" - permissions: - contents: read - issues: write - pull-requests: write - uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-label-by-branch.yml@{LAUNCH_WORKFLOWS_REF_TO_TEST} - secrets: inherit # pragma: allowlist secret -""" -RELEASE_WORKFLOW_CONTENTS = f""" -name: Draft Release - -on: - push: - branches: - - main - -permissions: - contents: read - -jobs: - draft-release: - name: "Draft Release on Merge" - permissions: - contents: write - pull-requests: write - uses: launchbynttdata/launch-workflows/.github/workflows/reusable-release-draft-on-merge.yml@{LAUNCH_WORKFLOWS_REF_TO_TEST} - secrets: inherit # pragma: allowlist secret -""" - - -@pytest.mark.parametrize( - "branch_name, expected_tag", - [ - ("fix/patch", "0.1.0"), - ("feature/minor", "0.1.0"), - ("patch!/major", "0.1.0"), - ], -) -def test_reusable_release_draft_empty_repository( - temporary_repository, branch_name, expected_tag -): - """In a brand new repository without any releases, the first release - _always_ has a zero major version per the SemVer spec. - - For more info: - - https://semver.org/spec/v2.0.0.html - - https://github.com/release-drafter/release-drafter/issues/1391 - """ - - with branch_created(temporary_repository, "main") as main: - populate_file( - repository=temporary_repository, - path=".github/release-drafter.yml", - content=RELEASE_DRAFTER_CONFIG_CONTENTS, - branch=main.name, - commit_message="Add release drafter configuration file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/pull-request-label.yml", - content=PR_WORKFLOW_CONTENTS, - branch=main.name, - commit_message="Add reusable PR label workflow file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/release-draft.yml", - content=RELEASE_WORKFLOW_CONTENTS, - branch=main.name, - commit_message="Add reusable release draft workflow file", - ) - - with branch_created( - temporary_repository, branch_name, origin_branch=main.name - ) as branch: - populate_file( - repository=temporary_repository, - path="test.txt", - content="Test file", - branch=branch.name, - ) - - pull_request = temporary_repository.create_pull( - base="main", - head=branch_name, - title=f"Test PR Label for {branch_name}", - body=f"This is a test pull request to validate the workflow drafts a release for {expected_tag}.", - ) - label_workflow = temporary_repository.get_workflow( - id_or_file_name="pull-request-label.yml" - ) - - with workflow_run_created(label_workflow, branch=branch.name) as label_run: - with workflow_run_completed(label_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Workflow run for {branch_name} did not complete successfully: {status}" - ) - if label_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs( - label_run, drop_log_timestamps=True - ) - raise AssertionError( - f"Workflow run for {branch_name} did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" - ) - pr_labels = [label.name for label in pull_request.get_labels()] - assert ( - pr_labels - ), "Expected at least one label to be applied to the pull request!" - - pull_request.merge() - - release_workflow = temporary_repository.get_workflow( - id_or_file_name="release-draft.yml" - ) - - with workflow_run_created( - release_workflow, branch=main.name - ) as drafter_run: - with workflow_run_completed(drafter_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Release drafter workflow run did not complete successfully: {status}" - ) - if drafter_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs( - drafter_run, drop_log_timestamps=True - ) - raise AssertionError( - f"Release drafter workflow run did not succeed as expected: {drafter_run.conclusion}\nLogs:\n{logs}" - ) - release = [ - release for release in temporary_repository.get_releases() - ][0] - assert release.title == expected_tag - # Perform the release - release.update_release( - name=release.title, message=release.body, draft=False - ) - tags = [tag for tag in temporary_repository.get_tags()] - assert tags[0].name == expected_tag - - -@pytest.mark.parametrize( - "branch_name, expected_tag", - [ - ("fix/patch", "0.1.0"), - ("feature/minor", "0.1.0"), - ("patch!/major", "0.1.0"), - ], -) -@pytest.mark.parametrize("tag_already_exists", [True, False]) -def test_reusable_release_draft_repo_without_exising_release( - temporary_repository, branch_name, expected_tag, tag_already_exists -): - """ - When a release is drafted into a repository without an existing release, - the first release drafted will always reflect 0.1.0. If a tag 0.1.0 exists, - the release will attempt to point at that existing tag and will not create - a new tag. This behavior is slightly counterintuitive, but is documented - here and in the markdown files for our release workflows, and users are - encouraged to edit the drafted release in the case of the draft workflow, - or encouraged to create a release before running the release-on-merge - workflow. - """ - - with branch_created(temporary_repository, "main") as main: - populate_file( - repository=temporary_repository, - path=".github/release-drafter.yml", - content=RELEASE_DRAFTER_CONFIG_CONTENTS, - branch=main.name, - commit_message="Add release drafter configuration file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/pull-request-label.yml", - content=PR_WORKFLOW_CONTENTS, - branch=main.name, - commit_message="Add reusable PR label workflow file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/release-draft.yml", - content=RELEASE_WORKFLOW_CONTENTS, - branch=main.name, - commit_message="Add reusable release draft workflow file", - skip_ci=True, - ) - - if tag_already_exists: - temporary_repository.create_git_ref( - ref="refs/tags/0.1.0", sha=main.commit.sha - ) - - with branch_created( - temporary_repository, branch_name, origin_branch=main.name - ) as branch: - populate_file( - repository=temporary_repository, - path="test.txt", - content="Test file", - branch=branch.name, - ) - - pull_request = temporary_repository.create_pull( - base="main", - head=branch_name, - title=f"Test PR Label for {branch_name}", - body=f"This is a test pull request to validate the workflow drafts a release for {expected_tag}.", - ) - label_workflow = temporary_repository.get_workflow( - id_or_file_name="pull-request-label.yml" - ) - - with workflow_run_created(label_workflow, branch=branch.name) as label_run: - with workflow_run_completed(label_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Workflow run for {branch_name} did not complete successfully: {status}" - ) - if label_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs( - label_run, drop_log_timestamps=True - ) - raise AssertionError( - f"Workflow run for {branch_name} did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" - ) - pr_labels = [label.name for label in pull_request.get_labels()] - assert ( - pr_labels - ), "Expected at least one label to be applied to the pull request!" - - pull_request.merge() - - release_workflow = temporary_repository.get_workflow( - id_or_file_name="release-draft.yml" - ) - - with workflow_run_created( - release_workflow, branch=main.name - ) as drafter_run: - with workflow_run_completed(drafter_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Release drafter workflow run did not complete successfully: {status}" - ) - if drafter_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs( - drafter_run, drop_log_timestamps=True - ) - raise AssertionError( - f"Release drafter workflow run did not succeed as expected: {drafter_run.conclusion}\nLogs:\n{logs}" - ) - release = [ - release for release in temporary_repository.get_releases() - ][0] - assert release.title == expected_tag - # Perform the release - release.update_release( - name=release.title, message=release.body, draft=False - ) - tags = [tag for tag in temporary_repository.get_tags()] - assert tags[0].name == expected_tag - - -@pytest.mark.parametrize( - "branch_name, expected_tag", - [ - ("fix/patch", "0.1.1"), - ("feature/minor", "0.2.0"), - ("patch!/major", "1.0.0"), - ], -) -def test_reusable_release_draft_repo_with_exising_release( - temporary_repository, branch_name, expected_tag -): - """ - First PR tag defaults to major, so this isn't working the way I'd originally hoped. - - Maybe we change up the testing strategy for the release workflows entirely. Set - up repositories in states where they're likely to need release-drafter workflows installed: - - - Brand new repo, no existing pull requests, tags, or releases - - Existing repo with pull requests and tags -> go through draft/update/publish and then merge another PR - - Existing repo with pull requests, tags, and releases - """ - - with branch_created(temporary_repository, "main") as main: - populate_file( - repository=temporary_repository, - path=".github/release-drafter.yml", - content=RELEASE_DRAFTER_CONFIG_CONTENTS, - branch=main.name, - commit_message="Add release drafter configuration file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/pull-request-label.yml", - content=PR_WORKFLOW_CONTENTS, - branch=main.name, - commit_message="Add reusable PR label workflow file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/release-draft.yml", - content=RELEASE_WORKFLOW_CONTENTS, - branch=main.name, - commit_message="Add reusable release draft workflow file", - skip_ci=True, - ) - - # Create the 0.1.0 release ahead of time - temporary_repository.create_git_release( - tag="0.1.0", - name="Release 0.1.0", - message="This is the first release.", - draft=False, - prerelease=False, - generate_release_notes=True, - target_commitish=main, - ) - - with branch_created( - temporary_repository, branch_name, origin_branch=main.name - ) as branch: - populate_file( - repository=temporary_repository, - path="test.txt", - content="Test file", - branch=branch.name, - ) - - pull_request = temporary_repository.create_pull( - base="main", - head=branch_name, - title=f"Test PR Label for {branch_name}", - body=f"This is a test pull request to validate the workflow drafts a release for {expected_tag}.", - ) - label_workflow = temporary_repository.get_workflow( - id_or_file_name="pull-request-label.yml" - ) - - with workflow_run_created(label_workflow, branch=branch.name) as label_run: - with workflow_run_completed(label_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Workflow run for {branch_name} did not complete successfully: {status}" - ) - if label_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs( - label_run, drop_log_timestamps=True - ) - raise AssertionError( - f"Workflow run for {branch_name} did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" - ) - pr_labels = [label.name for label in pull_request.get_labels()] - assert ( - pr_labels - ), "Expected at least one label to be applied to the pull request!" - - pull_request.merge() - - release_workflow = temporary_repository.get_workflow( - id_or_file_name="release-draft.yml" - ) - - with workflow_run_created( - release_workflow, branch=main.name - ) as drafter_run: - with workflow_run_completed(drafter_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Release drafter workflow run did not complete successfully: {status}" - ) - if drafter_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs( - drafter_run, drop_log_timestamps=True - ) - raise AssertionError( - f"Release drafter workflow run did not succeed as expected: {drafter_run.conclusion}\nLogs:\n{logs}" - ) - release = [ - release for release in temporary_repository.get_releases() - ][0] - assert release.title == expected_tag or breakpoint() - # Perform the release - release.update_release( - name=release.title, message=release.body, draft=False - ) - tags = [tag for tag in temporary_repository.get_tags()] - assert tags[0].name == expected_tag - - -@pytest.mark.parametrize( - "branch_name, initial_release_target_tag, update_to_tag", - [ - ("fix/patch", "0.1.0", "1.0.2"), - ("feature/minor", "0.1.0", "1.1.0"), - ("patch!/major", "0.1.0", "2.0.0"), - ], -) -def test_reusable_release_draft_existing_repo_with_prs_and_tags( - temporary_repository, branch_name, initial_release_target_tag, update_to_tag -): - """ - Test the reusable release drafter workflow on an existing repository with pull requests and tags. - - There have been no releases made to this repository, so the drafted release will be created with the "wrong" - tag associated to it. The release will need to be updated by someone (in this case, the test code but typically a human) - to reflect the next desired version before the release is published. - """ - - with branch_created(temporary_repository, "main") as main: - temporary_repository.create_git_ref(ref="refs/tags/1.0.0", sha=main.commit.sha) - - with branch_created(temporary_repository, "branch-one", "main") as branch_one: - populate_file( - repository=temporary_repository, - path="test_one.txt", - content="Test file one", - branch=branch_one.name, - ) - pull_request_one = temporary_repository.create_pull( - base="main", - head=branch_one.name, - title="Test PR One", - body="This is a test pull request for branch one.", - ) - pull_request_one.merge() - temporary_repository.create_git_ref( - ref="refs/tags/1.0.1", - sha=temporary_repository.get_branch( # get the latest sha off main - "main" - ).commit.sha, - ) - - temporary_repository.get_git_ref("heads/branch-one").delete() - - # Install the workflows - - with branch_created(temporary_repository, branch_name, "main") as branch_two: - populate_file( - repository=temporary_repository, - path=".github/release-drafter.yml", - content=RELEASE_DRAFTER_CONFIG_CONTENTS, - branch=branch_two.name, - commit_message="Add release drafter configuration file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/pull-request-label.yml", - content=PR_WORKFLOW_CONTENTS, - branch=branch_two.name, - commit_message="Add reusable PR label workflow file", - ) - populate_file( - repository=temporary_repository, - path=".github/workflows/release-draft.yml", - content=RELEASE_WORKFLOW_CONTENTS, - branch=branch_two.name, - commit_message="Add reusable release draft workflow file", - ) - pull_request_two = temporary_repository.create_pull( - base="main", - head=branch_two.name, - title="Install workflows", - body="Install release workflows", - ) - pull_request_two.merge() - - temporary_repository.get_git_ref(f"heads/{branch_name}").delete() - - # Confirm the release target - release_workflow = temporary_repository.get_workflow( - id_or_file_name="release-draft.yml" - ) - - with workflow_run_created(release_workflow, branch=main.name) as release_run: - with workflow_run_completed(release_run) as status: - if status != WorkflowRunStatus.COMPLETED: - raise AssertionError( - f"Release workflow run did not complete successfully: {status}" - ) - if release_run.conclusion != WorkflowRunConclusion.SUCCESS: - logs = get_workflow_run_logs(release_run, drop_log_timestamps=True) - raise AssertionError( - f"Release workflow run did not succeed as expected: {label_run.conclusion}\nLogs:\n{logs}" - ) - - release = [release for release in temporary_repository.get_releases()][0] - assert release.tag_name == initial_release_target_tag - - # Human (or computer) updates the release to the next expected tag - release.update_release( - name=update_to_tag, - tag_name=update_to_tag, - message=release.body, - draft=False, - ) - - # Tag should have been created - expected_tag_names = ["1.0.0", "1.0.1", update_to_tag] - found_tag_names = [tag.name for tag in temporary_repository.get_tags()] - - assert all(tag in found_tag_names for tag in expected_tag_names) or breakpoint() diff --git a/uv.lock b/uv.lock index 69ab2da..1cfa67b 100644 --- a/uv.lock +++ b/uv.lock @@ -1,13 +1,14 @@ version = 1 +revision = 3 requires-python = ">=3.11" [[package]] name = "certifi" version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386 } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216 }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] @@ -17,99 +18,99 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367 } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794 }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846 }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350 }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657 }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260 }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164 }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571 }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952 }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959 }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030 }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015 }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106 }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402 }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936 }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790 }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924 }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626 }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567 }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957 }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408 }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399 }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815 }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537 }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565 }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357 }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776 }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622 }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435 }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653 }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231 }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243 }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442 }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147 }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057 }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454 }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174 }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166 }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064 }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641 }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626 }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, ] [[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 } +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 }, + { 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]] @@ -119,38 +120,47 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903 } +sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092 }, - { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926 }, - { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235 }, - { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785 }, - { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050 }, - { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379 }, - { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355 }, - { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087 }, - { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873 }, - { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651 }, - { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050 }, - { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224 }, - { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143 }, - { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780 }, - { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091 }, - { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711 }, - { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299 }, - { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558 }, - { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020 }, - { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759 }, - { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991 }, - { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189 }, - { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769 }, - { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016 }, - { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878 }, - { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447 }, - { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778 }, - { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627 }, - { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593 }, - { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106 }, + { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, + { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, + { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, + { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, + { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, + { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, + { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, + { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, + { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, + { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, + { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, ] [[package]] @@ -160,9 +170,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "smmap" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, ] [[package]] @@ -172,27 +182,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076 } +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168 }, + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -203,6 +213,7 @@ dependencies = [ { name = "gitpython" }, { name = "pygithub" }, { name = "pytest" }, + { name = "pytest-xdist" }, ] [package.metadata] @@ -210,33 +221,34 @@ requires-dist = [ { name = "gitpython", specifier = ">=3.1.45" }, { name = "pygithub", specifier = ">=2.7.0" }, { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[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 } +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 }, + { 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 = "2.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] [[package]] @@ -250,27 +262,27 @@ dependencies = [ { name = "typing-extensions" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/a7/403e04aa96e2d94e1518d518d69718c2ba978c8d3ffa4ab3b101b94dbafa/pygithub-2.7.0.tar.gz", hash = "sha256:7cd6eafabb09b5369afba3586d86b1f1ad6f1326d2ff01bc47bb26615dce4cbb", size = 3707928 } +sdist = { url = "https://files.pythonhosted.org/packages/6a/a7/403e04aa96e2d94e1518d518d69718c2ba978c8d3ffa4ab3b101b94dbafa/pygithub-2.7.0.tar.gz", hash = "sha256:7cd6eafabb09b5369afba3586d86b1f1ad6f1326d2ff01bc47bb26615dce4cbb", size = 3707928, upload-time = "2025-07-31T11:52:53.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/76/d768dd31322173b3956692b75471ac37bf3759c7abb603152f6a9b6594a8/pygithub-2.7.0-py3-none-any.whl", hash = "sha256:40ecbfe26dc55cc34ab4b0ffa1d455e6f816ef9a2bc8d6f5ad18ce572f163700", size = 416514 }, + { url = "https://files.pythonhosted.org/packages/57/76/d768dd31322173b3956692b75471ac37bf3759c7abb603152f6a9b6594a8/pygithub-2.7.0-py3-none-any.whl", hash = "sha256:40ecbfe26dc55cc34ab4b0ffa1d455e6f816ef9a2bc8d6f5ad18ce572f163700", size = 416514, upload-time = "2025-07-31T11:52:51.909Z" }, ] [[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 } +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 }, + { 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.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, ] [package.optional-dependencies] @@ -285,17 +297,17 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/22/27582568be639dfe22ddb3902225f91f2f17ceff88ce80e4db396c8986da/PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", size = 3392854, upload-time = "2022-01-07T22:05:41.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920 }, - { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722 }, - { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087 }, - { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678 }, - { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660 }, - { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824 }, - { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912 }, - { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624 }, - { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141 }, + { url = "https://files.pythonhosted.org/packages/ce/75/0b8ede18506041c0bf23ac4d8e2971b4161cd6ce630b177d0a08eb0d8857/PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", size = 349920, upload-time = "2022-01-07T22:05:49.156Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/fddf10acd09637327a97ef89d2a9d621328850a72f1fdc8c08bdf72e385f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", size = 601722, upload-time = "2022-01-07T22:05:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/5d/70/87a065c37cca41a75f2ce113a5a2c2aa7533be648b184ade58971b5f7ccc/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", size = 680087, upload-time = "2022-01-07T22:05:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/ee/87/f1bb6a595f14a327e8285b9eb54d41fef76c585a0edef0a45f6fc95de125/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", size = 856678, upload-time = "2022-01-07T22:05:54.251Z" }, + { url = "https://files.pythonhosted.org/packages/66/28/ca86676b69bf9f90e710571b67450508484388bfce09acf8a46f0b8c785f/PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", size = 1133660, upload-time = "2022-01-07T22:05:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/3d/85/c262db650e86812585e2bc59e497a8f59948a005325a11bbbc9ecd3fe26b/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", size = 663824, upload-time = "2022-01-07T22:05:57.434Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1a/cc308a884bd299b651f1633acb978e8596c71c33ca85e9dc9fa33a5399b9/PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", size = 1117912, upload-time = "2022-01-07T22:05:58.665Z" }, + { url = "https://files.pythonhosted.org/packages/25/2d/b7df6ddb0c2a33afdb358f8af6ea3b8c4d1196ca45497dd37a56f0c122be/PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543", size = 204624, upload-time = "2022-01-07T22:06:00.085Z" }, + { url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141, upload-time = "2022-01-07T22:06:01.861Z" }, ] [[package]] @@ -309,9 +321,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] [[package]] @@ -324,34 +349,34 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258 } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847 }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] name = "smmap" version = "5.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329 } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303 }, + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, ] [[package]] name = "typing-extensions" version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ] [[package]] name = "urllib3" version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] From 6353fec0770949c254926d3664bec5d1a2841ec1 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 9 Feb 2026 16:02:19 -0600 Subject: [PATCH 17/20] drop case after fixing workflow --- .../pr-label-by-branch/test_reusable_pr_label_by_branch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py b/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py index 5093922..d6cdb1d 100644 --- a/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py +++ b/test/integration/pr-label-by-branch/test_reusable_pr_label_by_branch.py @@ -83,7 +83,6 @@ def test_reusable_pr_label_by_branch(temporary_repository): branch_name_label_map = { - "first_pr_always_major_regardless_of_name": ["major"], "bug/something": ["patch"], "fix/something": ["patch"], "patch/something": ["patch"], From 7f69e321911e268ee2cdeebe3bada5acf9c65bf7 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 9 Feb 2026 16:13:58 -0600 Subject: [PATCH 18/20] drop stray pycache files --- src/__pycache__/launch_github.cpython-311.pyc | Bin 8145 -> 0 bytes src/__pycache__/launch_github.cpython-313.pyc | Bin 10486 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/__pycache__/launch_github.cpython-311.pyc delete mode 100644 src/__pycache__/launch_github.cpython-313.pyc diff --git a/src/__pycache__/launch_github.cpython-311.pyc b/src/__pycache__/launch_github.cpython-311.pyc deleted file mode 100644 index b56a9c358486d4cd86f4b6fc43fce31b628ee78b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8145 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 uCr;JO?Ht?(0jRe5*+(@nsOO;fS`RF-! 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 DV$S@7 From b9de37363b3013612f36a9ecccea5710c487379a Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 9 Feb 2026 16:38:22 -0600 Subject: [PATCH 19/20] clean up __pycache__ --- test/__pycache__/__init__.cpython-313.pyc | Bin 171 -> 0 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 183 -> 0 bytes .../conftest.cpython-313-pytest-8.4.1.pyc | Bin 4077 -> 0 bytes ...ithub_matrix_tg.cpython-313-pytest-8.4.1.pyc | Bin 8836 -> 0 bytes ...label_by_branch.cpython-313-pytest-8.4.1.pyc | Bin 5298 -> 0 bytes ..._draft_on_merge.cpython-311-pytest-7.4.3.pyc | Bin 34022 -> 0 bytes ..._draft_on_merge.cpython-313-pytest-8.4.1.pyc | Bin 28049 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/__pycache__/__init__.cpython-313.pyc delete mode 100644 test/integration/__pycache__/__init__.cpython-313.pyc delete mode 100644 test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc delete mode 100644 test/integration/__pycache__/test_reusable_github_matrix_tg.cpython-313-pytest-8.4.1.pyc delete mode 100644 test/integration/__pycache__/test_reusable_pr_label_by_branch.cpython-313-pytest-8.4.1.pyc delete mode 100644 test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-311-pytest-7.4.3.pyc delete mode 100644 test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-313-pytest-8.4.1.pyc diff --git a/test/__pycache__/__init__.cpython-313.pyc b/test/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 1188860ccec91aebed9f8450ba24f76896f48546..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 171 zcmey&%ge<81oIraGC}lX5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iCerR!OQL%n< zMp0(5UP)qQPJWSoa(+sxeokU(UUEiKWnM{1N@7VOl&@Q!UzDAelV4t}Uy@o}q8}fh pnU`4-AFo$Xd5gm)H$SB`C)KWq6=*cb>S7S%BQql-V-Yiu1puXHEM))y diff --git a/test/integration/__pycache__/__init__.cpython-313.pyc b/test/integration/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 118bcb96e118d686dcf9c1c3d5d79c157c28a3af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 183 zcmey&%ge<81oIraGC}lX5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa|LYerR!OQL%n< zMp0(5UP)qQPJWSoa(+sxeokU(UUEiKWnM{1N@7VOl&@Q!UzDAelV4t}Uy@o}qMw;p zlA2zWSdy8aryn1mnU`4-AFo$Xd5gm)H$SB`C)KWq6=)5}8O0#RM`lJw#v*1Q3jjN9 BF+>0W diff --git a/test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc b/test/integration/__pycache__/conftest.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 7b39faf1ac1c41830c7c841771d060b6505457ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4077 zcma)8Z)_9E6`x)2de{G**a`VVVFM=MKpas*TXNi8!vQ%bI)~aZT#p>q#@-|g&aOMV z4mjW@^aRQPruIjPdM{dAuy`*Jky-q9l6^#k9Gd^(A5eKWQb+ogq( zXLkH%-kUe?{oZ?1^LU&H+Pl#Y)vp~0eMSB>o4qlqaV$c2Q355H8H5@7Wv6WhE1_u) z^HP1qj_q_szyke>SfpPEcEFdNaZbCii|)0}xTihX)8O-BFRXJjzG*-9QyM?hHQkN7 z>D)dOm=59~oeMKP)4jM?Li65uq^acY_W(+W3CAfW;T%9G8)V$~lD)2$z5V}lubayC z%zNg$6HI~`xA7oVA<3${NdtCPKU317B9(~x+a^d zuEBpNp&Ke}Dm-AUz>;F7mQ-^&zmQTj!<4nOVhM&~81>z}k=Aocx_LYD<)9ygUb{di z62S2?%<3%;@a$}Ha+{6@mt#*h_O7D40Nf??ecK`vw#BUexpRrRXlyDq8=sntMcq~oT^vgq3O2^l%UCrcrd-JC zcr2}Fl(DRw*V4-i1tLW@<;MTf>pH%+nANWvV?^|rs+r0Xm=P=hY}z6rMsfwKmrPQ6 zi#b!8yrSS9IMff_7=+;|T1Q_y-FHTAkKBpej+FYxKf1c*oZR9jpBuz-8=+yQ&78yt zHt4lp7{HVad}wQ8IfNcCF)RWNcatGNh9C@N!vzEA;o7(5l1iLJc`R`e zZclDy*|B2gv6gOi7SVy~5rWBGltl3zO@Pp7N#N)fB)|!D-o0 zEPF-;e;XjmZsZpi)f)zJMZ>fXBDD_3Fi?k}8zegsI6m?C?p(Tk>9-eucjebt{&0Rf zG*J#s{ORH+?|=M$IW+yLT<*EB<@wS2`HH)Db9HmMm&ap(IncC`cy0?`TcQM;de)0QJ%{ zY@KH;aZ4OcKK$AT&hzRGhz&(Dbt#ASRW+kXvXs@+a#kWXu4hguC!5RCqK+lvQgBzq zIk&7DQW`5VP=Lc!R&qL)aY4$cSV@y~E1Ao(DWzpiT2Q3Cp=2bP?Cg{saastE4dU=(W>KuOxq&S zNe@kt6Yz16aTvOB1qOnbsvC9n7hfy;hu5PO&U172=d+)4J>S?7FWii7L^mD9aB;BY zJ@h#@{KOg9n6288*tIR}DGPhHh5oY8|E1`>uHXp<3;BsZ4>VzlmYGDfh?R5G(keMtPAdQ z8QydCmJnO8EhfZK56ElP22kG6hR7ir?P>}-UM8uXwuB>T^P+?^X@}8uf^W)Im?Ynb z89oluJ6khmpyawK>OE_}X+?bey9Fu5QLkS7|EV>wpj8!0FfP%L<=na{nxlaT7l&<7ws4?9VjFg}BU#=ai~_HTEOl)FbNp599D>4%p-ng4ix z`}J7)^;qfjY$|ed?dyKvCH0 zJ6Z`!+rg1?aOCg7W4F#$e1SWK+l6BEk?&BYZ|^4m4}Z@y0riZ2BO-_Qmi#NRBKkIu z-*(>;zV#qquqZt0JNm63xx2pf4phATH1mYs{IlcXPd^;>vL8S>u|#J!PWa2 z*Hhtq+gxv%>wU!aJvYdMX=8HVneQ=w2_1m(uVMto@Q@`g1BK~MxM*Rcu!a_o?Zuj(`>Fq7>mxsi(nl4`T+0w(DUAA-T+h_~y$x0{6= z6bnphD8QDoS|a!s4uMQeQX;&_sjDNiE$>WBYO&STSc&h11STcX>N0RTQh(Yup7NNa z8%uyU0x4y6M3J{4g_UIkkWp+|+y&5<766Mk^SP`-0nz!)6i=_{nS54x1OFIgkSH;J z0Atn0FwDOY`T+gH@dfgJfjsbgjCvoVyZbQ}rOK=-Ub~C`>PXeU)5r3jaq(lmq0FokPk+emdMUkTUlZ_$lt)d*%kQkEif-^|Z zfD*@+qr`jI!yZzVa+UYAtQ=C>n{RRD5~} z%VH~O<(a$QCZ&laW_G3Y zRyHRY{5>(NrMDE)*ke=K&F3TyN9a2R1E^?{(YHL4TAf`;e{d(cG{1c310kK9n@_LZ zNw3WbYX-L~W@RHtYJzOCw&vjLgTEX8&)i-m!#a(HNzn#?<3LE^2sFCb_-qubqsK`@69unvv2O9YEf~0Cw!Qe;A zhKdavg={Vp+oioMM2YNXRa7!Xz`mP`tVyz_PVuNhO0J+vP|b-2d2?rDU)HpYsEH$H zJ!Tt09f@yewVlGoNF73?f+}w0q?p-a4IR_Ag*Z3=~CNfRhizdDo2@HQhn&H^6jf=DJqwg$~^QMci< z#5TMeL#&p;>H^m7)YYF&Q^?*G^MhtdD4^Umjwfmahj}}MG@eYRT zme)BxEAx8C8e+`VXBR&Yb0f+bTwc_6NGtG1%}V8pvILd+TaRh(PHa4jSr=v~xr&nG zs^JN2}(gUWQpm&S*+f|7X|CFO0?7W7#8#pmIkqT-isGv)3IKc&d+aGPc_u<0mn>m&UB= z=qG7wXY1!Ypz48lSE8I|yB(PNz+G=Wnc~xsQQ)`9)-&%hPYpr+NhJsUBWRm4! zw@SjEb~Fhs0LQ)lND^A@Igso>(gf8ce;j6?xOrxM&~6pZ*i-;|$%X<)Z9@B`3t+X= zJHR@GPT?%HbWM6r<+l5Oh1(vJTNoQX;*lh@0?)ldpKuPH{;4=ULcegnhI8cz5vU5h zaAA^_L+dfSEfq5LBsZ;()L>1vM&;&I^ZKX_PhPW2_3zX@h7rTCZ`O8fVIb8UX2xAM z4T()PB@^`?c4_@}yOf-;OZ6|M3r7kE-NBRTG>+9E8P%yAc1RYsxsoHP$SX^BF%@YX zEfxL=$)@TO*`*UCyL2+0#Dujd!0M8Ut*Muvtbdos_ma3mJPCUu1Puru~Hn9D!*wfJa#52nA z-f*(H;u!P9Ua6XVKZ#j8*$7uHA9toc_Z+=XK84iwAX_%mtK}0yOm0iHrCRX3C%32C zZJ9HF*C6xBXN`EO9V|G4{p+U<2QMef+-CD2jJ}c$U*edu7vUgAMLhhzezy}8w3Rlbj=1q!2*p& z)i{PLf)C;|CvHMp~~&)oAd65wh;KIBL{mxbyp?m|epxf!XVrO{~sCq{x+3 zqpUuHy-yh|PqKR;XYc`^RVkm>VH7vvVLFlvkRh$>de|Y`ji`#i8-_Pu*vMr!qwIgX zZr+4puKma`ds#KRkFzrrM?+Tu}Lyt*mLBFQRoyrpl5s$_7eUBjbgH3;r;8%kzhpMFWUS1{ToZ}ab3 zfhK;QUeEQv`V+`tskj!@f(R6b0^2G{sMLl_ldeWelLsyw>hL;r$f_OBvDe-Dya}xqtTVRs?DPEs^na;G2 zl;OoXbI|qnv971c#~)*&k z5D*8fwK{NBA80@`LuQCkTf~5Kt^vmmry&-s?}7TD4|ES=erT`{8lpUI*a61j|NJUs z)9fI_(_mX0vj71&rFG4PUU-lB9M1kL5C;lD`!;jKy~Re^)!#A@@pQ#U4KA0JB_o2N z^E91BXU6x;Q^1R@(Jz*ds& zJk@;^4G9}zD|!t(3?Ob9L3}>3BHdo-wVTxpx4I(|G(Ex7Ee%-KqTaZr8%@qP7lH?0 zg27&b!9OD~duQd{<+-)FTSgFK>DJQ?!T6}*6H>FYbAn*_=4aknP9^6EzKRfh5s98P z+VOxw4w=@H3i~La{Pu{hn~KV(3DxY8IYxF&xrDDb;JxP{Te%dNrh40l>fKdJca6guC`F z2QM`JC9c2Xzw&6k9Gfl0X1|_4j4hR8$xc-%nO-$*z4gcB%Ny(O-<(%e>QzEla%RGRnjNuuw2d^f5#QTjE6YnAY7 z)y0I9E(cDSY3VIThD(v*;>1!B^2qYTh2M9KRs1c*?y>LPu1@d6RS>Fs@~i&Cu7x7k zQSo&>?TeNBZj}0N9Qtm26Yi^o-zhGx7vH&CZ2IuI+Z72`8CM`!^)mj>vX3wM_@@^q z%NJ)#7iSKAGZg>!<8XOFEG>w|+Z)BE&3Y#gbMP%X_B4z==q+;RVep}Ow&EWItRVml zAmO2>(W!FuRw;Vx(07ZHOcmePim5`eX|GNVF$c@OSjiWAIyPS(TP=;P9{N@({(Hsb zZaI0sl)PVjZ@<{2*LwjEXRkh5Exx-}>P$Vn^UdHyadPc&FjeIEAGpv3VBy@09wxHN zmd2M4!z)E@vw1aO~_MPt+m#H1NAN?z;RS-$j`WG`gjGG9lQ`lyA8kXI>{{n7~N^pZ^q5- zAKW3_c-8>1#`KVClhUMRFF)QoJfA8u zeN}He>pjq4Fz~5PFm2u6hniS#rKP9phVQqn?NtwaVd6EnRDD?TGm$gZ0G5IP`%lDv M-hY>6-=h5e55e#B`Tzg` diff --git a/test/integration/__pycache__/test_reusable_pr_label_by_branch.cpython-313-pytest-8.4.1.pyc b/test/integration/__pycache__/test_reusable_pr_label_by_branch.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 8903321205bb8d51be8c2e5bea0642b08e3d349d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5298 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 G&Hn=<*RQVt diff --git a/test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-311-pytest-7.4.3.pyc b/test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-311-pytest-7.4.3.pyc deleted file mode 100644 index 9672d62c37cbd28553438e3b05e357f1f414e6d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34022 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 Z%is>Bo@3eTFkB*E`grvI%55_5{C~Uu;pzYY diff --git a/test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-313-pytest-8.4.1.pyc b/test/integration/__pycache__/test_reusable_release_draft_on_merge.cpython-313-pytest-8.4.1.pyc deleted file mode 100644 index 7aa8de5aa4a28155d24e33ef0635e22c3b767d66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28049 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 OQT|ym@ryrz-u*xGy}Eb+ From 88c00a9285ce966e1f5f9c97a7d2ad89f7a583a7 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 9 Feb 2026 16:38:35 -0600 Subject: [PATCH 20/20] clean up test lib --- test/lib/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/lib/__init__.py diff --git a/test/lib/__init__.py b/test/lib/__init__.py deleted file mode 100644 index e69de29..0000000