├── .flake8 ├── .github ├── dependabot.yml └── workflows │ ├── package.yml │ └── publish.yml ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker └── config.yml ├── docs ├── docker │ └── README.md └── readme │ └── github_token_permission.png ├── gitea_github_sync ├── __init__.py ├── __main__.py ├── cli.py ├── config.py ├── gitea.py ├── github.py ├── migration.py └── repository.py ├── mypy.ini ├── poetry.lock ├── pyproject.toml ├── tests ├── __init__.py ├── test_cli.py ├── test_config.py ├── test_gitea.py ├── test_github.py └── test_migration.py └── tox.ini /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 127 3 | exclude = .git 4 | extend-ignore = E203 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Test and lint 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - main 8 | workflow_call: {} 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.8", "3.9", "3.10", "3.11"] 16 | defaults: 17 | run: 18 | shell: bash 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Setup python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | architecture: x64 26 | - name: Install poetry 27 | uses: snok/install-poetry@v1 28 | - name: Install dependencies 29 | run: poetry install --with dev 30 | - name: Check linters for failure 31 | run: | 32 | poetry run pip install tox-gh-actions 33 | poetry run tox -elint 34 | - name: Upload code coverage 35 | uses: codecov/codecov-action@v4 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | file: ./coverage.xml 39 | name: gitea_github_sync 40 | flags: unittests 41 | build-docker: 42 | runs-on: ubuntu-latest 43 | defaults: 44 | run: 45 | shell: bash 46 | steps: 47 | - uses: actions/checkout@v2 48 | - name: Docker metadata 49 | id: meta 50 | uses: docker/metadata-action@v4 51 | with: 52 | images: muscaw/gitea-github-sync 53 | tags: | 54 | type=ref,event=branch 55 | type=ref,event=pr 56 | type=semver,pattern={{version}} 57 | type=semver,pattern={{major}}.{{minor}} 58 | - name: Build docker image 59 | uses: docker/build-push-action@v3 60 | with: 61 | context: . 62 | file: ./Dockerfile 63 | push: false 64 | tags: ${{ steps.meta.outputs.tags }} 65 | labels: ${{ steps.meta.outputs.labels }} 66 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Pypi 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+.[0-9]+.[0-9]+" 7 | 8 | jobs: 9 | call-build: 10 | uses: ./.github/workflows/package.yml 11 | build-docker: 12 | needs: call-build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Docker Buildx 17 | uses: docker/setup-buildx-action@v2 18 | - name: Docker metadata 19 | id: meta 20 | uses: docker/metadata-action@v4 21 | with: 22 | images: muscaw/gitea-github-sync 23 | tags: | 24 | type=ref,event=branch 25 | type=ref,event=pr 26 | type=semver,pattern={{version}} 27 | type=semver,pattern={{major}}.{{minor}} 28 | - name: Login to Docker Hub 29 | uses: docker/login-action@v2 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | - name: Build and push docker image 34 | uses: docker/build-push-action@v3 35 | with: 36 | context: . 37 | file: ./Dockerfile 38 | push: true 39 | tags: ${{ steps.meta.outputs.tags }} 40 | labels: ${{ steps.meta.outputs.labels }} 41 | publish-to-pypi: 42 | needs: call-build 43 | runs-on: ubuntu-latest 44 | defaults: 45 | run: 46 | shell: bash 47 | steps: 48 | - uses: actions/checkout@v2 49 | - name: Setup python 50 | uses: actions/setup-python@v2 51 | with: 52 | python-version: 3.8 53 | architecture: x64 54 | - name: Install poetry 55 | uses: snok/install-poetry@v1 56 | - name: Publish to Pypi 57 | env: 58 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.POETRY_PYPI_TOKEN_PYPI }} 59 | run: | 60 | poetry --build publish 61 | 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "tests" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true, 7 | "python.linting.mypyEnabled": true, 8 | "python.linting.flake8Enabled": true, 9 | "isort.check": true, 10 | "python.formatting.provider": "black" 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## Unreleased 9 | 10 | ### Added 11 | 12 | ## [0.1.1] - 2023-01-05 13 | 14 | ### Added 15 | - Create docker image as part of the release pipeline 16 | 17 | ## [0.1.0] - 2023-01-05 18 | 19 | ### Added 20 | - CLI now supports the following features 21 | - List gitea repositories 22 | - List github repositories 23 | - Mirror one repository 24 | - Sync github to gitea 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | RUN useradd -m python-user 4 | USER python-user 5 | 6 | WORKDIR /app 7 | 8 | ENV PATH="${PATH}:/home/python-user/.local/bin/" 9 | 10 | COPY gitea_github_sync/ /app/gitea_github_sync 11 | COPY README.md poetry.lock pyproject.toml /app/ 12 | COPY docker/config.yml /home/python-user/.config/gitea-github-sync/config.yml 13 | 14 | RUN pip install poetry && poetry install 15 | 16 | ENTRYPOINT ["poetry", "run", "python", "-m", "gitea_github_sync"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Kevin Grandjean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Supported Python Versions](https://img.shields.io/pypi/pyversions/gitea-github-sync/0.1.0)](https://pypi.org/project/gitea-github-sync/) [![PyPI version](https://badge.fury.io/py/gitea-github-sync.svg)](https://badge.fury.io/py/gitea-github-sync) 2 | 3 | [![codecov](https://img.shields.io/codecov/c/github/Muscaw/gitea-github-sync?label=codecov&logo=codecov)](https://codecov.io/gh/Muscaw/gitea-github-sync) ![PyPI - Downloads](https://img.shields.io/pypi/dm/gitea-github-sync) 4 | 5 | # gitea-github-sync 6 | 7 | gitea-github-sync provides a simple CLI to sync Github repositories to your Gitea instance. 8 | 9 | ## Installation 10 | 11 | ``` 12 | pip install gitea-github-sync 13 | ``` 14 | 15 | If you are interested in using a pre-packaged docker image, please look at the [Docker Readme](docs/docker/README.md) 16 | 17 | ## Setup 18 | Create a file in `$HOME/.config/gitea-github-sync/config.yml` with the following template and fill up the missing values: 19 | 20 | ```yaml 21 | gitea_api_url: https:///api/v1 22 | gitea_token: 23 | github_token: 24 | ``` 25 | 26 | ### Creating a Gitea token 27 | Go to https://\/user/settings/applications and generate a new token. 28 | 29 | ### Creating a Github token 30 | 31 | Go to https://github.com/settings/tokens/new and create a token with the following values set: 32 | - Note: this is a note to help you understand how the token is used. 33 | - Expiration: No expiration 34 | - repo: Select all of repo 35 | 36 | ![Screenshot of token configuration](docs/readme/github_token_permission.png) 37 | 38 | #### Github token limitation 39 | Github allows you to create _Personal access tokens_. These tokens now exist in two different flavors: 40 | - Fine-grained tokens 41 | - Classic tokens 42 | 43 | Both work with gitea-github-sync, but given that Gitea does not allow the modification of the access token through the API, a **non-expiring** token must be used which limits the usage to Classic tokens. 44 | 45 | ## Usage 46 | 47 | `gitea-github-sync --help` Shows the help 48 | 49 | `gitea-github-sync list-all-gitea-repositories` Lists all available Gitea repositories in your account 50 | 51 | `gitea-github-sync list-all-github-repositories` Lists all available Github repositories in your account 52 | 53 | `gitea-github-sync migrate-repo FULL_REPO_NAME` Migrates one repo from Github to Gitea 54 | 55 | `gitea-github-sync sync` Migrates all repos not present in Gitea from Github 56 | 57 | ## Automate gitea-github-sync execution 58 | 59 | There are multiple ways to automate the execution of gitea-github-sync. One of them is using cron: 60 | 61 | First, execute `crontab -e` to open the cron configuration file in edit mode. 62 | 63 | Then add the following line: 64 | ``` 65 | 0 12 * * * gitea-github-sync sync 66 | ``` 67 | 68 | This will execute the sync operation every day at twelve. 69 | 70 | ## Limitations 71 | 72 | When using the migration feature of Gitea, a Github token must be passed for Gitea to continuously pull the new changes from Github. 73 | 74 | The token used by gitea-github-sync to list repositories is the same that is used by Gitea for continuous monitoring. Updating the value of this token is unfortunately not possible through the API as of now. 75 | -------------------------------------------------------------------------------- /docker/config.yml: -------------------------------------------------------------------------------- 1 | gitea_api_url: ${GITEA_API_URL} 2 | gitea_token: ${GITEA_TOKEN} 3 | github_token: ${GITHUB_TOKEN} 4 | -------------------------------------------------------------------------------- /docs/docker/README.md: -------------------------------------------------------------------------------- 1 | # gitea-github-sync docker image 2 | 3 | gitea-github-sync is automatically packaged in a docker image and can be pulled with the following command: 4 | 5 | `docker pull muscaw/gitea-github-sync:latest` 6 | 7 | ## Setup 8 | 9 | ### Setup using environment variables 10 | 11 | The docker image contains a configuration file that uses environment variables to configure gitea-github-sync. 12 | 13 | You can create a file called _.env_ with the following content: 14 | 15 | ``` 16 | GITHUB_TOKEN= 17 | GITEA_API_URL=https:///api/v1 18 | GITEA_TOKEN= 19 | ``` 20 | 21 | Run the docker image with the env file: 22 | 23 | `docker run --rm --env-file .env muscaw/gitea-github-sync:latest sync` 24 | 25 | ### Mount a configuration file 26 | 27 | Create the config.yml file wherever you want and mount it in the docker container: 28 | 29 | `docker run --rm -v :/home/python-user/.config/gitea-github-sync/config.yml muscaw/gitea-github-sync:latest sync` 30 | -------------------------------------------------------------------------------- /docs/readme/github_token_permission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muscaw/gitea-github-sync/8ce1bd265f13ca88eeca20246aee1f5ebfc67163/docs/readme/github_token_permission.png -------------------------------------------------------------------------------- /gitea_github_sync/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muscaw/gitea-github-sync/8ce1bd265f13ca88eeca20246aee1f5ebfc67163/gitea_github_sync/__init__.py -------------------------------------------------------------------------------- /gitea_github_sync/__main__.py: -------------------------------------------------------------------------------- 1 | from . import cli 2 | 3 | cli.cli() 4 | -------------------------------------------------------------------------------- /gitea_github_sync/cli.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import click 4 | from rich import print 5 | 6 | from . import config, gitea, github, migration, repository 7 | 8 | 9 | @click.group() 10 | def cli() -> None: 11 | pass 12 | 13 | 14 | def print_repositories(repos: List[repository.Repository], display_stats: bool) -> None: 15 | for repo in repos: 16 | print(f"[b]{repo.get_org_name()}[/]/{repo.get_repo_name()}") 17 | 18 | if display_stats: 19 | print() 20 | print("[b]Repository stats[/]") 21 | number_public_repos = sum( 22 | 1 if repo.visibility == repository.Visibility.PUBLIC else 0 for repo in repos 23 | ) 24 | number_private_repos = sum( 25 | 1 if repo.visibility == repository.Visibility.PRIVATE else 0 for repo in repos 26 | ) 27 | number_unknown_repos = len(repos) - number_public_repos - number_private_repos 28 | print(f"Number of public repos identified: [b red]{number_public_repos}[/]") 29 | print(f"Number of private repos identified: [b red]{number_private_repos}[/]") 30 | print(f"Number of unknown repos identified: [b red]{number_unknown_repos}[/]") 31 | print(f"Total number of repos identified: [b red]{len(repos)}[/]") 32 | 33 | 34 | @click.option("--stats", is_flag=True) 35 | @cli.command() 36 | def list_all_github_repositories(stats: bool) -> None: 37 | gh = github.get_github() 38 | repos = github.list_all_repositories(gh) 39 | print_repositories(repos, stats) 40 | 41 | 42 | @click.option("--stats", is_flag=True) 43 | @cli.command() 44 | def list_all_gitea_repositories(stats: bool) -> None: 45 | gt = gitea.get_gitea() 46 | repos = gt.get_repos() 47 | print_repositories(repos, stats) 48 | 49 | 50 | @cli.command() 51 | @click.argument("full_repo_name") 52 | def migrate_repo(full_repo_name: str) -> None: 53 | conf = config.load_config() 54 | gt = gitea.get_gitea() 55 | gh = github.get_github() 56 | github_repos = github.list_all_repositories(gh) 57 | try: 58 | repo = next((repo for repo in github_repos if repo.full_repo_name == full_repo_name)) 59 | except StopIteration: 60 | print(f"[b red]Repository {full_repo_name} does not exist on Github[/]") 61 | raise click.Abort() 62 | 63 | try: 64 | gt.migrate_repo(repo=repo, github_token=conf.github_token) 65 | except gitea.GiteaMigrationError as e: 66 | print(f"[red]Migration Error for [b]{e.full_repo_name}[/]") 67 | 68 | 69 | @cli.command() 70 | def sync() -> None: 71 | conf = config.load_config() 72 | gt = gitea.get_gitea() 73 | gh = github.get_github() 74 | github_repos = github.list_all_repositories(gh) 75 | gitea_repos = gt.get_repos() 76 | repos_to_sync = migration.list_missing_github_repos( 77 | gh_repos=github_repos, gitea_repos=gitea_repos 78 | ) 79 | len_repos = len(repos_to_sync) 80 | print(f"Starting migration for {len_repos} repos") 81 | for repo in repos_to_sync: 82 | print(f"Migrating [b]{repo.full_repo_name}[/]") 83 | try: 84 | gt.migrate_repo(repo=repo, github_token=conf.github_token) 85 | except gitea.GiteaMigrationError as e: 86 | print(f"[red]Migration Error for [b]{e.full_repo_name}[/]") 87 | len_repos -= 1 88 | if len_repos == 0: 89 | print("No repos were migrated") 90 | else: 91 | print(f"Migrated {len_repos} out of {len(repos_to_sync)} repos successfully") 92 | if len_repos < len(repos_to_sync): 93 | print(f"Failed {len(repos_to_sync) - len_repos} out of {len(repos_to_sync)} migrations") 94 | -------------------------------------------------------------------------------- /gitea_github_sync/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | from piny import PydanticValidator, StrictMatcher, YamlLoader 6 | from pydantic import BaseModel 7 | 8 | 9 | class Config(BaseModel): 10 | github_token: str 11 | gitea_api_url: str 12 | gitea_token: str 13 | 14 | 15 | def config_file_location() -> Path: 16 | return Path.home() / ".config" / "gitea-github-sync" / "config.yml" 17 | 18 | 19 | def load_config(config_location: Path = config_file_location()) -> Config: 20 | return Config( 21 | **YamlLoader( 22 | path=config_location, 23 | matcher=StrictMatcher, 24 | validator=PydanticValidator, 25 | schema=Config, 26 | ).load() 27 | ) 28 | -------------------------------------------------------------------------------- /gitea_github_sync/gitea.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Any, Dict, List, Optional 5 | 6 | import requests 7 | 8 | from gitea_github_sync import config 9 | 10 | from .repository import Repository, Visibility 11 | 12 | 13 | @dataclass(frozen=True) 14 | class GiteaMigrationError(ValueError): 15 | full_repo_name: str 16 | 17 | def __str__(self) -> str: 18 | return f"Could not migrate {self.full_repo_name}" 19 | 20 | 21 | @dataclass(frozen=True) 22 | class Gitea: 23 | api_url: str 24 | api_token: str 25 | 26 | def _get_authorization_header(self) -> Dict[str, str]: 27 | return {"Authorization": f"token {self.api_token}"} 28 | 29 | def _get_all_pages(self, path: str) -> List[Dict[str, Any]]: 30 | output = [] 31 | url: Optional[str] = f"{self.api_url}{path}" 32 | while url is not None: 33 | auth = self._get_authorization_header() 34 | result = requests.get(url, headers=auth) 35 | result.raise_for_status() 36 | data = result.json() 37 | output.extend(data) 38 | 39 | url = result.links["next"]["url"] if "next" in result.links else None 40 | return output 41 | 42 | def get_repos(self) -> List[Repository]: 43 | repos = self._get_all_pages("/user/repos") 44 | return [ 45 | Repository( 46 | repo["full_name"], 47 | visibility=Visibility.PRIVATE if repo["private"] else Visibility.PUBLIC, 48 | ) 49 | for repo in repos 50 | ] 51 | 52 | def migrate_repo(self, repo: Repository, github_token: str) -> None: 53 | request_data = { 54 | "auth_token": github_token, 55 | "clone_addr": f"https://github.com/{repo.full_repo_name}", 56 | "repo_name": repo.get_repo_name(), 57 | "service": "github", 58 | "mirror": True, 59 | "private": repo.visibility == Visibility.PRIVATE, 60 | } 61 | res = requests.post( 62 | f"{self.api_url}/repos/migrate", 63 | headers=self._get_authorization_header(), 64 | json=request_data, 65 | ) 66 | try: 67 | res.raise_for_status() 68 | except requests.HTTPError as e: 69 | raise GiteaMigrationError(repo.full_repo_name) from e 70 | 71 | 72 | def get_gitea(conf: Optional[config.Config] = None) -> Gitea: 73 | if conf is None: 74 | conf = config.load_config() 75 | return Gitea(api_url=conf.gitea_api_url, api_token=conf.gitea_token) 76 | -------------------------------------------------------------------------------- /gitea_github_sync/github.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import List, Optional 4 | 5 | from github import Github 6 | 7 | from . import config 8 | from .repository import Repository, Visibility 9 | 10 | 11 | def get_github(conf: Optional[config.Config] = None) -> Github: 12 | if conf is None: 13 | conf = config.load_config() 14 | return Github(login_or_token=conf.github_token) 15 | 16 | 17 | def list_all_repositories(gh: Github) -> List[Repository]: 18 | repos = gh.get_user().get_repos() 19 | return [ 20 | Repository( 21 | full_repo_name=repo.full_name, 22 | visibility=Visibility.from_str(repo.visibility), 23 | ) 24 | for repo in repos 25 | ] 26 | -------------------------------------------------------------------------------- /gitea_github_sync/migration.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from gitea_github_sync.repository import Repository 4 | 5 | 6 | def list_missing_github_repos( 7 | gh_repos: List[Repository], gitea_repos: List[Repository] 8 | ) -> List[Repository]: 9 | gitea_repos_by_name = [repo.get_repo_name() for repo in gitea_repos] 10 | return [repo for repo in gh_repos if repo.get_repo_name() not in gitea_repos_by_name] 11 | -------------------------------------------------------------------------------- /gitea_github_sync/repository.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from enum import Flag, auto 5 | 6 | 7 | class Visibility(Flag): 8 | PUBLIC = auto() 9 | PRIVATE = auto() 10 | UNKNOWN = auto() 11 | 12 | @staticmethod 13 | def from_str(value: str) -> Visibility: 14 | if value == "public": 15 | return Visibility.PUBLIC 16 | elif value == "private": 17 | return Visibility.PRIVATE 18 | else: 19 | return Visibility.UNKNOWN 20 | 21 | 22 | @dataclass(frozen=True) 23 | class Repository: 24 | full_repo_name: str 25 | visibility: Visibility 26 | 27 | def get_org_name(self) -> str: 28 | return self.full_repo_name.split("/")[0] 29 | 30 | def get_repo_name(self) -> str: 31 | return self.full_repo_name.split("/")[1] 32 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = 3.10 3 | strict = True 4 | 5 | [mypy-piny.*] 6 | ignore_missing_imports = True -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.7.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 11 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 12 | ] 13 | 14 | [package.dependencies] 15 | typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} 16 | 17 | [[package]] 18 | name = "black" 19 | version = "24.4.2" 20 | description = "The uncompromising code formatter." 21 | optional = false 22 | python-versions = ">=3.8" 23 | files = [ 24 | {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, 25 | {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, 26 | {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, 27 | {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, 28 | {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, 29 | {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, 30 | {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, 31 | {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, 32 | {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, 33 | {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, 34 | {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, 35 | {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, 36 | {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, 37 | {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, 38 | {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, 39 | {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, 40 | {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, 41 | {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, 42 | {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, 43 | {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, 44 | {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, 45 | {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, 46 | ] 47 | 48 | [package.dependencies] 49 | click = ">=8.0.0" 50 | mypy-extensions = ">=0.4.3" 51 | packaging = ">=22.0" 52 | pathspec = ">=0.9.0" 53 | platformdirs = ">=2" 54 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 55 | typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} 56 | 57 | [package.extras] 58 | colorama = ["colorama (>=0.4.3)"] 59 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 60 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 61 | uvloop = ["uvloop (>=0.15.2)"] 62 | 63 | [[package]] 64 | name = "cachetools" 65 | version = "5.3.3" 66 | description = "Extensible memoizing collections and decorators" 67 | optional = false 68 | python-versions = ">=3.7" 69 | files = [ 70 | {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, 71 | {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, 72 | ] 73 | 74 | [[package]] 75 | name = "certifi" 76 | version = "2024.6.2" 77 | description = "Python package for providing Mozilla's CA Bundle." 78 | optional = false 79 | python-versions = ">=3.6" 80 | files = [ 81 | {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, 82 | {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, 83 | ] 84 | 85 | [[package]] 86 | name = "cffi" 87 | version = "1.16.0" 88 | description = "Foreign Function Interface for Python calling C code." 89 | optional = false 90 | python-versions = ">=3.8" 91 | files = [ 92 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 93 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 94 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 95 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 96 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 97 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 98 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 99 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 100 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 101 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 102 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 103 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 104 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 105 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 106 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 107 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 108 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 109 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 110 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 111 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 112 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 113 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 114 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 115 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 116 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 117 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 118 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 119 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 120 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 121 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 122 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 123 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 124 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 125 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 126 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 127 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 128 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 129 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 130 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 131 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 132 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 133 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 134 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 135 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 136 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 137 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 138 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 139 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 140 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 141 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 142 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 143 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 144 | ] 145 | 146 | [package.dependencies] 147 | pycparser = "*" 148 | 149 | [[package]] 150 | name = "chardet" 151 | version = "5.2.0" 152 | description = "Universal encoding detector for Python 3" 153 | optional = false 154 | python-versions = ">=3.7" 155 | files = [ 156 | {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, 157 | {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, 158 | ] 159 | 160 | [[package]] 161 | name = "charset-normalizer" 162 | version = "3.3.2" 163 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 164 | optional = false 165 | python-versions = ">=3.7.0" 166 | files = [ 167 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 168 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 169 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 170 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 171 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 172 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 173 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 174 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 175 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 176 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 177 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 178 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 179 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 180 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 181 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 182 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 183 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 184 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 185 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 186 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 187 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 188 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 189 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 190 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 191 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 192 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 193 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 194 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 195 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 196 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 197 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 198 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 199 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 200 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 201 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 202 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 203 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 204 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 205 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 206 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 207 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 208 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 209 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 210 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 211 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 212 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 213 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 214 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 215 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 216 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 217 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 218 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 219 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 220 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 221 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 222 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 223 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 224 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 225 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 226 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 227 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 228 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 229 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 230 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 231 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 232 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 233 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 234 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 235 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 236 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 237 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 238 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 239 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 240 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 241 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 242 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 243 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 244 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 245 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 246 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 247 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 248 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 249 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 250 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 251 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 252 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 253 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 254 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 255 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 256 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 257 | ] 258 | 259 | [[package]] 260 | name = "click" 261 | version = "8.1.7" 262 | description = "Composable command line interface toolkit" 263 | optional = false 264 | python-versions = ">=3.7" 265 | files = [ 266 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 267 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 268 | ] 269 | 270 | [package.dependencies] 271 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 272 | 273 | [[package]] 274 | name = "colorama" 275 | version = "0.4.6" 276 | description = "Cross-platform colored terminal text." 277 | optional = false 278 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 279 | files = [ 280 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 281 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 282 | ] 283 | 284 | [[package]] 285 | name = "coverage" 286 | version = "7.5.3" 287 | description = "Code coverage measurement for Python" 288 | optional = false 289 | python-versions = ">=3.8" 290 | files = [ 291 | {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, 292 | {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, 293 | {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, 294 | {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, 295 | {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, 296 | {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, 297 | {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, 298 | {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, 299 | {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, 300 | {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, 301 | {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, 302 | {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, 303 | {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, 304 | {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, 305 | {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, 306 | {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, 307 | {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, 308 | {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, 309 | {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, 310 | {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, 311 | {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, 312 | {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, 313 | {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, 314 | {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, 315 | {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, 316 | {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, 317 | {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, 318 | {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, 319 | {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, 320 | {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, 321 | {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, 322 | {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, 323 | {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, 324 | {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, 325 | {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, 326 | {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, 327 | {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, 328 | {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, 329 | {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, 330 | {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, 331 | {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, 332 | {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, 333 | {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, 334 | {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, 335 | {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, 336 | {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, 337 | {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, 338 | {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, 339 | {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, 340 | {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, 341 | {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, 342 | {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, 343 | ] 344 | 345 | [package.dependencies] 346 | tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} 347 | 348 | [package.extras] 349 | toml = ["tomli"] 350 | 351 | [[package]] 352 | name = "cryptography" 353 | version = "42.0.8" 354 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 355 | optional = false 356 | python-versions = ">=3.7" 357 | files = [ 358 | {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, 359 | {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, 360 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, 361 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, 362 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, 363 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, 364 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, 365 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, 366 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, 367 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, 368 | {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, 369 | {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, 370 | {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, 371 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, 372 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, 373 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, 374 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, 375 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, 376 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, 377 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, 378 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, 379 | {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, 380 | {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, 381 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, 382 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, 383 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, 384 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, 385 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, 386 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, 387 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, 388 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, 389 | {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, 390 | ] 391 | 392 | [package.dependencies] 393 | cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} 394 | 395 | [package.extras] 396 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 397 | docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] 398 | nox = ["nox"] 399 | pep8test = ["check-sdist", "click", "mypy", "ruff"] 400 | sdist = ["build"] 401 | ssh = ["bcrypt (>=3.1.5)"] 402 | test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] 403 | test-randomorder = ["pytest-randomly"] 404 | 405 | [[package]] 406 | name = "deprecated" 407 | version = "1.2.14" 408 | description = "Python @deprecated decorator to deprecate old python classes, functions or methods." 409 | optional = false 410 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 411 | files = [ 412 | {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, 413 | {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, 414 | ] 415 | 416 | [package.dependencies] 417 | wrapt = ">=1.10,<2" 418 | 419 | [package.extras] 420 | dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] 421 | 422 | [[package]] 423 | name = "distlib" 424 | version = "0.3.8" 425 | description = "Distribution utilities" 426 | optional = false 427 | python-versions = "*" 428 | files = [ 429 | {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, 430 | {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, 431 | ] 432 | 433 | [[package]] 434 | name = "exceptiongroup" 435 | version = "1.2.1" 436 | description = "Backport of PEP 654 (exception groups)" 437 | optional = false 438 | python-versions = ">=3.7" 439 | files = [ 440 | {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, 441 | {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, 442 | ] 443 | 444 | [package.extras] 445 | test = ["pytest (>=6)"] 446 | 447 | [[package]] 448 | name = "filelock" 449 | version = "3.14.0" 450 | description = "A platform independent file lock." 451 | optional = false 452 | python-versions = ">=3.8" 453 | files = [ 454 | {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, 455 | {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, 456 | ] 457 | 458 | [package.extras] 459 | docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 460 | testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] 461 | typing = ["typing-extensions (>=4.8)"] 462 | 463 | [[package]] 464 | name = "flake8" 465 | version = "7.1.0" 466 | description = "the modular source code checker: pep8 pyflakes and co" 467 | optional = false 468 | python-versions = ">=3.8.1" 469 | files = [ 470 | {file = "flake8-7.1.0-py2.py3-none-any.whl", hash = "sha256:2e416edcc62471a64cea09353f4e7bdba32aeb079b6e360554c659a122b1bc6a"}, 471 | {file = "flake8-7.1.0.tar.gz", hash = "sha256:48a07b626b55236e0fb4784ee69a465fbf59d79eec1f5b4785c3d3bc57d17aa5"}, 472 | ] 473 | 474 | [package.dependencies] 475 | mccabe = ">=0.7.0,<0.8.0" 476 | pycodestyle = ">=2.12.0,<2.13.0" 477 | pyflakes = ">=3.2.0,<3.3.0" 478 | 479 | [[package]] 480 | name = "idna" 481 | version = "3.7" 482 | description = "Internationalized Domain Names in Applications (IDNA)" 483 | optional = false 484 | python-versions = ">=3.5" 485 | files = [ 486 | {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, 487 | {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, 488 | ] 489 | 490 | [[package]] 491 | name = "iniconfig" 492 | version = "2.0.0" 493 | description = "brain-dead simple config-ini parsing" 494 | optional = false 495 | python-versions = ">=3.7" 496 | files = [ 497 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 498 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 499 | ] 500 | 501 | [[package]] 502 | name = "isort" 503 | version = "5.13.2" 504 | description = "A Python utility / library to sort Python imports." 505 | optional = false 506 | python-versions = ">=3.8.0" 507 | files = [ 508 | {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, 509 | {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, 510 | ] 511 | 512 | [package.extras] 513 | colors = ["colorama (>=0.4.6)"] 514 | 515 | [[package]] 516 | name = "markdown-it-py" 517 | version = "3.0.0" 518 | description = "Python port of markdown-it. Markdown parsing, done right!" 519 | optional = false 520 | python-versions = ">=3.8" 521 | files = [ 522 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 523 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 524 | ] 525 | 526 | [package.dependencies] 527 | mdurl = ">=0.1,<1.0" 528 | 529 | [package.extras] 530 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 531 | code-style = ["pre-commit (>=3.0,<4.0)"] 532 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 533 | linkify = ["linkify-it-py (>=1,<3)"] 534 | plugins = ["mdit-py-plugins"] 535 | profiling = ["gprof2dot"] 536 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 537 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 538 | 539 | [[package]] 540 | name = "mccabe" 541 | version = "0.7.0" 542 | description = "McCabe checker, plugin for flake8" 543 | optional = false 544 | python-versions = ">=3.6" 545 | files = [ 546 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 547 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 548 | ] 549 | 550 | [[package]] 551 | name = "mdurl" 552 | version = "0.1.2" 553 | description = "Markdown URL utilities" 554 | optional = false 555 | python-versions = ">=3.7" 556 | files = [ 557 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 558 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 559 | ] 560 | 561 | [[package]] 562 | name = "mypy" 563 | version = "1.10.1" 564 | description = "Optional static typing for Python" 565 | optional = false 566 | python-versions = ">=3.8" 567 | files = [ 568 | {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, 569 | {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, 570 | {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, 571 | {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, 572 | {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, 573 | {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, 574 | {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, 575 | {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, 576 | {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, 577 | {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, 578 | {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, 579 | {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, 580 | {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, 581 | {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, 582 | {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, 583 | {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, 584 | {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, 585 | {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, 586 | {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, 587 | {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, 588 | {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, 589 | {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, 590 | {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, 591 | {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, 592 | {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, 593 | {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, 594 | {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, 595 | ] 596 | 597 | [package.dependencies] 598 | mypy-extensions = ">=1.0.0" 599 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 600 | typing-extensions = ">=4.1.0" 601 | 602 | [package.extras] 603 | dmypy = ["psutil (>=4.0)"] 604 | install-types = ["pip"] 605 | mypyc = ["setuptools (>=50)"] 606 | reports = ["lxml"] 607 | 608 | [[package]] 609 | name = "mypy-extensions" 610 | version = "1.0.0" 611 | description = "Type system extensions for programs checked with the mypy type checker." 612 | optional = false 613 | python-versions = ">=3.5" 614 | files = [ 615 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 616 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 617 | ] 618 | 619 | [[package]] 620 | name = "packaging" 621 | version = "24.1" 622 | description = "Core utilities for Python packages" 623 | optional = false 624 | python-versions = ">=3.8" 625 | files = [ 626 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 627 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 628 | ] 629 | 630 | [[package]] 631 | name = "pathspec" 632 | version = "0.12.1" 633 | description = "Utility library for gitignore style pattern matching of file paths." 634 | optional = false 635 | python-versions = ">=3.8" 636 | files = [ 637 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 638 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 639 | ] 640 | 641 | [[package]] 642 | name = "piny" 643 | version = "1.1.0" 644 | description = "Load YAML configs with environment variables interpolation" 645 | optional = false 646 | python-versions = ">=3.7" 647 | files = [ 648 | {file = "piny-1.1.0-py3-none-any.whl", hash = "sha256:adc6873192dead13118e588901348cbb94c8163b128a34ed277694a875e05993"}, 649 | {file = "piny-1.1.0.tar.gz", hash = "sha256:18a005aeccb2af6f55790e539698865be4ab546661c27f410ec6871afe10118a"}, 650 | ] 651 | 652 | [package.dependencies] 653 | Click = ">=8,<9" 654 | PyYAML = ">=6,<7" 655 | 656 | [package.extras] 657 | marshmallow = ["marshmallow (>=3)"] 658 | pydantic = ["pydantic (>=0.28)"] 659 | trafaret = ["trafaret (>=1.2.0)"] 660 | 661 | [[package]] 662 | name = "platformdirs" 663 | version = "4.2.2" 664 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 665 | optional = false 666 | python-versions = ">=3.8" 667 | files = [ 668 | {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, 669 | {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, 670 | ] 671 | 672 | [package.extras] 673 | docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] 674 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] 675 | type = ["mypy (>=1.8)"] 676 | 677 | [[package]] 678 | name = "pluggy" 679 | version = "1.5.0" 680 | description = "plugin and hook calling mechanisms for python" 681 | optional = false 682 | python-versions = ">=3.8" 683 | files = [ 684 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 685 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 686 | ] 687 | 688 | [package.extras] 689 | dev = ["pre-commit", "tox"] 690 | testing = ["pytest", "pytest-benchmark"] 691 | 692 | [[package]] 693 | name = "pycodestyle" 694 | version = "2.12.0" 695 | description = "Python style guide checker" 696 | optional = false 697 | python-versions = ">=3.8" 698 | files = [ 699 | {file = "pycodestyle-2.12.0-py2.py3-none-any.whl", hash = "sha256:949a39f6b86c3e1515ba1787c2022131d165a8ad271b11370a8819aa070269e4"}, 700 | {file = "pycodestyle-2.12.0.tar.gz", hash = "sha256:442f950141b4f43df752dd303511ffded3a04c2b6fb7f65980574f0c31e6e79c"}, 701 | ] 702 | 703 | [[package]] 704 | name = "pycparser" 705 | version = "2.22" 706 | description = "C parser in Python" 707 | optional = false 708 | python-versions = ">=3.8" 709 | files = [ 710 | {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, 711 | {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, 712 | ] 713 | 714 | [[package]] 715 | name = "pydantic" 716 | version = "2.7.4" 717 | description = "Data validation using Python type hints" 718 | optional = false 719 | python-versions = ">=3.8" 720 | files = [ 721 | {file = "pydantic-2.7.4-py3-none-any.whl", hash = "sha256:ee8538d41ccb9c0a9ad3e0e5f07bf15ed8015b481ced539a1759d8cc89ae90d0"}, 722 | {file = "pydantic-2.7.4.tar.gz", hash = "sha256:0c84efd9548d545f63ac0060c1e4d39bb9b14db8b3c0652338aecc07b5adec52"}, 723 | ] 724 | 725 | [package.dependencies] 726 | annotated-types = ">=0.4.0" 727 | pydantic-core = "2.18.4" 728 | typing-extensions = ">=4.6.1" 729 | 730 | [package.extras] 731 | email = ["email-validator (>=2.0.0)"] 732 | 733 | [[package]] 734 | name = "pydantic-core" 735 | version = "2.18.4" 736 | description = "Core functionality for Pydantic validation and serialization" 737 | optional = false 738 | python-versions = ">=3.8" 739 | files = [ 740 | {file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"}, 741 | {file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"}, 742 | {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"}, 743 | {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"}, 744 | {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"}, 745 | {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"}, 746 | {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"}, 747 | {file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"}, 748 | {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"}, 749 | {file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"}, 750 | {file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"}, 751 | {file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"}, 752 | {file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"}, 753 | {file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"}, 754 | {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"}, 755 | {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"}, 756 | {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"}, 757 | {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"}, 758 | {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"}, 759 | {file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"}, 760 | {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"}, 761 | {file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"}, 762 | {file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"}, 763 | {file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"}, 764 | {file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"}, 765 | {file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"}, 766 | {file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"}, 767 | {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"}, 768 | {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"}, 769 | {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"}, 770 | {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"}, 771 | {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"}, 772 | {file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"}, 773 | {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"}, 774 | {file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"}, 775 | {file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"}, 776 | {file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"}, 777 | {file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"}, 778 | {file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"}, 779 | {file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"}, 780 | {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"}, 781 | {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"}, 782 | {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"}, 783 | {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"}, 784 | {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"}, 785 | {file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"}, 786 | {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"}, 787 | {file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"}, 788 | {file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"}, 789 | {file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"}, 790 | {file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"}, 791 | {file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"}, 792 | {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"}, 793 | {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"}, 794 | {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"}, 795 | {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"}, 796 | {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"}, 797 | {file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"}, 798 | {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"}, 799 | {file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"}, 800 | {file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"}, 801 | {file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"}, 802 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"}, 803 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"}, 804 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"}, 805 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"}, 806 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"}, 807 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"}, 808 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"}, 809 | {file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"}, 810 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"}, 811 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"}, 812 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"}, 813 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"}, 814 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"}, 815 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"}, 816 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"}, 817 | {file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"}, 818 | {file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"}, 819 | ] 820 | 821 | [package.dependencies] 822 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 823 | 824 | [[package]] 825 | name = "pyflakes" 826 | version = "3.2.0" 827 | description = "passive checker of Python programs" 828 | optional = false 829 | python-versions = ">=3.8" 830 | files = [ 831 | {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, 832 | {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, 833 | ] 834 | 835 | [[package]] 836 | name = "pygithub" 837 | version = "2.3.0" 838 | description = "Use the full Github API v3" 839 | optional = false 840 | python-versions = ">=3.7" 841 | files = [ 842 | {file = "PyGithub-2.3.0-py3-none-any.whl", hash = "sha256:65b499728be3ce7b0cd2cd760da3b32f0f4d7bc55e5e0677617f90f6564e793e"}, 843 | {file = "PyGithub-2.3.0.tar.gz", hash = "sha256:0148d7347a1cdeed99af905077010aef81a4dad988b0ba51d4108bf66b443f7e"}, 844 | ] 845 | 846 | [package.dependencies] 847 | Deprecated = "*" 848 | pyjwt = {version = ">=2.4.0", extras = ["crypto"]} 849 | pynacl = ">=1.4.0" 850 | requests = ">=2.14.0" 851 | typing-extensions = ">=4.0.0" 852 | urllib3 = ">=1.26.0" 853 | 854 | [[package]] 855 | name = "pygments" 856 | version = "2.18.0" 857 | description = "Pygments is a syntax highlighting package written in Python." 858 | optional = false 859 | python-versions = ">=3.8" 860 | files = [ 861 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 862 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 863 | ] 864 | 865 | [package.extras] 866 | windows-terminal = ["colorama (>=0.4.6)"] 867 | 868 | [[package]] 869 | name = "pyjwt" 870 | version = "2.8.0" 871 | description = "JSON Web Token implementation in Python" 872 | optional = false 873 | python-versions = ">=3.7" 874 | files = [ 875 | {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, 876 | {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, 877 | ] 878 | 879 | [package.dependencies] 880 | cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} 881 | 882 | [package.extras] 883 | crypto = ["cryptography (>=3.4.0)"] 884 | dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] 885 | docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] 886 | tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] 887 | 888 | [[package]] 889 | name = "pynacl" 890 | version = "1.5.0" 891 | description = "Python binding to the Networking and Cryptography (NaCl) library" 892 | optional = false 893 | python-versions = ">=3.6" 894 | files = [ 895 | {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, 896 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, 897 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, 898 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, 899 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, 900 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, 901 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, 902 | {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, 903 | {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, 904 | {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, 905 | ] 906 | 907 | [package.dependencies] 908 | cffi = ">=1.4.1" 909 | 910 | [package.extras] 911 | docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] 912 | tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] 913 | 914 | [[package]] 915 | name = "pyproject-api" 916 | version = "1.6.1" 917 | description = "API to interact with the python pyproject.toml based projects" 918 | optional = false 919 | python-versions = ">=3.8" 920 | files = [ 921 | {file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"}, 922 | {file = "pyproject_api-1.6.1.tar.gz", hash = "sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538"}, 923 | ] 924 | 925 | [package.dependencies] 926 | packaging = ">=23.1" 927 | tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} 928 | 929 | [package.extras] 930 | docs = ["furo (>=2023.8.19)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"] 931 | testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68.1.2)", "wheel (>=0.41.2)"] 932 | 933 | [[package]] 934 | name = "pytest" 935 | version = "8.2.2" 936 | description = "pytest: simple powerful testing with Python" 937 | optional = false 938 | python-versions = ">=3.8" 939 | files = [ 940 | {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, 941 | {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, 942 | ] 943 | 944 | [package.dependencies] 945 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 946 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 947 | iniconfig = "*" 948 | packaging = "*" 949 | pluggy = ">=1.5,<2.0" 950 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 951 | 952 | [package.extras] 953 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 954 | 955 | [[package]] 956 | name = "pytest-cov" 957 | version = "4.1.0" 958 | description = "Pytest plugin for measuring coverage." 959 | optional = false 960 | python-versions = ">=3.7" 961 | files = [ 962 | {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, 963 | {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, 964 | ] 965 | 966 | [package.dependencies] 967 | coverage = {version = ">=5.2.1", extras = ["toml"]} 968 | pytest = ">=4.6" 969 | 970 | [package.extras] 971 | testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] 972 | 973 | [[package]] 974 | name = "pyyaml" 975 | version = "6.0.1" 976 | description = "YAML parser and emitter for Python" 977 | optional = false 978 | python-versions = ">=3.6" 979 | files = [ 980 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, 981 | {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, 982 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, 983 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, 984 | {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, 985 | {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, 986 | {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, 987 | {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, 988 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, 989 | {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, 990 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, 991 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, 992 | {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, 993 | {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, 994 | {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, 995 | {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, 996 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, 997 | {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, 998 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, 999 | {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, 1000 | {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, 1001 | {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, 1002 | {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, 1003 | {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, 1004 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, 1005 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, 1006 | {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, 1007 | {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, 1008 | {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, 1009 | {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, 1010 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, 1011 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, 1012 | {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, 1013 | {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, 1014 | {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, 1015 | {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, 1016 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, 1017 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, 1018 | {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, 1019 | {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, 1020 | {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, 1021 | {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, 1022 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, 1023 | {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, 1024 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, 1025 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, 1026 | {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, 1027 | {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, 1028 | {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, 1029 | {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, 1030 | {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "requests" 1035 | version = "2.32.3" 1036 | description = "Python HTTP for Humans." 1037 | optional = false 1038 | python-versions = ">=3.8" 1039 | files = [ 1040 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 1041 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 1042 | ] 1043 | 1044 | [package.dependencies] 1045 | certifi = ">=2017.4.17" 1046 | charset-normalizer = ">=2,<4" 1047 | idna = ">=2.5,<4" 1048 | urllib3 = ">=1.21.1,<3" 1049 | 1050 | [package.extras] 1051 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1052 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1053 | 1054 | [[package]] 1055 | name = "responses" 1056 | version = "0.25.3" 1057 | description = "A utility library for mocking out the `requests` Python library." 1058 | optional = false 1059 | python-versions = ">=3.8" 1060 | files = [ 1061 | {file = "responses-0.25.3-py3-none-any.whl", hash = "sha256:521efcbc82081ab8daa588e08f7e8a64ce79b91c39f6e62199b19159bea7dbcb"}, 1062 | {file = "responses-0.25.3.tar.gz", hash = "sha256:617b9247abd9ae28313d57a75880422d55ec63c29d33d629697590a034358dba"}, 1063 | ] 1064 | 1065 | [package.dependencies] 1066 | pyyaml = "*" 1067 | requests = ">=2.30.0,<3.0" 1068 | urllib3 = ">=1.25.10,<3.0" 1069 | 1070 | [package.extras] 1071 | tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"] 1072 | 1073 | [[package]] 1074 | name = "rich" 1075 | version = "13.7.1" 1076 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 1077 | optional = false 1078 | python-versions = ">=3.7.0" 1079 | files = [ 1080 | {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, 1081 | {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, 1082 | ] 1083 | 1084 | [package.dependencies] 1085 | markdown-it-py = ">=2.2.0" 1086 | pygments = ">=2.13.0,<3.0.0" 1087 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} 1088 | 1089 | [package.extras] 1090 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 1091 | 1092 | [[package]] 1093 | name = "tomli" 1094 | version = "2.0.1" 1095 | description = "A lil' TOML parser" 1096 | optional = false 1097 | python-versions = ">=3.7" 1098 | files = [ 1099 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1100 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1101 | ] 1102 | 1103 | [[package]] 1104 | name = "tox" 1105 | version = "4.15.1" 1106 | description = "tox is a generic virtualenv management and test command line tool" 1107 | optional = false 1108 | python-versions = ">=3.8" 1109 | files = [ 1110 | {file = "tox-4.15.1-py3-none-any.whl", hash = "sha256:f00a5dc4222b358e69694e47e3da0227ac41253509bca9f45aa8f012053e8d9d"}, 1111 | {file = "tox-4.15.1.tar.gz", hash = "sha256:53a092527d65e873e39213ebd4bd027a64623320b6b0326136384213f95b7076"}, 1112 | ] 1113 | 1114 | [package.dependencies] 1115 | cachetools = ">=5.3.2" 1116 | chardet = ">=5.2" 1117 | colorama = ">=0.4.6" 1118 | filelock = ">=3.13.1" 1119 | packaging = ">=23.2" 1120 | platformdirs = ">=4.1" 1121 | pluggy = ">=1.3" 1122 | pyproject-api = ">=1.6.1" 1123 | tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} 1124 | virtualenv = ">=20.25" 1125 | 1126 | [package.extras] 1127 | docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.25.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] 1128 | testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=8.0.2)", "distlib (>=0.3.8)", "flaky (>=3.7)", "hatch-vcs (>=0.4)", "hatchling (>=1.21)", "psutil (>=5.9.7)", "pytest (>=7.4.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-xdist (>=3.5)", "re-assert (>=1.1)", "time-machine (>=2.13)", "wheel (>=0.42)"] 1129 | 1130 | [[package]] 1131 | name = "typing-extensions" 1132 | version = "4.12.2" 1133 | description = "Backported and Experimental Type Hints for Python 3.8+" 1134 | optional = false 1135 | python-versions = ">=3.8" 1136 | files = [ 1137 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 1138 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "urllib3" 1143 | version = "2.2.2" 1144 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1145 | optional = false 1146 | python-versions = ">=3.8" 1147 | files = [ 1148 | {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, 1149 | {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, 1150 | ] 1151 | 1152 | [package.extras] 1153 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1154 | h2 = ["h2 (>=4,<5)"] 1155 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1156 | zstd = ["zstandard (>=0.18.0)"] 1157 | 1158 | [[package]] 1159 | name = "virtualenv" 1160 | version = "20.26.2" 1161 | description = "Virtual Python Environment builder" 1162 | optional = false 1163 | python-versions = ">=3.7" 1164 | files = [ 1165 | {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, 1166 | {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, 1167 | ] 1168 | 1169 | [package.dependencies] 1170 | distlib = ">=0.3.7,<1" 1171 | filelock = ">=3.12.2,<4" 1172 | platformdirs = ">=3.9.1,<5" 1173 | 1174 | [package.extras] 1175 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] 1176 | test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] 1177 | 1178 | [[package]] 1179 | name = "wrapt" 1180 | version = "1.16.0" 1181 | description = "Module for decorators, wrappers and monkey patching." 1182 | optional = false 1183 | python-versions = ">=3.6" 1184 | files = [ 1185 | {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, 1186 | {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, 1187 | {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, 1188 | {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, 1189 | {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, 1190 | {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, 1191 | {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, 1192 | {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, 1193 | {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, 1194 | {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, 1195 | {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, 1196 | {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, 1197 | {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, 1198 | {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, 1199 | {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, 1200 | {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, 1201 | {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, 1202 | {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, 1203 | {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, 1204 | {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, 1205 | {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, 1206 | {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, 1207 | {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, 1208 | {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, 1209 | {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, 1210 | {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, 1211 | {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, 1212 | {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, 1213 | {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, 1214 | {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, 1215 | {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, 1216 | {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, 1217 | {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, 1218 | {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, 1219 | {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, 1220 | {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, 1221 | {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, 1222 | {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, 1223 | {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, 1224 | {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, 1225 | {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, 1226 | {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, 1227 | {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, 1228 | {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, 1229 | {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, 1230 | {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, 1231 | {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, 1232 | {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, 1233 | {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, 1234 | {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, 1235 | {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, 1236 | {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, 1237 | {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, 1238 | {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, 1239 | {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, 1240 | {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, 1241 | {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, 1242 | {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, 1243 | {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, 1244 | {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, 1245 | {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, 1246 | {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, 1247 | {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, 1248 | {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, 1249 | {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, 1250 | {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, 1251 | {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, 1252 | {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, 1253 | {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, 1254 | {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, 1255 | ] 1256 | 1257 | [metadata] 1258 | lock-version = "2.0" 1259 | python-versions = "^3.8.1" 1260 | content-hash = "e9529f7f6250d35a81265246862e52af9abf8f8b02acf82cfd8152df6ce572ea" 1261 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | profile = "black" 3 | 4 | [tool.black] 5 | line-length = 100 6 | 7 | [tool.poetry] 8 | name = "gitea-github-sync" 9 | version = "0.2.0" 10 | homepage = "https://github.com/Muscaw/gitea-github-sync" 11 | description = "Syncs your gitea instance with your Github account" 12 | authors = ["Kevin Grandjean "] 13 | readme = "README.md" 14 | license = "MIT" 15 | classifiers = [ 16 | "Development Status :: 5 - Production/Stable", 17 | "Environment :: Console", 18 | "Intended Audience :: Developers", 19 | "Intended Audience :: End Users/Desktop", 20 | "Intended Audience :: Information Technology", 21 | "Intended Audience :: System Administrators", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | "Programming Language :: Python :: 3.8", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Typing :: Typed", 29 | ] 30 | packages = [{include = "gitea_github_sync"}] 31 | 32 | [tool.poetry.scripts] 33 | gitea-github-sync = "gitea_github_sync.cli:cli" 34 | 35 | [tool.poetry.dependencies] 36 | python = "^3.8.1" 37 | pygithub = ">=1.57,<3.0" 38 | click = "^8.1.3" 39 | piny = ">=0.6,<1.2" 40 | pydantic = ">=1.10.4,<3.0.0" 41 | rich = "^13.0.0" 42 | requests = "^2.28.1" 43 | 44 | 45 | [tool.poetry.group.dev] 46 | optional = true 47 | 48 | [tool.poetry.group.dev.dependencies] 49 | pytest = ">=7.2,<9.0" 50 | black = ">=22.12,<25.0" 51 | flake8 = ">=6,<8" 52 | isort = "^5.11.4" 53 | mypy = ">=0.991,<1.11" 54 | tox = "^4.1.1" 55 | pytest-cov = "^4.0.0" 56 | responses = ">=0.22,<0.26" 57 | 58 | [build-system] 59 | requires = ["poetry-core"] 60 | build-backend = "poetry.core.masonry.api" 61 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Muscaw/gitea-github-sync/8ce1bd265f13ca88eeca20246aee1f5ebfc67163/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | from io import StringIO 3 | from typing import List 4 | from unittest.mock import MagicMock, PropertyMock, call, patch 5 | 6 | import pytest 7 | from click.testing import CliRunner 8 | 9 | from gitea_github_sync.cli import cli, print_repositories 10 | from gitea_github_sync.gitea import GiteaMigrationError 11 | from gitea_github_sync.repository import Repository, Visibility 12 | 13 | 14 | @pytest.fixture 15 | def repositories_fixture() -> List[Repository]: 16 | return [ 17 | Repository("some-team/a-repo", Visibility.PUBLIC), 18 | Repository("some-team/b-repo", Visibility.PRIVATE), 19 | Repository("some-team/c-repo", Visibility.UNKNOWN), 20 | ] 21 | 22 | 23 | @pytest.mark.parametrize("expected_stat", [True, False]) 24 | @patch("gitea_github_sync.cli.print_repositories", autospec=True) 25 | @patch("gitea_github_sync.cli.github.get_github", autospec=True) 26 | @patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True) 27 | def test_list_all_github_repositories( 28 | mock_list_all_repositories: MagicMock, 29 | mock_get_github: MagicMock, 30 | mock_print_repositories: MagicMock, 31 | expected_stat: bool, 32 | repositories_fixture: List[Repository], 33 | ) -> None: 34 | mock_github = MagicMock() 35 | mock_get_github.return_value = mock_github 36 | mock_list_all_repositories.return_value = repositories_fixture 37 | 38 | runner = CliRunner() 39 | command = ( 40 | ["list-all-github-repositories", "--stats"] 41 | if expected_stat 42 | else ["list-all-github-repositories"] 43 | ) 44 | result = runner.invoke(cli, command) 45 | 46 | assert result.exit_code == 0 47 | mock_print_repositories.assert_called_once_with(repositories_fixture, expected_stat) 48 | 49 | 50 | @pytest.mark.parametrize("expected_stat", [True, False]) 51 | @patch("gitea_github_sync.cli.print_repositories", autospec=True) 52 | @patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True) 53 | def test_list_all_gitea_repositories( 54 | mock_get_gitea: MagicMock, 55 | mock_print_repositories: MagicMock, 56 | expected_stat: bool, 57 | repositories_fixture: List[Repository], 58 | ) -> None: 59 | mock_gitea = MagicMock() 60 | mock_get_gitea.return_value = mock_gitea 61 | mock_gitea.get_repos.return_value = repositories_fixture 62 | 63 | runner = CliRunner() 64 | command = ( 65 | ["list-all-gitea-repositories", "--stats"] 66 | if expected_stat 67 | else ["list-all-gitea-repositories"] 68 | ) 69 | result = runner.invoke(cli, command) 70 | 71 | assert result.exit_code == 0 72 | mock_print_repositories.assert_called_once_with(repositories_fixture, expected_stat) 73 | 74 | 75 | @patch("gitea_github_sync.cli.config.load_config", autospec=True) 76 | @patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True) 77 | @patch("gitea_github_sync.cli.github.get_github", autospec=True) 78 | @patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True) 79 | def test_migrate_repo( 80 | mock_get_gitea: MagicMock, 81 | mock_get_github: MagicMock, 82 | mock_list_all_repositories: MagicMock, 83 | mock_load_config: MagicMock, 84 | repositories_fixture: List[Repository], 85 | ) -> None: 86 | expected_repo = Repository("Muscaw/gitea-github-sync", Visibility.PRIVATE) 87 | expected_github_token = "some-github-token" 88 | 89 | type(mock_load_config.return_value).github_token = PropertyMock( 90 | return_value=expected_github_token 91 | ) 92 | mock_list_all_repositories.return_value = repositories_fixture + [expected_repo] 93 | 94 | runner = CliRunner() 95 | command = ["migrate-repo", "Muscaw/gitea-github-sync"] 96 | result = runner.invoke(cli, command) 97 | 98 | assert result.exit_code == 0 99 | mock_list_all_repositories.assert_called_once_with(mock_get_github.return_value) 100 | mock_get_gitea.return_value.migrate_repo.assert_called_once_with( 101 | repo=expected_repo, github_token=expected_github_token 102 | ) 103 | 104 | 105 | @patch("gitea_github_sync.cli.config.load_config", autospec=True) 106 | @patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True) 107 | @patch("gitea_github_sync.cli.github.get_github", autospec=True) 108 | @patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True) 109 | def test_migrate_repo_no_match( 110 | mock_get_gitea: MagicMock, 111 | mock_get_github: MagicMock, 112 | mock_list_all_repositories: MagicMock, 113 | mock_load_config: MagicMock, 114 | repositories_fixture: List[Repository], 115 | ) -> None: 116 | mock_list_all_repositories.return_value = repositories_fixture 117 | repo_name = "Muscaw/gitea-github-sync" 118 | 119 | runner = CliRunner() 120 | command = ["migrate-repo", repo_name] 121 | result = runner.invoke(cli, command) 122 | 123 | assert result.exit_code != 0 124 | assert "Aborted!" in result.stdout 125 | assert f"Repository {repo_name} does not exist on Github" in result.stdout 126 | mock_list_all_repositories.assert_called_once_with(mock_get_github.return_value) 127 | mock_get_gitea.return_value.migrate_repo.assert_not_called() 128 | mock_load_config.assert_called_once() 129 | 130 | 131 | @patch("gitea_github_sync.cli.config.load_config", autospec=True) 132 | @patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True) 133 | @patch("gitea_github_sync.cli.github.get_github", autospec=True) 134 | @patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True) 135 | def test_migrate_repo_gitea_migration_error( 136 | mock_get_gitea: MagicMock, 137 | mock_get_github: MagicMock, 138 | mock_list_all_repositories: MagicMock, 139 | mock_load_config: MagicMock, 140 | repositories_fixture: List[Repository], 141 | ) -> None: 142 | expected_repo = Repository("Muscaw/gitea-github-sync", Visibility.PRIVATE) 143 | expected_github_token = "some-github-token" 144 | 145 | type(mock_load_config.return_value).github_token = PropertyMock( 146 | return_value=expected_github_token 147 | ) 148 | mock_list_all_repositories.return_value = repositories_fixture + [expected_repo] 149 | mock_get_gitea.return_value.migrate_repo.side_effect = GiteaMigrationError( 150 | full_repo_name=expected_repo.full_repo_name 151 | ) 152 | 153 | runner = CliRunner() 154 | command = ["migrate-repo", "Muscaw/gitea-github-sync"] 155 | result = runner.invoke(cli, command) 156 | 157 | assert result.exit_code == 0 158 | assert "Migration Error for Muscaw/gitea-github-sync" in result.stdout 159 | mock_list_all_repositories.assert_called_once_with(mock_get_github.return_value) 160 | mock_get_gitea.return_value.migrate_repo.assert_called_once_with( 161 | repo=expected_repo, github_token=expected_github_token 162 | ) 163 | 164 | 165 | NO_REPOS: List[Repository] = [] 166 | MULTIPLE_REPOS = [ 167 | Repository("some-team/a-repo", Visibility.PUBLIC), 168 | Repository("some-team/b-repo", Visibility.PRIVATE), 169 | Repository("some-team/c-repo", Visibility.UNKNOWN), 170 | ] 171 | 172 | 173 | @pytest.mark.parametrize( 174 | "repos_to_sync, expected_output", 175 | [ 176 | ( 177 | NO_REPOS, 178 | textwrap.dedent( 179 | """\ 180 | Starting migration for 0 repos 181 | No repos were migrated 182 | """ 183 | ), 184 | ), 185 | ( 186 | MULTIPLE_REPOS, 187 | textwrap.dedent( 188 | """\ 189 | Starting migration for 3 repos 190 | Migrating some-team/a-repo 191 | Migrating some-team/b-repo 192 | Migrating some-team/c-repo 193 | Migrated 3 out of 3 repos successfully 194 | """ 195 | ), 196 | ), 197 | ], 198 | ) 199 | @patch("gitea_github_sync.cli.migration.list_missing_github_repos", autospec=True) 200 | @patch("gitea_github_sync.cli.config.load_config", autospec=True) 201 | @patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True) 202 | @patch("gitea_github_sync.cli.github.get_github", autospec=True) 203 | @patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True) 204 | def test_sync( 205 | mock_get_gitea: MagicMock, 206 | mock_get_github: MagicMock, 207 | mock_list_all_repositories: MagicMock, 208 | mock_load_config: MagicMock, 209 | mock_list_missing_github_repos: MagicMock, 210 | repos_to_sync: List[Repository], 211 | expected_output: str, 212 | ) -> None: 213 | expected_github_token = "some-github-token" 214 | 215 | type(mock_load_config.return_value).github_token = PropertyMock( 216 | return_value=expected_github_token 217 | ) 218 | mock_list_missing_github_repos.return_value = repos_to_sync 219 | 220 | runner = CliRunner() 221 | command = ["sync"] 222 | result = runner.invoke(cli, command) 223 | 224 | assert result.exit_code == 0 225 | assert result.stdout == expected_output 226 | mock_load_config.assert_called_once() 227 | mock_list_all_repositories.assert_called_once_with(mock_get_github.return_value) 228 | mock_list_missing_github_repos.assert_called_once_with( 229 | gh_repos=mock_list_all_repositories.return_value, 230 | gitea_repos=mock_get_gitea.return_value.get_repos.return_value, 231 | ) 232 | mock_get_gitea.return_value.migrate_repo.assert_has_calls( 233 | [call(repo=repo, github_token=expected_github_token) for repo in repos_to_sync] 234 | ) 235 | 236 | 237 | REPOS_MIGRATION_ERR = [ 238 | Repository("some-team/a-repo", Visibility.PUBLIC), 239 | Repository("some-team/migerr-repo", Visibility.PRIVATE), 240 | Repository("some-team/c-repo", Visibility.UNKNOWN), 241 | ] 242 | 243 | 244 | @pytest.mark.parametrize( 245 | "repos_to_sync, expected_output", 246 | [ 247 | ( 248 | NO_REPOS, 249 | textwrap.dedent( 250 | """\ 251 | Starting migration for 0 repos 252 | No repos were migrated 253 | """ 254 | ), 255 | ), 256 | ( 257 | MULTIPLE_REPOS, 258 | textwrap.dedent( 259 | """\ 260 | Starting migration for 3 repos 261 | Migrating some-team/a-repo 262 | Migrating some-team/b-repo 263 | Migrating some-team/c-repo 264 | Migrated 3 out of 3 repos successfully 265 | """ 266 | ), 267 | ), 268 | ( 269 | [Repository("some-team/a-repo", Visibility.PUBLIC)], 270 | textwrap.dedent( 271 | """\ 272 | Starting migration for 1 repos 273 | Migrating some-team/a-repo 274 | Migrated 1 out of 1 repos successfully 275 | """ 276 | ), 277 | ), 278 | ( 279 | REPOS_MIGRATION_ERR, 280 | textwrap.dedent( 281 | """\ 282 | Starting migration for 3 repos 283 | Migrating some-team/a-repo 284 | Migrating some-team/migerr-repo 285 | Migration Error for some-team/migerr-repo 286 | Migrating some-team/c-repo 287 | Migrated 2 out of 3 repos successfully 288 | Failed 1 out of 3 migrations 289 | """ 290 | ), 291 | ), 292 | ], 293 | ) 294 | @patch("gitea_github_sync.cli.migration.list_missing_github_repos", autospec=True) 295 | @patch("gitea_github_sync.cli.config.load_config", autospec=True) 296 | @patch("gitea_github_sync.cli.github.list_all_repositories", autospec=True) 297 | @patch("gitea_github_sync.cli.github.get_github", autospec=True) 298 | @patch("gitea_github_sync.cli.gitea.get_gitea", autospec=True) 299 | def test_sync_with_errors( 300 | mock_get_gitea: MagicMock, 301 | mock_get_github: MagicMock, 302 | mock_list_all_repositories: MagicMock, 303 | mock_load_config: MagicMock, 304 | mock_list_missing_github_repos: MagicMock, 305 | repos_to_sync: List[Repository], 306 | expected_output: str, 307 | ) -> None: 308 | expected_github_token = "some-github-token" 309 | 310 | type(mock_load_config.return_value).github_token = PropertyMock( 311 | return_value=expected_github_token 312 | ) 313 | mock_list_missing_github_repos.return_value = repos_to_sync 314 | mock_get_gitea.return_value.get_repos.return_value = MULTIPLE_REPOS 315 | 316 | # Mocking migrate_repo to raise an error for 'some-team/migerr-repo' 317 | def migrate_repo_side_effect(repo: Repository, github_token: str) -> None: 318 | if repo.full_repo_name == "some-team/migerr-repo": 319 | raise GiteaMigrationError(full_repo_name=repo.full_repo_name) 320 | 321 | mock_get_gitea.return_value.migrate_repo.side_effect = migrate_repo_side_effect 322 | 323 | runner = CliRunner() 324 | command = ["sync"] 325 | result = runner.invoke(cli, command) 326 | 327 | assert result.exit_code == 0 328 | assert result.stdout == expected_output 329 | mock_load_config.assert_called_once() 330 | mock_list_all_repositories.assert_called_once_with(mock_get_github.return_value) 331 | mock_list_missing_github_repos.assert_called_once_with( 332 | gh_repos=mock_list_all_repositories.return_value, 333 | gitea_repos=mock_get_gitea.return_value.get_repos.return_value, 334 | ) 335 | mock_get_gitea.return_value.migrate_repo.assert_has_calls( 336 | [call(repo=repo, github_token=expected_github_token) for repo in repos_to_sync] 337 | ) 338 | 339 | 340 | @patch("sys.stdout", new_callable=StringIO) 341 | def test_print_repositories_without_stats( 342 | stdout: StringIO, 343 | repositories_fixture: List[Repository], 344 | ) -> None: 345 | print_repositories(repositories_fixture, False) 346 | assert stdout.getvalue() == "some-team/a-repo\nsome-team/b-repo\nsome-team/c-repo\n" 347 | 348 | 349 | @patch("sys.stdout", new_callable=StringIO) 350 | def test_print_repositories(stdout: StringIO, repositories_fixture: List[Repository]) -> None: 351 | print_repositories(repositories_fixture, True) 352 | expected_result = textwrap.dedent( 353 | """\ 354 | some-team/a-repo 355 | some-team/b-repo 356 | some-team/c-repo 357 | 358 | Repository stats 359 | Number of public repos identified: 1 360 | Number of private repos identified: 1 361 | Number of unknown repos identified: 1 362 | Total number of repos identified: 3 363 | """ 364 | ) 365 | 366 | assert stdout.getvalue() == expected_result 367 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from unittest.mock import MagicMock, mock_open, patch 3 | 4 | import pytest 5 | from piny import ValidationError 6 | 7 | from gitea_github_sync.config import Config, config_file_location, load_config 8 | 9 | VALID_CONFIG_FILE = """ 10 | github_token: some-github-token 11 | gitea_api_url: https://some-gitea-url.com 12 | gitea_token: some-gitea-token 13 | """ 14 | 15 | VALID_CONFIG = Config( 16 | github_token="some-github-token", 17 | gitea_api_url="https://some-gitea-url.com", 18 | gitea_token="some-gitea-token", 19 | ) 20 | 21 | DEFAULT_CONFIG_FILE_PATH = config_file_location() 22 | 23 | 24 | @patch("builtins.open", new_callable=mock_open, read_data=VALID_CONFIG_FILE) 25 | def test_load_config(mock_file_open: MagicMock) -> None: 26 | config = load_config() 27 | 28 | assert config == VALID_CONFIG 29 | mock_file_open.assert_called_once_with(DEFAULT_CONFIG_FILE_PATH) 30 | 31 | 32 | @patch("builtins.open", new_callable=mock_open, read_data=VALID_CONFIG_FILE) 33 | def test_load_config_non_default_path(mock_file_open: MagicMock) -> None: 34 | other_path = Path("/config.yml") 35 | config = load_config(config_location=other_path) 36 | 37 | assert config == VALID_CONFIG 38 | mock_file_open.assert_called_once_with(other_path) 39 | 40 | 41 | @patch("builtins.open", new_callable=mock_open, read_data="bad-file") 42 | def test_load_config_bad_file(mock_file_open: MagicMock) -> None: 43 | with pytest.raises(ValidationError): 44 | load_config() 45 | 46 | mock_file_open.assert_called_once_with(DEFAULT_CONFIG_FILE_PATH) 47 | 48 | 49 | def test_config_file_location() -> None: 50 | result = config_file_location() 51 | assert result == Path.home() / ".config" / "gitea-github-sync" / "config.yml" 52 | -------------------------------------------------------------------------------- /tests/test_gitea.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock, patch 2 | 3 | import pytest 4 | import responses 5 | from responses import matchers 6 | 7 | from gitea_github_sync.config import Config 8 | from gitea_github_sync.gitea import Gitea, GiteaMigrationError, get_gitea 9 | from gitea_github_sync.repository import Repository, Visibility 10 | 11 | GITEA_BASE_API_URL = "https://gitea.yourinstance.com/api/v1" 12 | GITEA_TOKEN = "your-gitea-token" 13 | 14 | 15 | @pytest.fixture 16 | def conf_fixture() -> Config: 17 | return Config( 18 | github_token="some-token", gitea_api_url=GITEA_BASE_API_URL, gitea_token=GITEA_TOKEN 19 | ) 20 | 21 | 22 | @pytest.fixture 23 | def gitea_fixture(conf_fixture: Config) -> Gitea: 24 | return Gitea(api_url=conf_fixture.gitea_api_url, api_token=conf_fixture.gitea_token) 25 | 26 | 27 | @responses.activate 28 | def test_gitea_get_repos(gitea_fixture: Gitea) -> None: 29 | json = [ 30 | {"full_name": "some-team/a-repo", "private": True}, 31 | {"full_name": "some-team/b-repo", "private": False}, 32 | ] 33 | 34 | expected_repos = [ 35 | Repository(full_repo_name="some-team/a-repo", visibility=Visibility.PRIVATE), 36 | Repository(full_repo_name="some-team/b-repo", visibility=Visibility.PUBLIC), 37 | ] 38 | responses.get( 39 | f"{GITEA_BASE_API_URL}/user/repos", 40 | match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})], 41 | json=json, 42 | ) 43 | 44 | result = gitea_fixture.get_repos() 45 | assert expected_repos == result 46 | 47 | 48 | @responses.activate 49 | def test_gitea_get_repos_multiple_pages(gitea_fixture: Gitea) -> None: 50 | json_1 = [ 51 | {"full_name": "some-team/a-repo", "private": True}, 52 | {"full_name": "some-team/b-repo", "private": False}, 53 | ] 54 | json_2 = [ 55 | {"full_name": "some-team/c-repo", "private": True}, 56 | {"full_name": "some-team/d-repo", "private": False}, 57 | ] 58 | json_3 = [ 59 | {"full_name": "some-team/e-repo", "private": True}, 60 | {"full_name": "some-team/f-repo", "private": False}, 61 | ] 62 | 63 | expected_repos = [ 64 | Repository(full_repo_name="some-team/a-repo", visibility=Visibility.PRIVATE), 65 | Repository(full_repo_name="some-team/b-repo", visibility=Visibility.PUBLIC), 66 | Repository(full_repo_name="some-team/c-repo", visibility=Visibility.PRIVATE), 67 | Repository(full_repo_name="some-team/d-repo", visibility=Visibility.PUBLIC), 68 | Repository(full_repo_name="some-team/e-repo", visibility=Visibility.PRIVATE), 69 | Repository(full_repo_name="some-team/f-repo", visibility=Visibility.PUBLIC), 70 | ] 71 | responses.get( 72 | f"{GITEA_BASE_API_URL}/user/repos", 73 | match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})], 74 | json=json_1, 75 | headers={ 76 | "link": ( 77 | f'<{GITEA_BASE_API_URL}/user/repos?page=2>; rel="next",' 78 | + f'<{GITEA_BASE_API_URL}/user/repos?page=3>; rel="last"' 79 | ) 80 | }, 81 | ) 82 | responses.get( 83 | f"{GITEA_BASE_API_URL}/user/repos?page=2", 84 | match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})], 85 | json=json_2, 86 | headers={ 87 | "link": ( 88 | f'<{GITEA_BASE_API_URL}/user/repos?page=3>; rel="next",' 89 | + f'<{GITEA_BASE_API_URL}/user/repos?page=3>; rel="last",' 90 | + f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="first",' 91 | + f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="prev"' 92 | ) 93 | }, 94 | ) 95 | responses.get( 96 | f"{GITEA_BASE_API_URL}/user/repos?page=3", 97 | match=[matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"})], 98 | json=json_3, 99 | headers={ 100 | "link": ( 101 | f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="first",' 102 | + f'<{GITEA_BASE_API_URL}/user/repos?page=1>; rel="prev"' 103 | ) 104 | }, 105 | ) 106 | 107 | result = gitea_fixture.get_repos() 108 | assert expected_repos == result 109 | 110 | 111 | @responses.activate 112 | @pytest.mark.parametrize("is_private", [True, False]) 113 | def test_gitea_migrate_repo(gitea_fixture: Gitea, is_private: bool) -> None: 114 | gh_token = "some-github-token" 115 | expected_data = { 116 | "auth_token": gh_token, 117 | "clone_addr": "https://github.com/Muscaw/gitea-github-sync", 118 | "repo_name": "gitea-github-sync", 119 | "service": "github", 120 | "mirror": True, 121 | "private": is_private, 122 | } 123 | repo = Repository( 124 | full_repo_name="Muscaw/gitea-github-sync", 125 | visibility=Visibility.PRIVATE if is_private else Visibility.PUBLIC, 126 | ) 127 | responses.post( 128 | f"{GITEA_BASE_API_URL}/repos/migrate", 129 | match=[ 130 | matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"}), 131 | matchers.json_params_matcher(expected_data), 132 | ], 133 | ) 134 | 135 | gitea_fixture.migrate_repo(repo, gh_token) 136 | 137 | 138 | @responses.activate 139 | @pytest.mark.parametrize("is_private", [True, False]) 140 | def test_gitea_migrate_repo_failure_to_migrate(gitea_fixture: Gitea, is_private: bool) -> None: 141 | gh_token = "some-github-token" 142 | expected_data = { 143 | "auth_token": gh_token, 144 | "clone_addr": "https://github.com/Muscaw/gitea-github-sync", 145 | "repo_name": "gitea-github-sync", 146 | "service": "github", 147 | "mirror": True, 148 | "private": is_private, 149 | } 150 | repo = Repository( 151 | full_repo_name="Muscaw/gitea-github-sync", 152 | visibility=Visibility.PRIVATE if is_private else Visibility.PUBLIC, 153 | ) 154 | responses.post( 155 | f"{GITEA_BASE_API_URL}/repos/migrate", 156 | match=[ 157 | matchers.header_matcher({"Authorization": f"token {GITEA_TOKEN}"}), 158 | matchers.json_params_matcher(expected_data), 159 | ], 160 | status=409, 161 | ) 162 | 163 | with pytest.raises(GiteaMigrationError): 164 | gitea_fixture.migrate_repo(repo, gh_token) 165 | 166 | 167 | def test_gitea(gitea_fixture: Gitea, conf_fixture: Config) -> None: 168 | gt = get_gitea(conf_fixture) 169 | 170 | assert gt == gitea_fixture 171 | 172 | 173 | @patch("gitea_github_sync.gitea.config.load_config", autospec=True) 174 | def test_gitea_default_value( 175 | mock_load_config: MagicMock, gitea_fixture: Gitea, conf_fixture: Config 176 | ) -> None: 177 | mock_load_config.return_value = conf_fixture 178 | gt = get_gitea() 179 | 180 | assert gt == gitea_fixture 181 | mock_load_config.assert_called_once() 182 | 183 | 184 | def test_gitea_migration_error() -> None: 185 | error = GiteaMigrationError("Muscaw/gitea-github-sync") 186 | assert str(error) == "Could not migrate Muscaw/gitea-github-sync" 187 | -------------------------------------------------------------------------------- /tests/test_github.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | from unittest.mock import MagicMock, patch 4 | 5 | import pytest 6 | from github import Github 7 | 8 | from gitea_github_sync.config import Config 9 | from gitea_github_sync.github import get_github, list_all_repositories 10 | from gitea_github_sync.repository import Repository, Visibility 11 | 12 | from .test_config import VALID_CONFIG 13 | 14 | 15 | @pytest.fixture 16 | def conf_fixture() -> Config: 17 | return VALID_CONFIG 18 | 19 | 20 | @pytest.mark.parametrize( 21 | "str_value, expected", 22 | [ 23 | ("public", Visibility.PUBLIC), 24 | ("private", Visibility.PRIVATE), 25 | ("unknown", Visibility.UNKNOWN), 26 | ("random_value", Visibility.UNKNOWN), 27 | ], 28 | ) 29 | def test_visibility_from_str(str_value: str, expected: Visibility) -> None: 30 | result = Visibility.from_str(str_value) 31 | assert result == expected 32 | 33 | 34 | @pytest.mark.parametrize( 35 | "expected_org, repo", 36 | [ 37 | ("team", Repository(full_repo_name="team/some-repo", visibility=Visibility.UNKNOWN)), 38 | ( 39 | "some-team", 40 | Repository(full_repo_name="some-team/some-repo", visibility=Visibility.UNKNOWN), 41 | ), 42 | ], 43 | ) 44 | def test_repository_get_org_name(expected_org: str, repo: Repository) -> None: 45 | org_name = repo.get_org_name() 46 | assert org_name == expected_org 47 | 48 | 49 | @pytest.mark.parametrize( 50 | "expected_repo_name, repo", 51 | [ 52 | ("repo", Repository(full_repo_name="team/repo", visibility=Visibility.UNKNOWN)), 53 | ( 54 | "some-repo", 55 | Repository(full_repo_name="some-team/some-repo", visibility=Visibility.UNKNOWN), 56 | ), 57 | ], 58 | ) 59 | def test_repository_get_repo_name(expected_repo_name: str, repo: Repository) -> None: 60 | repo_name = repo.get_repo_name() 61 | assert repo_name == expected_repo_name 62 | 63 | 64 | @patch("gitea_github_sync.github.Github", autospec=True) 65 | def test_github(mock_github: MagicMock, conf_fixture: Config) -> None: 66 | gh = get_github(conf_fixture) 67 | 68 | assert gh == mock_github.return_value 69 | mock_github.assert_called_once_with(conf_fixture.github_token) 70 | 71 | 72 | @patch("gitea_github_sync.github.config.load_config", autospec=True) 73 | @patch("gitea_github_sync.github.Github", autospec=True) 74 | def test_github_default_value( 75 | mock_github: MagicMock, mock_load_config: MagicMock, conf_fixture: Config 76 | ) -> None: 77 | mock_load_config.return_value = conf_fixture 78 | gh = get_github() 79 | 80 | assert gh == mock_github.return_value 81 | mock_github.assert_called_once_with(conf_fixture.github_token) 82 | mock_load_config.assert_called_once() 83 | 84 | 85 | @dataclass(frozen=True) 86 | class MockGithubRepository: 87 | full_name: str 88 | visibility: str 89 | 90 | 91 | @pytest.mark.parametrize( 92 | "gh_repos, expected_repos", 93 | [ 94 | ( 95 | [ 96 | MockGithubRepository(full_name="a/a-repo", visibility="public"), 97 | MockGithubRepository(full_name="b/a-repo", visibility="private"), 98 | MockGithubRepository(full_name="c/a-repo", visibility="unknown"), 99 | MockGithubRepository(full_name="d/a-repo", visibility="something-else"), 100 | ], 101 | [ 102 | Repository(full_repo_name="a/a-repo", visibility=Visibility.PUBLIC), 103 | Repository(full_repo_name="b/a-repo", visibility=Visibility.PRIVATE), 104 | Repository(full_repo_name="c/a-repo", visibility=Visibility.UNKNOWN), 105 | Repository(full_repo_name="d/a-repo", visibility=Visibility.UNKNOWN), 106 | ], 107 | ), 108 | ( 109 | [MockGithubRepository(full_name="some-team/a-repo", visibility="public")], 110 | [Repository(full_repo_name="some-team/a-repo", visibility=Visibility.PUBLIC)], 111 | ), 112 | ( 113 | [], 114 | [], 115 | ), 116 | ], 117 | ) 118 | def test_list_all_repositories( 119 | gh_repos: List[MockGithubRepository], expected_repos: List[Repository] 120 | ) -> None: 121 | mock_gh = MagicMock(spec_set=Github) 122 | mock_gh.get_user.return_value.get_repos.return_value = gh_repos 123 | 124 | result = list_all_repositories(mock_gh) 125 | 126 | assert result == expected_repos 127 | mock_gh.get_user.assert_called_once() 128 | mock_gh.get_user.return_value.get_repos.assert_called_once() 129 | -------------------------------------------------------------------------------- /tests/test_migration.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pytest 4 | 5 | from gitea_github_sync.migration import list_missing_github_repos 6 | from gitea_github_sync.repository import Repository, Visibility 7 | 8 | 9 | def r(org_name: str, repo_name: str) -> Repository: 10 | return Repository(full_repo_name=f"{org_name}/{repo_name}", visibility=Visibility.PUBLIC) 11 | 12 | 13 | def team_a_repo(repo_name: str) -> Repository: 14 | return r(org_name="team-a", repo_name=repo_name) 15 | 16 | 17 | def team_b_repo(repo_name: str) -> Repository: 18 | return r(org_name="team-b", repo_name=repo_name) 19 | 20 | 21 | @pytest.mark.parametrize( 22 | "gh_repos, gt_repos, expected_diff", 23 | [ 24 | pytest.param( 25 | [team_a_repo("a-repo"), team_a_repo("b-repo")], 26 | [team_b_repo("a-repo"), team_b_repo("b-repo")], 27 | [], 28 | id="equal", 29 | ), 30 | pytest.param( 31 | [team_a_repo("a-repo"), team_a_repo("b-repo")], 32 | [team_b_repo("a-repo")], 33 | [team_a_repo("b-repo")], 34 | id="missing-repo-on-gitea", 35 | ), 36 | pytest.param( 37 | [team_a_repo("a-repo"), team_a_repo("b-repo")], 38 | [team_b_repo("a-repo"), team_b_repo("b-repo"), team_b_repo("c-repo")], 39 | [], 40 | id="too-many-repos-on-gitea", 41 | ), 42 | ], 43 | ) 44 | def test_list_missing_github_repos( 45 | gh_repos: List[Repository], gt_repos: List[Repository], expected_diff: List[Repository] 46 | ) -> None: 47 | result = list_missing_github_repos(gh_repos=gh_repos, gitea_repos=gt_repos) 48 | 49 | assert result == expected_diff 50 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | lint, 4 | py{38, 39, 310, 311} 5 | check 6 | 7 | [gh-actions] 8 | python = 9 | 3.8: py38 10 | 3.9: py39 11 | 3.10: py310, mypy, check 12 | 3.11: py311 13 | 14 | [testenv] 15 | description = Run tests 16 | deps = 17 | poetry 18 | 19 | [testenv:format] 20 | description = Format code 21 | commands = 22 | poetry install --with dev 23 | black . 24 | isort . 25 | 26 | [testenv:lint] 27 | description = Check linters for failure 28 | commands = 29 | poetry install --with dev 30 | black --check . 31 | isort -c . 32 | flake8 gitea_github_sync tests 33 | mypy -p gitea_github_sync --install-types --non-interactive 34 | mypy tests/ --install-types --non-interactive 35 | pytest --cov-report term-missing --cov-report=xml:./coverage.xml --cov=gitea_github_sync tests/ {posargs} 36 | --------------------------------------------------------------------------------