├── tests ├── __init__.py ├── conftest.py ├── _utilities.py ├── _fixtures.py └── test_command.py ├── src └── poetry_relax │ ├── py.typed │ ├── __init__.py │ ├── _core.py │ └── command.py ├── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── lint.yaml │ ├── release.yaml │ ├── build.yaml │ └── test.yaml ├── .gitattributes ├── scripts ├── ruff-check ├── ruff-format ├── lint └── version ├── LICENSE ├── pyproject.toml ├── README.md └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/poetry_relax/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: zanieb 2 | 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | poetry.lock linguist-generated=true -------------------------------------------------------------------------------- /scripts/ruff-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ruff check "$@" 4 | -------------------------------------------------------------------------------- /scripts/ruff-format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ruff format "$@" 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | # Improve diff display for assertions in utilities 4 | # Note: This must occur before import of the module 5 | pytest.register_assert_rewrite("tests._utilities") 6 | 7 | from ._fixtures import * # noqa 8 | -------------------------------------------------------------------------------- /src/poetry_relax/__init__.py: -------------------------------------------------------------------------------- 1 | from poetry.console.application import Application 2 | from poetry.plugins.application_plugin import ApplicationPlugin 3 | 4 | from poetry_relax.command import RelaxCommand 5 | 6 | # Plugin registration with Poetry 7 | 8 | 9 | def _command_factory() -> RelaxCommand: 10 | return RelaxCommand() 11 | 12 | 13 | class RelaxPlugin(ApplicationPlugin): 14 | def activate(self, application: Application): 15 | application.command_loader.register_factory("relax", _command_factory) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Zanie (mzanie) 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. -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | env: 4 | POETRY_VERSION: "1.6.1" 5 | # Set the terminal so `tput` will work 6 | TERM: "linux" 7 | 8 | on: 9 | pull_request: 10 | types: [opened, reopened, synchronize] 11 | push: 12 | branches: 13 | - main 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | run-tests: 20 | name: Python linters 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 5 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | with: 27 | persist-credentials: false 28 | fetch-depth: 0 29 | 30 | - name: Set up Python 3.10 31 | uses: actions/setup-python@v4 32 | with: 33 | python-version: "3.10" 34 | 35 | - name: Set up Poetry 36 | run: | 37 | pip install poetry==${{ env.POETRY_VERSION }} 38 | 39 | - name: Install packages 40 | run: | 41 | poetry install 42 | 43 | - name: Lint 44 | run: | 45 | ./scripts/lint check . 46 | 47 | - name: Check poetry 48 | run: | 49 | poetry check 50 | poetry lock --check 51 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | env: 4 | POETRY_VERSION: "1.6.1" 5 | 6 | on: 7 | push: 8 | tags: 9 | - "[0-9]+.[0-9]+.[0-9]+" 10 | - "[0-9]+.[0-9]+rc[0-9]+" 11 | - "[0-9]+.[0-9]+[ab][0-9]+" 12 | 13 | jobs: 14 | release: 15 | name: Release to PyPI 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | persist-credentials: false 22 | fetch-depth: 0 23 | 24 | - name: Set up Python 3.10 25 | uses: actions/setup-python@v4 26 | with: 27 | python-version: "3.10" 28 | 29 | - name: Set up Poetry 30 | run: | 31 | pip install poetry==${{ env.POETRY_VERSION }} 32 | 33 | - name: Install local poetry-relax 34 | run: | 35 | poetry self add $(pwd) 36 | 37 | - name: Relax constraints 38 | run: | 39 | poetry relax --check 40 | 41 | # Note: If build and publish steps are ever separated, the version must 42 | # be set before building 43 | - name: Publish package 44 | env: 45 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }} 46 | run: | 47 | poetry version "${GITHUB_REF#refs/*/}" 48 | poetry publish --build 49 | -------------------------------------------------------------------------------- /scripts/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | EXIT_STATUS=0 4 | 5 | bold() { echo "$(tput bold)$*$(tput sgr0)"; } 6 | show() { bold " 7 | → $*" >&2; } 8 | run() { show $1; poetry run -- "$@" || EXIT_STATUS=$?; } 9 | 10 | mode="$1" 11 | shift 12 | targets="$*" 13 | 14 | if [ -z "$mode" ]; then 15 | mode="fix" 16 | fi 17 | 18 | if [ -z "$targets" ]; then 19 | echo "Checking for staged changes fix..." 20 | git diff --staged --quiet --exit-code && bold "Nothing to lint!" && exit 0 21 | targets="." 22 | fi 23 | 24 | # TODO: Determine how to get this stash to behave well 25 | # git stash --keep-index --include-untracked 26 | 27 | # Add the scripts directory to the path 28 | scripts_dir=$(realpath "$(dirname "$0")") 29 | PATH="$scripts_dir:$PATH" 30 | 31 | if [ "$mode" = "check" ]; then 32 | echo "Checking for lint..." 33 | run ruff-check --diff $targets 34 | run ruff-format --check $targets 35 | run mypy --namespace-packages --exclude tests $targets 36 | elif [ "$mode" = "fix" ]; then 37 | echo "Fixing lint..." 38 | run ruff-check --fix $targets 39 | run ruff-format $targets 40 | else 41 | echo "Unknown mode '$mode' expected 'fix' or 'check'." 42 | exit 3 43 | 44 | fi; 45 | 46 | # TODO: See note above. 47 | # git stash pop --quiet 48 | 49 | [ $EXIT_STATUS -eq 0 ] && bold " 50 | ✔ LGTM" || bold " 51 | ✘ Lint failed!" 52 | 53 | exit $EXIT_STATUS 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "poetry-relax" 3 | version = "0.0.0" 4 | repository = "https://github.com/madkinsz/poetry-relax" 5 | description = "Plugin for Poetry to relax upper version pins" 6 | authors = ["Zanie "] 7 | readme = "README.md" 8 | keywords = ["poetry", "plugin", "versioning", "version"] 9 | packages = [{ include = "poetry_relax", from = "src" }] 10 | classifiers = [ 11 | "Development Status :: 3 - Alpha", 12 | "Operating System :: OS Independent", 13 | "Environment :: Plugins", 14 | "Intended Audience :: Developers", 15 | "License :: OSI Approved :: MIT License", 16 | "Natural Language :: English", 17 | "Programming Language :: Python", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.7", 20 | "Programming Language :: Python :: 3.8", 21 | "Programming Language :: Python :: 3.9", 22 | "Programming Language :: Python :: 3.10", 23 | "Programming Language :: Python :: 3.11", 24 | "Programming Language :: Python :: 3.12", 25 | "Topic :: Software Development :: Libraries", 26 | "Topic :: Software Development", 27 | "Typing :: Typed", 28 | ] 29 | 30 | [tool.poetry.dependencies] 31 | python = "^3.8" 32 | poetry = ">=1.2" 33 | 34 | [tool.poetry.plugins."poetry.application.plugin"] 35 | relax = "poetry_relax:RelaxPlugin" 36 | 37 | [tool.poetry.group.dev.dependencies] 38 | pytest = ">=7.1.3" 39 | pytest-xdist = ">=2.5.0" 40 | mypy = ">=0.971" 41 | types-setuptools = ">=67.6.0.5" 42 | ruff = ">=0.0.287" 43 | 44 | [build-system] 45 | requires = ["poetry-core"] 46 | build-backend = "poetry.core.masonry.api" 47 | 48 | [tool.ruff.lint] 49 | extend-select = ["I"] 50 | 51 | # Line length is enforced by the formatter 52 | ignore = ["E501"] 53 | -------------------------------------------------------------------------------- /scripts/version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """" 3 | Simple utility for generating version strings based on the information in the git index. 4 | 5 | Supports three modes: parts, local, and dev. 6 | 7 | parts 8 | Display all available version parts. 9 | Format: {timestamp: str, short_hash: str, closest_tag: str, distance: int} 10 | 11 | $ version parts 12 | {'timestamp': '1663305183', 'short_hash': '440f9b6', 'closest_tag': '0.0.0', 'distance': 50} 13 | 14 | local 15 | Generate a PEP 440 compliant local version tag. 16 | Format: {last_tag}+{num commits}.{commit hash} 17 | 18 | $ version local 19 | 0.0.0+50.440f9b6 20 | 21 | dev: Generate a PEP 440 compliant development prerelease version tag. 22 | Format: {last_tag + 1}.dev{timestamp} 23 | 24 | $ version dev 25 | 0.0.1.dev1663305183 26 | 27 | Implementation based on versioneer but is unlikely to be robust to old git versions and 28 | tagging schemes that deviate from PEP 440. 29 | """ 30 | import re 31 | import subprocess 32 | import sys 33 | 34 | 35 | def panic(message): 36 | print(message, file=sys.stderr) 37 | exit(1) 38 | 39 | 40 | def run(command): 41 | return subprocess.check_output(command).decode().strip() 42 | 43 | 44 | def get_version_parts(): 45 | git_describe = run(["git", "describe", "--tags", "--always", "--long"]) 46 | commit_timestamp = run(["git", "show", "-s", "--format=%ct", "HEAD"]) 47 | 48 | parts = {} 49 | 50 | parts["timestamp"] = commit_timestamp 51 | 52 | if "-" in git_describe: 53 | # TAG-NUM-gHEX 54 | match = re.match(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) 55 | if not match: 56 | panic() 57 | 58 | # tag 59 | parts["closest_tag"] = match.group(1) 60 | 61 | # distance: number of commits since tag 62 | parts["distance"] = int(match.group(2)) 63 | 64 | # commit: short hex revision ID 65 | parts["short_hash"] = match.group(3) 66 | 67 | else: 68 | # HEX: no tags 69 | parts["short_hash"] = git_describe 70 | 71 | parts["closest_tag"] = "0.0.0" 72 | 73 | git_rev_list = run(["git", "rev-list", "HEAD", "--left-right"]) 74 | # total number of commits 75 | parts["distance"] = len(git_rev_list.split()) 76 | 77 | return parts 78 | 79 | 80 | if __name__ == "__main__": 81 | if not len(sys.argv) > 1: 82 | panic("Missing mode. Expected one of 'parts', 'local', 'dev'.") 83 | 84 | mode = sys.argv[1].lower() 85 | 86 | parts = get_version_parts() 87 | 88 | if mode == "parts": 89 | print(parts) 90 | 91 | elif mode == "local": 92 | print("{closest_tag}+{distance}.{short_hash}".format(**parts)) 93 | 94 | elif mode == "dev": 95 | # bump patch on closet tag 96 | tag_parts = parts["closest_tag"].split(".") 97 | tag_parts[-1] = str(int(tag_parts[-1]) + 1) 98 | patch_tag = ".".join(tag_parts) 99 | 100 | print("{}.dev{timestamp}".format(patch_tag, **parts)) 101 | 102 | else: 103 | panic(f"Invalid mode {mode!r}. Expected one of 'parts', 'local', 'dev'.") 104 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | env: 4 | POETRY_VERSION: "1.6.1" 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | types: 12 | - opened 13 | - reopened 14 | - synchronize 15 | - labeled 16 | branches: 17 | - main 18 | 19 | jobs: 20 | build-and-publish: 21 | name: Publish test release 22 | runs-on: ubuntu-latest 23 | outputs: 24 | build-version: ${{ steps.build.outputs.version }} 25 | 26 | if: github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'test-build') 27 | 28 | steps: 29 | - uses: actions/checkout@v3 30 | with: 31 | persist-credentials: false 32 | fetch-depth: 0 33 | 34 | - name: Set up Python 3.10 35 | uses: actions/setup-python@v4 36 | with: 37 | python-version: "3.10" 38 | 39 | - name: Set up Poetry 40 | run: | 41 | pip install poetry==${{ env.POETRY_VERSION }} 42 | 43 | - name: Install local poetry-relax 44 | run: | 45 | poetry self add $(pwd) 46 | 47 | - name: Relax constraints 48 | run: | 49 | poetry relax --check 50 | 51 | - name: Publish to Test PyPI 52 | id: build 53 | env: 54 | POETRY_PYPI_TOKEN_TEST_PYPI: ${{ secrets.TEST_PYPI_API_TOKEN }} 55 | run: | 56 | version=$(./scripts/version dev) 57 | echo "::set-output name=version::$version" 58 | poetry version $version 59 | poetry config repositories.test-pypi https://test.pypi.org/legacy/ 60 | poetry publish --build -r test-pypi 61 | 62 | test-install: 63 | # We test the install on a clean machine to avoid poetry behavior attempting to 64 | # install the project root when it is checked out 65 | name: Test install 66 | runs-on: ubuntu-latest 67 | needs: build-and-publish 68 | timeout-minutes: 5 69 | 70 | steps: 71 | - name: Set up Python 3.10 72 | uses: actions/setup-python@v4 73 | with: 74 | python-version: "3.10" 75 | 76 | - name: Set up Poetry 77 | run: | 78 | pip install poetry==${{ env.POETRY_VERSION }} 79 | poetry init --name 'test-project' --no-interaction 80 | poetry source add test-pypi https://test.pypi.org/simple/ --priority=explicit 81 | 82 | - name: Wait for package to be available 83 | run: > 84 | until 85 | curl --silent "https://test.pypi.org/simple/poetry-relax/" 86 | | grep --quiet "${{ needs.build-and-publish.outputs.build-version }}"; 87 | do sleep 10; 88 | done 89 | && 90 | sleep 60 91 | # We sleep for an additional 60 seconds as it seems to take a bit longer for 92 | # the package to be consistently available 93 | 94 | # Note: The above will not sleep forever due to the job level timeout 95 | 96 | - name: Install release from Test PyPI 97 | run: > 98 | poetry add 99 | --source test-pypi 100 | poetry-relax==${{ needs.build-and-publish.outputs.build-version }} 101 | 102 | - name: Check release version 103 | run: | 104 | installed=$(poetry run python -c "import pkg_resources; print(pkg_resources.get_distribution('poetry_relax').version)") 105 | test $installed = ${{ needs.build-and-publish.outputs.build-version }} 106 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | env: 4 | # Enable colored output for pytest 5 | # https://github.com/pytest-dev/pytest/issues/7443 6 | # https://github.com/actions/runner/issues/241 7 | PY_COLORS: 1 8 | 9 | on: 10 | pull_request: 11 | types: [opened, reopened, synchronize] 12 | push: 13 | branches: 14 | - main 15 | 16 | permissions: 17 | contents: read 18 | 19 | # Limit concurrency by workflow/branch combination. 20 | # 21 | # For pull request builds, pushing additional changes to the 22 | # branch will cancel prior in-progress and pending builds. 23 | # 24 | # For builds triggered on a branch push, additional changes 25 | # will wait for prior builds to complete before starting. 26 | # 27 | # https://docs.github.com/en/actions/using-jobs/using-concurrency 28 | concurrency: 29 | group: ${{ github.workflow }}-${{ github.ref }} 30 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 31 | 32 | jobs: 33 | python-tests: 34 | name: Python ${{ matrix.python-version }}, Poetry ${{ matrix.poetry-version}}, ${{ matrix.os }} 35 | 36 | strategy: 37 | matrix: 38 | os: 39 | - "Ubuntu" 40 | python-version: 41 | - "3.8" 42 | - "3.9" 43 | - "3.10" 44 | - "3.11" 45 | - "3.12" 46 | poetry-version: 47 | - "1.2" 48 | - "1.3" 49 | - "1.4" 50 | - "1.5" 51 | - "1.6" 52 | - "1.7" 53 | 54 | include: 55 | 56 | # Run 3.12 tests with relaxed constraints 57 | - python-version: "3.12" 58 | os: "Ubuntu" 59 | relax: true 60 | 61 | # Run Windows and macOS tests for a single Poetry/Python pair 62 | - os: "Windows" 63 | python-version: "3.12" 64 | poetry-version: "1.7" 65 | 66 | - os: "macOS" 67 | python-version: "3.12" 68 | poetry-version: "1.7" 69 | 70 | exclude: 71 | # The following Poetry versions do not support Python 3.12 72 | - python-version: "3.12" 73 | poetry-version: 1.2 74 | - python-version: "3.12" 75 | poetry-version: 1.3 76 | - python-version: "3.12" 77 | poetry-version: 1.4 78 | 79 | fail-fast: false 80 | 81 | runs-on: ${{ matrix.os }}-latest 82 | timeout-minutes: 10 83 | 84 | steps: 85 | - uses: actions/checkout@v3 86 | with: 87 | persist-credentials: false 88 | fetch-depth: 0 89 | 90 | - name: Set up Python ${{ matrix.python-version }} 91 | uses: actions/setup-python@v4 92 | with: 93 | python-version: ${{ matrix.python-version }} 94 | # TODO: This appears to require poetry to be installed before usage 95 | # cache: "poetry" 96 | 97 | - name: Install Poetry ${{ matrix.poetry-version }} 98 | run: | 99 | pip install "poetry~=${{ matrix.poetry-version }}.0" 100 | 101 | # Ensure that Poetry is not upgraded past the version we are testing 102 | poetry add "poetry@~${{ matrix.poetry-version }}" --lock 103 | 104 | - name: Install packages 105 | run: | 106 | poetry install 107 | 108 | - name: Relax constraints 109 | if: ${{ matrix.relax }} 110 | run: | 111 | # Install the plugin 112 | poetry self add $(pwd) 113 | poetry relax --update 114 | 115 | - name: Run tests 116 | run: | 117 | poetry run -- pytest tests 118 | -------------------------------------------------------------------------------- /tests/_utilities.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from contextlib import contextmanager 4 | from functools import partial 5 | from pathlib import Path 6 | from typing import Any, Dict, Generator, Optional, Union 7 | 8 | import tomlkit 9 | from cleo.commands.command import Command 10 | from cleo.io.outputs.output import Verbosity 11 | from cleo.testers.command_tester import CommandTester as _CommandTester 12 | from poetry.console.application import Application as PoetryApplication 13 | from poetry.core.packages.dependency_group import MAIN_GROUP 14 | from poetry.utils.env import EnvManager 15 | 16 | from poetry_relax._core import PoetryConsoleError 17 | 18 | PYPROJECT = "pyproject.toml" 19 | LOCKFILE = "poetry.lock" 20 | 21 | 22 | def load_tomlfile(path: Union[str, Path] = "./") -> tomlkit.TOMLDocument: 23 | return tomlkit.loads(Path(path).read_text()) 24 | 25 | 26 | @contextmanager 27 | def update_tomlfile( 28 | file: Union[str, Path], 29 | ) -> Generator[tomlkit.TOMLDocument, None, None]: 30 | """ 31 | Updates a toml file by reading then yielding the existing contents for mutation. 32 | """ 33 | project_config = load_tomlfile(file) 34 | yield project_config 35 | Path(file).write_text(tomlkit.dumps(project_config)) 36 | 37 | 38 | @contextmanager 39 | def assert_tomlfile_matches( 40 | file: Union[str, Path], 41 | ) -> Generator[tomlkit.TOMLDocument, None, None]: 42 | """ 43 | Asserts that the toml file in the given directory (defaults to current) 44 | is matches the yielded object after the duration of the context. 45 | 46 | Yields the initial contents of the file which can be mutated for comparison. 47 | """ 48 | project_config = load_tomlfile(file) 49 | yield project_config 50 | new_project_config = load_tomlfile(file) 51 | assert project_config == new_project_config 52 | 53 | 54 | @contextmanager 55 | def assert_tomlfile_unchanged(file: Union[str, Path]) -> Generator[None, None, None]: 56 | """ 57 | Asserts that the toml file in the given directory (defaults to current) 58 | is unchanged during the duration of the context. 59 | """ 60 | with assert_tomlfile_matches(file): 61 | yield 62 | 63 | 64 | # Aliases for test readability 65 | 66 | assert_pyproject_unchanged = partial(assert_tomlfile_unchanged, PYPROJECT) 67 | assert_pyproject_matches = partial(assert_tomlfile_matches, PYPROJECT) 68 | update_pyproject = partial(update_tomlfile, PYPROJECT) 69 | assert_lockfile_unchanged = partial(assert_tomlfile_unchanged, LOCKFILE) 70 | assert_lockfile_matches = partial(assert_tomlfile_matches, LOCKFILE) 71 | load_lockfile = partial(load_tomlfile, LOCKFILE) 72 | load_pyproject = partial(load_tomlfile, PYPROJECT) 73 | 74 | 75 | def load_lockfile_packages() -> Dict[str, dict]: 76 | """ 77 | Returns a mapping of package names to package information in the current lockfile 78 | """ 79 | lockfile = load_lockfile() 80 | return {package["name"]: package for package in lockfile["package"]} 81 | 82 | 83 | @contextmanager 84 | def tmpchdir(new_dir: Union[str, Path]) -> Generator[None, None, None]: 85 | pwd = os.getcwd() 86 | os.chdir(new_dir) 87 | try: 88 | yield 89 | finally: 90 | os.chdir(pwd) 91 | 92 | 93 | def get_dependency_group( 94 | tomlfile_config: tomlkit.TOMLDocument, group: str = MAIN_GROUP 95 | ) -> Dict[str, str]: 96 | """ 97 | Retrieve a dependency group from the poetry tool config in a tomlfile document. 98 | 99 | Defaults to the "main" group. 100 | 101 | The given tomlfile document may be modified to create empty collections as needed. 102 | """ 103 | poetry_config = tomlfile_config["tool"]["poetry"] 104 | 105 | if group == MAIN_GROUP: 106 | if "dependencies" not in poetry_config: 107 | poetry_config["dependencies"] = tomlkit.table() 108 | 109 | return poetry_config["dependencies"] 110 | else: 111 | if "group" not in poetry_config: 112 | poetry_config["group"] = tomlkit.table(is_super_table=True) 113 | 114 | groups = poetry_config["group"] 115 | if group not in groups: 116 | dependencies_toml: dict[str, Any] = tomlkit.parse( 117 | f"[tool.poetry.group.{group}.dependencies]\n\n" 118 | ) 119 | group_table = dependencies_toml["tool"]["poetry"]["group"][group] 120 | poetry_config["group"][group] = group_table 121 | 122 | if "dependencies" not in poetry_config["group"][group]: 123 | poetry_config["group"][group]["dependencies"] = tomlkit.table() 124 | 125 | return poetry_config["group"][group]["dependencies"] 126 | 127 | 128 | def assert_io_contains(content: str, io) -> None: 129 | output = io.fetch_output() 130 | # Ensure the output can be retrieved again later 131 | io.fetch_output = lambda: output 132 | assert content in output 133 | 134 | 135 | # Backport Path.is_relative_to from Python 3.9+ to older 136 | 137 | if sys.version_info < (3, 9): 138 | 139 | def check_paths_relative(self, *other): 140 | try: 141 | self.relative_to(*other) 142 | return True 143 | except ValueError: 144 | return False 145 | 146 | else: 147 | check_paths_relative = Path.is_relative_to 148 | 149 | 150 | class PoetryCommandTester(_CommandTester): 151 | def __init__(self, command: Command, application: PoetryApplication) -> None: 152 | super().__init__(command) 153 | self.configure_for_application(application) 154 | 155 | def configure_for_application(self, application: PoetryApplication): 156 | self._application = application 157 | application.add(self.command) 158 | 159 | manager = EnvManager(poetry=application.poetry) 160 | env = manager.get(reload=True) 161 | 162 | # The following is necessary to set up the command and is usually handled by 163 | # poetry.console.application.Application.__init__ on command dispatch. The 164 | # tester appears to bypass these handlers so we duplicate the setup here 165 | self.command.set_env(env) 166 | application.configure_installer_for_command(self.command, self.io) 167 | 168 | def execute( 169 | self, 170 | args: str = "", 171 | inputs: Optional[str] = None, 172 | interactive: Optional[bool] = None, 173 | verbosity: Optional[Verbosity] = None, 174 | decorated: Optional[bool] = None, 175 | supports_utf8: bool = True, 176 | ) -> int: 177 | # Reload the application to ensure that project changes are reflected 178 | self._application.reset_poetry() 179 | 180 | try: 181 | return super().execute( 182 | args, inputs, interactive, verbosity, decorated, supports_utf8 183 | ) 184 | except PoetryConsoleError as exc: 185 | # Typically handling by poetry, but we need to handle it manually in our 186 | # testing 187 | self.io.write_line(str(exc)) 188 | self._status_code = getattr(exc, "exit_code", None) or 1 189 | return self._status_code 190 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Latest version 4 | 5 | 6 | Supported Python versions 7 | 8 | 9 | Test status 10 | 11 | 12 | Build status 13 | 14 |

15 | 16 | # poetry-relax 17 | 18 | A [Poetry](https://github.com/python-poetry/poetry) plugin to relax dependency versions when publishing libraries. Relax your project's dependencies from `foobar^2.0.0` to `foobar>=2.0.0`. 19 | 20 | By default, Poetry uses caret constraints which would limit `foobar` versions to `<3.0`. 21 | **poetry-relax** removes these upper version bounds, allowing dependencies to be upgraded. 22 | 23 | Removing upper version bounds is important when publishing libraries. 24 | When searching for versions of dependencies to install, the resolver (e.g. `pip`) must respect the bounds your library specifies. 25 | When a new version of the dependency is released, consumers of your library _cannot_ install it unless a new version of your library is also released. 26 | 27 | It is not feasible to release patches for every previous version of most libraries, which forces users to use the most recent version of the library or be stuck without the new version of the dependency. 28 | When many libraries contain upper version bounds, the dependencies can easily become _unsolvable_ — where libraries have incompatible dependency version requirements. 29 | By removing upper version bounds from your library, control is returned to the user. 30 | 31 | Poetry's default behavior is to include upper version bounds. Many people have spoken up against this style of dependency management in the Python ecosystem, including members of the Python core development team. See [the bottom of the readme](#references) for links and additional context. 32 | 33 | Since the Poetry project will not allow this behavior to be configured, maintainers have resorted to manual editing of dependency constraints after adding. **poetry-relax** aims to automate and simplify this process. 34 | 35 | **poetry-relax** provides: 36 | - Automated removal of upper bound constraints specified with `^` 37 | - Safety check if package requirements are still solvable after relaxing constraints 38 | - Upgrade of dependencies after relaxing constraints 39 | - Update of the lock file without upgrading dependencies 40 | - Limit dependency relaxation to specific dependency groups 41 | - Retention of intentional upper bounds indicating true incompatibilities 42 | - CLI messages designed to match Poetry's output 43 | 44 | ## Installation 45 | 46 | The plugin must be installed in Poetry's environment. This requires use of the `self` subcommand. 47 | 48 | ```bash 49 | $ poetry self add poetry-relax 50 | ``` 51 | 52 | ## Usage 53 | 54 | Relax constraints for which Poetry set an upper version: 55 | 56 | ```bash 57 | $ poetry relax 58 | ``` 59 | 60 | Relax constraints and check that they are resolvable without performing upgrades: 61 | 62 | ```bash 63 | $ poetry relax --check 64 | ``` 65 | 66 | Relax constraints and upgrade packages: 67 | 68 | ```bash 69 | $ poetry relax --update 70 | ``` 71 | 72 | Relax constraints and update the lock file without upgrading packages: 73 | 74 | ```bash 75 | $ poetry relax --lock 76 | ``` 77 | 78 | Preview the changes `poetry relax` would make without modifying the project: 79 | 80 | ```bash 81 | $ poetry relax --dry-run 82 | ``` 83 | 84 | Relax constraints for specific dependency groups: 85 | 86 | ```bash 87 | $ poetry relax --only foo --only bar 88 | ``` 89 | 90 | Relax constraints excluding specific dependency groups: 91 | 92 | ```bash 93 | $ poetry relax --without foo --without bar 94 | ``` 95 | 96 | 97 | ## Examples 98 | 99 | The behavior of Poetry is quite reasonable for local development! `poetry relax` is most useful when used in CI/CD pipelines. 100 | 101 | ### Relaxing requirements before publishing 102 | 103 | Run `poetry relax` before building and publishing a package. 104 | 105 | See it at work in [the release workflow for this project](https://github.com/zanieb/poetry-relax/blob/main/.github/workflows/release.yaml). 106 | 107 | 108 | ### Relaxing requirements for testing 109 | 110 | Run `poetry relax --update` before tests to test against the newest possible versions of packages. 111 | 112 | See it at work in [the test workflow for this project](https://github.com/zanieb/poetry-relax/blob/main/.github/workflows/test.yaml). 113 | 114 | ## Frequently asked questions 115 | 116 | > Can this plugin change the behavior of `poetry add` to relax constraints? 117 | 118 | Not at this time. The Poetry project states that plugins must not alter the behavior of core Poetry commands. If this behavior would be useful for you, please chime in [on the tracking issue](https://github.com/zanieb/poetry-relax/issues/5). 119 | 120 | > Does this plugin remove upper constraints I've added? 121 | 122 | This plugin will only relax constraints specified with a caret (`^`). Upper constraints added with `<` and `<=` will not be changed. 123 | 124 | > Is this plugin stable? 125 | 126 | This plugin is tested against multiple versions of Poetry and has an integration focused test suite. It is safe to use this in production, though it is recommend to pin versions. Breaking changes will be avoided unless infeasible due to upstream changes in Poetry. This project follows the semantic versioning scheme and breaking changes will be denoted by a change to the major version number. 127 | 128 | > Will this plugin drop the upper bound on Python itself? 129 | 130 | Believe it or not, this is an even more contentious subset of this issue as Poetry will not allow packages with no upper bound on Python to exist alongside those that include one. This basically means that we cannot relax this requirement without breaking the vast majority of use-cases. For this reason, we cannot relax `python^3` at this time. See [the post on the Poetry discussion board](https://github.com/python-poetry/poetry/discussions/3757#discussioncomment-435345) for more details. 131 | 132 | ## Contributing 133 | 134 | This project is managed with Poetry. Here are the basics for getting started. 135 | 136 | Clone the repository: 137 | ```bash 138 | $ git clone https://github.com/zanieb/poetry-relax.git 139 | $ cd poetry-relax 140 | ``` 141 | 142 | Install packages: 143 | ```bash 144 | $ poetry install 145 | ``` 146 | 147 | Run the test suite: 148 | ```bash 149 | $ pytest tests 150 | ``` 151 | 152 | Run linters before opening pull requests: 153 | ```bash 154 | $ ./scripts/lint check . 155 | $ ./scripts/lint fix . 156 | ``` 157 | 158 | ## References 159 | 160 | There's a lot to read on this topic! It's contentious and causing a lot of problems for maintainers and users. 161 | 162 | The following blog posts by Henry Schreiner are quite comprehensive: 163 | - [Should You Use Upper Bound Version Constraints?](https://iscinumpy.dev/post/bound-version-constraints/) 164 | - [Poetry Versions](https://iscinumpy.dev/post/poetry-versions/) 165 | 166 | Content from some members of the Python core developer team: 167 | - [Semantic Versioning Will Not Save You](https://hynek.me/articles/semver-will-not-save-you/) 168 | - [Why I don't like SemVer anymore](https://snarky.ca/why-i-dont-like-semver/) 169 | - [Version numbers: how to use them?](https://bernat.tech/posts/version-numbers/) 170 | - [Versioning Software](https://caremad.io/posts/2016/02/versioning-software/) 171 | 172 | Discussion and issues in the Poetry project: 173 | - [Please stop pinning to major versions by default, especially for Python itself](https://github.com/python-poetry/poetry/issues/3747) 174 | - [Change default dependency constraint from ^ to >=](https://github.com/python-poetry/poetry/issues/3427) 175 | - [Developers should be able to turn off dependency upper-bound calculations](https://github.com/python-poetry/poetry/issues/2731) 176 | -------------------------------------------------------------------------------- /tests/_fixtures.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | import sys 5 | import tempfile 6 | from pathlib import Path 7 | from typing import Callable 8 | 9 | import packaging.version 10 | import pytest 11 | from poetry.console.application import Application as PoetryApplication 12 | from poetry.utils.env import EnvManager, VirtualEnv 13 | 14 | from poetry_relax._core import POETRY_VERSION 15 | 16 | from ._utilities import check_paths_relative, tmpchdir 17 | 18 | 19 | @pytest.fixture(scope="session") 20 | def poetry_cache_directory() -> Path: 21 | with tempfile.TemporaryDirectory(prefix="poetry-relax-test-cache") as tmpdir: 22 | yield Path(tmpdir) 23 | 24 | 25 | @pytest.fixture(scope="session") 26 | def poetry_application_factory( 27 | poetry_cache_directory: Path, 28 | ) -> Callable[[], PoetryApplication]: 29 | # Defined as a factory so we can use it in other session-scoped fixtures while 30 | # retaining independent instances per test 31 | def factory() -> PoetryApplication: 32 | application = PoetryApplication() 33 | application.poetry.config.merge( 34 | { 35 | "cache-dir": str(poetry_cache_directory), 36 | "virtualenvs": { 37 | "in-project": True, 38 | "system-site-packages": False, 39 | }, 40 | } 41 | ) 42 | return application 43 | 44 | yield factory 45 | 46 | 47 | @pytest.fixture 48 | def poetry_application( 49 | poetry_application_factory: Callable[[], PoetryApplication], 50 | poetry_project_path: Path, 51 | ) -> PoetryApplication: 52 | application = poetry_application_factory() 53 | 54 | # There are a few assertions in this style, as these fixtures were finicky to get 55 | # behaving correctly and we want to ensure our assumptions are correct before 56 | # tests run 57 | assert check_paths_relative(application.poetry.file.path, poetry_project_path), f""" 58 | The poetry application's config file should be relative to the test project path: 59 | {poetry_project_path} 60 | but the following path was found: 61 | {application.poetry.file.path}" 62 | """ 63 | 64 | yield application 65 | 66 | 67 | @pytest.fixture(scope="session") 68 | def base_poetry_project_path( 69 | poetry_application_factory: Callable[[], PoetryApplication], 70 | ) -> Path: 71 | with tempfile.TemporaryDirectory(prefix="poetry-relax-test-base") as tmpdir: 72 | # Create virtual environments in the temporary project 73 | os.environ["POETRY_VIRTUALENVS_IN_PROJECT"] = "true" 74 | 75 | init_process = subprocess.run( 76 | ["poetry", "init", "--no-interaction"], 77 | cwd=tmpdir, 78 | stderr=subprocess.PIPE, 79 | stdout=sys.stdout, 80 | ) 81 | 82 | try: 83 | init_process.check_returncode() 84 | except subprocess.CalledProcessError as exc: 85 | init_error = init_process.stderr.decode().strip() 86 | raise RuntimeError( 87 | f"Failed to initialize test project: {init_error}" 88 | ) from exc 89 | 90 | # Hide that we are in a virtual environment already or Poetry will be refuse to 91 | # use one in the directory later 92 | os.environ.pop("VIRTUAL_ENV", None) 93 | 94 | # Create a virtual environment 95 | with tmpchdir(tmpdir): 96 | application = poetry_application_factory() 97 | env_manager = EnvManager(application.poetry) 98 | 99 | # Poetry expects a `str` in earlier versions 100 | if POETRY_VERSION < packaging.version.Version("1.4.0"): 101 | executable = sys.executable 102 | else: 103 | executable = Path(sys.executable) 104 | 105 | env = env_manager.create_venv( 106 | application.create_io(), 107 | executable, 108 | # Force required to create it despite tests generally being run inside a 109 | # virtual environment 110 | force=True, 111 | ) 112 | 113 | tmp_path = Path(tmpdir).resolve() 114 | assert check_paths_relative(env.path.resolve(), tmp_path), f""" 115 | The virtual environment in the base test project should be in the 116 | temporary directory: 117 | {tmp_path} 118 | but was created at: 119 | {env.path}" 120 | """ 121 | 122 | yield tmp_path 123 | 124 | 125 | @pytest.fixture 126 | def poetry_project_path(base_poetry_project_path: Path, tmp_path: Path) -> Path: 127 | project_path = tmp_path / "project" 128 | print(f"Creating test project at {project_path}") 129 | 130 | # Copy the initialized project into a clean temp directory 131 | shutil.copytree(base_poetry_project_path, project_path) 132 | 133 | # Change the working directory for the duration of the test 134 | with tmpchdir(project_path): 135 | yield project_path 136 | 137 | 138 | @pytest.fixture(scope="session") 139 | def seeded_base_poetry_project_path( 140 | base_poetry_project_path: Path, 141 | seeded_cloudpickle_version, 142 | seeded_typing_extensions_version, 143 | ) -> Path: 144 | with tempfile.TemporaryDirectory(prefix="poetry-relax-test-seeded-base") as tmpdir: 145 | seeded_base = Path(tmpdir).resolve() / "seeded-base" 146 | 147 | print(f"Creating base seeded project at {seeded_base}") 148 | 149 | # Copy the initialized project into a the directory 150 | shutil.copytree(base_poetry_project_path, seeded_base, symlinks=True) 151 | 152 | print(f"Installing 'cloudpickle=={seeded_cloudpickle_version}'") 153 | seed_process = subprocess.run( 154 | [ 155 | "poetry", 156 | "add", 157 | f"cloudpickle=={seeded_cloudpickle_version}", 158 | "importlib_metadata>=1.0", # Needed for checking installed package versions 159 | "--no-interaction", 160 | "-v", 161 | ], 162 | cwd=seeded_base, 163 | stderr=subprocess.PIPE, 164 | stdout=sys.stdout, 165 | env={**os.environ}, 166 | ) 167 | 168 | try: 169 | seed_process.check_returncode() 170 | except subprocess.CalledProcessError as exc: 171 | seed_error = seed_process.stderr.decode().strip() 172 | raise RuntimeError(f"Failed to seed test project: {seed_error}") from exc 173 | 174 | print() # Poetry does not print newlines at the end of install 175 | 176 | print( 177 | f"Installing 'typing_extensions=={seeded_typing_extensions_version}' in group 'dev'" 178 | ) 179 | seed_process = subprocess.run( 180 | [ 181 | "poetry", 182 | "add", 183 | f"typing_extensions=={seeded_typing_extensions_version}", 184 | "--group", 185 | "dev", 186 | "--no-interaction", 187 | "-v", 188 | ], 189 | cwd=seeded_base, 190 | stderr=subprocess.PIPE, 191 | stdout=sys.stdout, 192 | env={**os.environ}, 193 | ) 194 | 195 | try: 196 | seed_process.check_returncode() 197 | except subprocess.CalledProcessError as exc: 198 | seed_error = seed_process.stderr.decode().strip() 199 | raise RuntimeError(f"Failed to seed test project: {seed_error}") from exc 200 | 201 | print() # Poetry does not print newlines at the end of install 202 | 203 | yield seeded_base 204 | 205 | 206 | @pytest.fixture(scope="session") 207 | def seeded_cloudpickle_version() -> str: 208 | yield "0.1.1" 209 | 210 | 211 | @pytest.fixture(scope="session") 212 | def seeded_typing_extensions_version() -> str: 213 | yield "3.6.2" 214 | 215 | 216 | @pytest.fixture 217 | def seeded_poetry_project_path( 218 | seeded_base_poetry_project_path: Path, 219 | tmp_path: Path, 220 | ) -> Path: 221 | project_path = tmp_path / "seeded-project" 222 | print(f"Creating seeded test project at {project_path}") 223 | 224 | # Copy the initialized project into a clean temp directory 225 | shutil.copytree(seeded_base_poetry_project_path, project_path, symlinks=True) 226 | 227 | # Change the working directory for the duration of the test 228 | with tmpchdir(project_path): 229 | yield project_path 230 | 231 | 232 | @pytest.fixture 233 | def seeded_project_venv( 234 | seeded_poetry_project_path: Path, poetry_application_factory: PoetryApplication 235 | ) -> VirtualEnv: 236 | if sys.platform == "win32": 237 | executable = seeded_poetry_project_path / ".venv" / "Scripts" / "python.exe" 238 | else: 239 | executable = seeded_poetry_project_path / ".venv" / "bin" / "python" 240 | 241 | assert executable.exists(), f""" 242 | The virtual environment should exist in the test project path but was not found at: 243 | {executable}" 244 | """ 245 | 246 | manager = EnvManager(poetry=poetry_application_factory().poetry) 247 | 248 | print(f"Loading virtual environment at {executable}") 249 | env = manager.get(reload=True) 250 | 251 | assert check_paths_relative(env.path, seeded_poetry_project_path), f""" 252 | The virtual environment in the test project should be in the project path: 253 | {seeded_poetry_project_path} 254 | but the following path was activated: 255 | {env.path}" 256 | """ 257 | 258 | # This will throw an exception if it fails 259 | env.run_python_script("import cloudpickle") 260 | 261 | yield env 262 | -------------------------------------------------------------------------------- /src/poetry_relax/_core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core utilities for the `poetry relax` functionality. 3 | """ 4 | 5 | import contextlib 6 | import functools 7 | import re 8 | import sys 9 | from copy import copy 10 | from typing import ( 11 | TYPE_CHECKING, 12 | Callable, 13 | Dict, 14 | Generator, 15 | Iterable, 16 | List, 17 | Optional, 18 | Tuple, 19 | Union, 20 | ) 21 | 22 | import packaging.version 23 | from poetry.core.factory import Factory 24 | from poetry.core.packages.dependency_group import MAIN_GROUP 25 | 26 | if TYPE_CHECKING: 27 | # See https://github.com/python-poetry/cleo/pull/254 for ignore 28 | from cleo.io.io import IO # type: ignore 29 | from poetry.core.factory import DependencyConfig, DependencyConstraint 30 | from poetry.core.packages.dependency import Dependency 31 | from poetry.installation.installer import Installer 32 | from poetry.poetry import Poetry 33 | 34 | 35 | if sys.version_info < (3, 8): # Python 3.7 support 36 | import pkg_resources 37 | 38 | POETRY_VERSION = packaging.version.Version( 39 | pkg_resources.get_distribution("poetry").version 40 | ) 41 | else: 42 | import importlib.metadata as importlib_metadata 43 | 44 | POETRY_VERSION = packaging.version.Version(importlib_metadata.version("poetry")) 45 | 46 | if POETRY_VERSION < packaging.version.Version("1.3.0"): 47 | from poetry.core.semver.version_range import VersionRange # type: ignore 48 | else: 49 | from poetry.core.constraints.version import VersionRange 50 | 51 | if POETRY_VERSION < packaging.version.Version("1.3.0"): 52 | # Poetry 1.2.x defined a different name for Cleo 1.x 53 | # isort: off 54 | from poetry.console.exceptions import ( # type: ignore 55 | PoetrySimpleConsoleException as PoetryConsoleError, 56 | ) 57 | else: 58 | from poetry.console.exceptions import PoetryConsoleError # noqa: F401 59 | 60 | 61 | # Regular expressions derived from `poetry.core.semver.helpers.parse_constraint` 62 | # These are used to parse complex constraint strings into single constraints 63 | AND_CONSTRAINT_SEPARATORS = re.compile( 64 | r"((?< ,]) *(? int: 98 | """ 99 | Run an installer update. 100 | 101 | Ensures that any existing dependencies in the given groups are replaced with the new 102 | dependencies if their names match. 103 | 104 | New dependencies are also whitelisted to be updated during locking. 105 | """ 106 | all_dependencies = [] 107 | 108 | for group_name, dependencies in dependencies_by_group.items(): 109 | group = poetry.package.dependency_group(group_name) 110 | 111 | # Ensure if we are given a generator that we can consume it more than once 112 | dependencies = list(dependencies) 113 | 114 | for dependency in dependencies: 115 | with contextlib.suppress(ValueError): 116 | group.remove_dependency(dependency.name) 117 | group.add_dependency(dependency) 118 | 119 | all_dependencies.extend(dependencies) 120 | 121 | # Refresh the locker 122 | poetry.set_locker(poetry.locker.__class__(poetry.locker.lock, poetry_config)) 123 | installer.set_locker(poetry.locker) 124 | installer.only_groups(dependencies_by_group.keys()) 125 | installer.set_package(poetry.package) 126 | installer.dry_run(dry_run) 127 | installer.verbose(verbose) 128 | installer.update() 129 | 130 | if lockfile_only: 131 | installer.lock() 132 | 133 | installer.whitelist([d.name for d in all_dependencies]) 134 | 135 | last_line: str = "" 136 | 137 | def update_messages_for_dry_run(write, message, **kwargs): 138 | nonlocal last_line 139 | 140 | # Prevent duplicate messages unless they're whitespace 141 | # TODO: Determine the root cause of duplicates 142 | if message.strip() and message == last_line: 143 | return 144 | last_line = message 145 | 146 | if dry_run: 147 | message = message.replace("Updating", "Would update") 148 | message = message.replace("Installing", "Checking") 149 | message = message.replace("Skipped", "Would skip") 150 | 151 | return write(message, **kwargs) 152 | 153 | def silence(*args, **kwargs): 154 | pass 155 | 156 | with patch_io_writes( 157 | installer._io, 158 | silence if silent else update_messages_for_dry_run, # type: ignore 159 | ): 160 | return installer.run() 161 | 162 | 163 | def extract_dependency_config_for_group( 164 | group: str, poetry_config: dict 165 | ) -> Optional["DependencyConfig"]: 166 | """ 167 | Retrieve the dictionary of dependencies defined for the given group in the poetry 168 | config. 169 | 170 | Returns `None` if the group does not exist or does not have any dependencies. 171 | """ 172 | if group == MAIN_GROUP: 173 | return poetry_config.get("dependencies", None) 174 | 175 | return poetry_config.get("group", {}).get(group, {}).get("dependencies", None) 176 | 177 | 178 | def flattened_dependency_config_items( 179 | config: "DependencyConfig", 180 | ) -> Generator[Tuple[str, "DependencyConstraint"], None, None]: 181 | """ 182 | Flatten dependencies with multiple constraints into separate dependencies. 183 | """ 184 | for name, constraints in config.items(): 185 | if isinstance(constraints, list): 186 | for constraint in constraints: 187 | yield name, constraint 188 | else: 189 | yield name, constraints 190 | 191 | 192 | def drop_upper_bound_from_version_range(constraint: VersionRange) -> VersionRange: 193 | """ 194 | Drop the upper bound from a version range constraint. 195 | """ 196 | return VersionRange(constraint.min, max=None, include_min=constraint.include_min) 197 | 198 | 199 | def mutate_constraint(constraints: str, callback: Callable[[str], str]) -> str: 200 | """ 201 | Given a string of constraints, parse into single constraints, replace each one with 202 | the result of `callback`, then join into the original constraint string. 203 | 204 | Attempts to support modification of parts of constraint strings with minimal 205 | changes to the original format. 206 | 207 | Trailing and leading whitespace will be stripped. 208 | """ 209 | # If the poetry helpers were used to parse the constraints, the user's constraints 210 | # can be modified which can be undesirable. For example, ">2.5,!=2.7" would be 211 | # changed to ">2.5,<2.7 || > 2.7". 212 | if constraints == "*": 213 | return callback(constraints) 214 | 215 | # Parse _or_ expressions first 216 | or_constraints = re.split(OR_CONSTRAINT_SEPARATORS, constraints.strip()) 217 | 218 | # Note a capture group was used so re.split returns the captured separators as well 219 | # We need to retain these for joining the string after callbacks are performed 220 | # It's easiest to just mutate the lists rather than performing fancy zips 221 | for i in range(0, len(or_constraints), 2): 222 | # Parse _and_ expressions 223 | and_constraints = re.split( 224 | AND_CONSTRAINT_SEPARATORS, 225 | # Trailing `,` allowed but not retained — following Poetry internals 226 | or_constraints[i].rstrip(",").strip(), 227 | ) 228 | 229 | # If there are no _and_ expressions, this will still be called once 230 | for j in range(0, len(and_constraints), 2): 231 | and_constraints[j] = callback(and_constraints[j]) 232 | 233 | or_constraints[i] = "".join(and_constraints) 234 | 235 | return "".join(or_constraints) 236 | 237 | 238 | def drop_upper_bound_from_caret_constraint(constraint: str) -> str: 239 | """ 240 | Replace a caret constraint string with an equivalent lower-bound only constraint. 241 | 242 | If the constraint is not a caret constraint, it will be returned unchanged. 243 | """ 244 | if constraint.startswith("^"): 245 | return constraint.replace("^", ">=", 1) 246 | else: 247 | return constraint 248 | 249 | 250 | def drop_caret_bound_from_dependency(dependency: "Dependency") -> "Dependency": 251 | """ 252 | Generate a new dependency with no upper bound from an existing dependency. 253 | 254 | If the dependency does not use a caret constraint to specify its upper bound, 255 | it will not be changed but a new copy will be returned. 256 | """ 257 | # If the constraint is empty, there is nothing to do 258 | if not dependency.pretty_constraint: 259 | return dependency 260 | 261 | new_version = mutate_constraint( 262 | dependency.pretty_constraint, drop_upper_bound_from_caret_constraint 263 | ) 264 | 265 | # Copy the existing dependency to retain as much information as possible 266 | new_dependency = copy(dependency) 267 | 268 | # If the constraint is empty, assignment will fail 269 | if not new_version: 270 | raise ValueError( 271 | f"Updating constraint for {dependency} resulted in an empty version" 272 | ) 273 | 274 | # Update the constraint to the new version 275 | # The property setter parses this into a proper constraint type 276 | new_dependency.constraint = new_version # type: ignore 277 | 278 | return new_dependency 279 | 280 | 281 | def update_dependency_config( 282 | config: Union[List["DependencyConstraint"], "DependencyConstraint"], 283 | dependency: "Dependency", 284 | ) -> Union[List["DependencyConstraint"], "DependencyConstraint"]: 285 | """ 286 | Update the configuration for a single dependency to use the constraints from a 287 | new dependency object. 288 | """ 289 | if isinstance(config, list): 290 | # When multiple constraints are given for a single dependency 291 | # we need to find out which one we've updated by checking for a matching marker 292 | return [ 293 | update_dependency_constraint(item, dependency) 294 | if ( 295 | Factory.create_dependency(dependency.name, item).marker 296 | == dependency.marker 297 | ) 298 | else item 299 | for item in config 300 | ] 301 | else: 302 | return update_dependency_constraint(config, dependency) 303 | 304 | 305 | def update_dependency_constraint( 306 | constraint: "DependencyConstraint", dependency: "Dependency" 307 | ) -> "DependencyConstraint": 308 | """ 309 | Update the constraint of a dependency config to use the constraint from a 310 | new dependency object. 311 | """ 312 | if isinstance(constraint, dict): 313 | new_constraint = constraint.copy() 314 | new_constraint["version"] = dependency.pretty_constraint 315 | return new_constraint 316 | else: 317 | return dependency.pretty_constraint 318 | -------------------------------------------------------------------------------- /src/poetry_relax/command.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import Any, Dict, List, Set, Tuple 3 | 4 | # cleo is not PEP 561 compliant must be ignored 5 | # See https://github.com/python-poetry/cleo/pull/254 6 | from cleo.helpers import option # type: ignore 7 | from packaging.version import Version 8 | from poetry.console.commands.installer_command import InstallerCommand 9 | from poetry.core.factory import Factory 10 | from poetry.core.packages.dependency import Dependency 11 | from tomlkit.toml_document import TOMLDocument 12 | 13 | from poetry_relax._core import ( 14 | POETRY_VERSION, 15 | PoetryConsoleError, 16 | drop_caret_bound_from_dependency, 17 | extract_dependency_config_for_group, 18 | flattened_dependency_config_items, 19 | run_installer_update, 20 | update_dependency_config, 21 | ) 22 | 23 | 24 | def _pretty_group(group: str) -> str: 25 | return f" in group {group!r}" 26 | 27 | 28 | class RelaxCommand(InstallerCommand): 29 | """ 30 | Implementation of `poetry relax`. 31 | """ 32 | 33 | # Inherits from `InstallerCommand` for access to internal utilities 34 | 35 | name = "relax" 36 | description = "Relax project dependencies." 37 | options = [ 38 | option( 39 | "only", 40 | description=( 41 | "A group to relax constraints in. If not provided, all groups are used" 42 | "; including optional groups." 43 | # If a group is specified, it is treated like the Poetry `--only` flag. 44 | ), 45 | flag=False, 46 | default=None, 47 | multiple=True, 48 | ), 49 | option( 50 | "group", 51 | "-G", 52 | flag=False, 53 | default=None, 54 | description=( 55 | "A group to relax constraints in." 56 | " (Deprecated; use `--only` instead.)" 57 | ), 58 | multiple=True, 59 | ), 60 | option( 61 | "without", 62 | description="A group to exclude from relaxing constraints.", 63 | flag=False, 64 | default=None, 65 | multiple=True, 66 | ), 67 | option( 68 | "dry-run", 69 | None, 70 | description=("Output the operations but do not execute anything."), 71 | ), 72 | option( 73 | "lock", 74 | None, 75 | description="Run a lock file update after changing the constraints.", 76 | ), 77 | option( 78 | "check", 79 | None, 80 | description=( 81 | "Check if versions are valid after changing the constraints by running " 82 | "the Poetry solver." 83 | ), 84 | ), 85 | option( 86 | "update", None, description="Run an update after changing the constraints." 87 | ), 88 | ] 89 | help = ( 90 | "The relax command removes upper version constraints designated by " 91 | "carets (^)." 92 | ) 93 | 94 | def _get_only_group_option(self): 95 | only = self.option("only") 96 | deprecated_groups = self.option("group") 97 | 98 | if deprecated_groups: 99 | self.line( 100 | "The `--group` option is deprecated; use `--only` instead." 101 | "" 102 | ) 103 | 104 | return set(only + deprecated_groups) 105 | 106 | def handle(self) -> int: 107 | """ 108 | The plugin entrypoint for the `poetry relax` command. 109 | """ 110 | 111 | # The following implemention relies heavily on internal Poetry objects and 112 | # is based on the `poetry add` implementation which is available under the MIT 113 | # license. 114 | 115 | # Read poetry file as a dictionary 116 | if self.io.is_verbose(): 117 | self.line(f"Using poetry file at {self.poetry.file.path}") 118 | pyproject_config: dict[str, Any] = self.poetry.file.read() 119 | poetry_config = pyproject_config["tool"]["poetry"] 120 | 121 | # Validate given groups using Poetry's internal handler 122 | self._validate_group_options( 123 | {opt: (self.option(opt) or set()) for opt in {"only", "without", "group"}} 124 | ) 125 | 126 | groups = [ 127 | str(group) 128 | for group in ( 129 | self._get_only_group_option() 130 | # Use all groups by default, including optional groups 131 | or sorted( 132 | self.poetry.package.dependency_group_names(include_optional=True) 133 | ) 134 | ) 135 | # Exclude groups specified by the `--without` option 136 | if group not in self.option("without") 137 | ] 138 | 139 | if not groups: 140 | self.info("No groups to relax.") 141 | return 1 142 | 143 | updated_dependencies: Dict[ 144 | str, List[Tuple[str, Dependency]] 145 | ] = {} # Dependencies updated per group 146 | 147 | for group in groups: 148 | # Load dependencies in the given group 149 | pretty_group = _pretty_group(group) 150 | self.info( 151 | f"Checking dependencies{pretty_group} for relaxable constraints..." 152 | ) 153 | 154 | dependency_config = extract_dependency_config_for_group( 155 | group, poetry_config 156 | ) 157 | if dependency_config is None: 158 | self.line(f"No dependencies found{pretty_group}.") 159 | continue 160 | 161 | # Parse the dependencies 162 | target_dependencies = [ 163 | Factory.create_dependency(name, constraint) 164 | for name, constraint in flattened_dependency_config_items( 165 | dependency_config 166 | ) 167 | if name != "python" 168 | ] 169 | 170 | if not target_dependencies: 171 | self.line(f"No dependencies to relax{pretty_group}.") 172 | updated_dependencies[group] = [] 173 | continue 174 | 175 | if self.io.is_verbose(): 176 | self.line( 177 | f"Found {len(target_dependencies)} dependencies{pretty_group}." 178 | ) 179 | 180 | # Construct new dependency objects with the max constraint removed 181 | new_dependencies = [ 182 | drop_caret_bound_from_dependency(d) for d in target_dependencies 183 | ] 184 | 185 | updated_dependencies[group] = [ 186 | (old.pretty_constraint, new) 187 | for old, new in zip(target_dependencies, new_dependencies) 188 | # We use the pretty constraint in updates to retain the user's string 189 | if old.pretty_constraint != new.pretty_constraint 190 | ] 191 | 192 | if self.io.is_verbose(): 193 | self.line( 194 | f"Proposing updates to {len(updated_dependencies[group])} " 195 | f"dependencies{pretty_group}." 196 | ) 197 | 198 | updated_count = sum(len(deps) for deps in updated_dependencies.values()) 199 | if not updated_count: 200 | self.info("No dependency constraints to relax.") 201 | return 0 202 | 203 | self.line(f"Proposing updates to {updated_count} dependencies.") 204 | 205 | # Validate that the update is valid by running the installer 206 | if self.option("update") or self.option("check") or self.option("lock"): 207 | if self.io.is_verbose(): 208 | for group in groups: 209 | for old_constraint, dependency in updated_dependencies[group]: 210 | marker = ( 211 | f" (when {dependency.marker})" if dependency.marker else "" 212 | ) 213 | self.info( 214 | f"Proposing update for {dependency.name} constraint from " 215 | f"{old_constraint} to {dependency.pretty_constraint}" 216 | f"{marker}{_pretty_group(group)}" 217 | ) 218 | 219 | should_not_update = self.option("dry-run") or not ( 220 | self.option("update") or self.option("lock") 221 | ) 222 | if should_not_update: 223 | self.info("Checking new dependencies can be solved...") 224 | else: 225 | self.info("Running Poetry package installer...") 226 | 227 | # Cosmetic new line 228 | self.line("") 229 | 230 | # Check for a valid installer otherwise it will be hidden with no message 231 | try: 232 | assert self.installer is not None 233 | except AssertionError: 234 | self.line("Poetry did not instantiate an installer for the plugin.") 235 | self.line("Aborting!", style="fg=red;options=bold") 236 | return 1 237 | 238 | try: 239 | status = run_installer_update( 240 | poetry=self.poetry, 241 | installer=self.installer, 242 | lockfile_only=self.option("lock"), 243 | dependencies_by_group={ 244 | group: (d for _, d in deps) 245 | for group, deps in updated_dependencies.items() 246 | }, 247 | poetry_config=poetry_config, 248 | dry_run=should_not_update, 249 | verbose=self.io.is_verbose(), 250 | silent=( 251 | # Do not display installer output by default, it's confusing 252 | should_not_update and not self.io.is_verbose() 253 | ), 254 | ) 255 | except Exception as exc: 256 | self.line(str(exc), style="fg=red;options=bold") 257 | status = 1 258 | else: 259 | if self.option("check"): 260 | self.line("\nDependency check successful.") 261 | else: 262 | if not self.option("check"): 263 | self.info("Skipping check for valid versions.") 264 | 265 | status = 0 266 | 267 | # Cosmetic new line 268 | self.line("") 269 | 270 | for group in groups: 271 | dependency_config = extract_dependency_config_for_group( 272 | group, poetry_config 273 | ) 274 | if dependency_config is None: 275 | continue 276 | 277 | for old_constraint, dependency in updated_dependencies[group]: 278 | # Mutate the dependency config (and consequently the pyproject config) 279 | # (mypy complains because the type is not hinted as a mutable mapping) 280 | dependency_config[dependency.name] = update_dependency_config( # type: ignore 281 | dependency_config[dependency.name], dependency 282 | ) 283 | 284 | # Display the final updates since they can be buried by the installer update 285 | marker = f" (when {dependency.marker})" if dependency.marker else "" 286 | self.info( 287 | f"Updated {dependency.pretty_name} constraint from " 288 | f"{old_constraint} to {dependency.pretty_constraint}" 289 | f"{marker}{_pretty_group(group)}" 290 | ) 291 | 292 | if status == 0 and not self.option("dry-run"): 293 | assert isinstance(pyproject_config, TOMLDocument) 294 | self.poetry.file.write(pyproject_config) 295 | self.info("Updated config file with relaxed constraints.") 296 | 297 | elif status != 0: 298 | self.line( 299 | "Aborted relax due to failure during dependency update.", 300 | style="fg=red;options=bold", 301 | ) 302 | else: 303 | self.info("Skipped update of config file due to dry-run flag.") 304 | 305 | return status 306 | 307 | def _validate_group_options(self, group_options: Dict[str, Set[str]]) -> None: 308 | """ 309 | Raises en error if it detects that a group is not part of pyproject.toml 310 | """ 311 | if POETRY_VERSION >= Version("1.5.0"): 312 | return super()._validate_group_options(group_options) 313 | 314 | # Backport of the validation logic from Poetry 1.5.x 315 | 316 | invalid_options = defaultdict(set) 317 | for opt, groups in group_options.items(): 318 | for group in groups: 319 | if not self.poetry.package.has_dependency_group(group): 320 | invalid_options[group].add(opt) 321 | if invalid_options: 322 | message_parts = [] 323 | for group in sorted(invalid_options): 324 | opts = ", ".join( 325 | f"--{opt}" 326 | for opt in sorted(invalid_options[group]) 327 | ) 328 | message_parts.append(f"{group} (via {opts})") 329 | raise PoetryConsoleError(f"Group(s) not found: {', '.join(message_parts)}") 330 | -------------------------------------------------------------------------------- /tests/test_command.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | import sys 4 | import uuid 5 | from pathlib import Path 6 | from typing import Callable 7 | 8 | import pytest 9 | from cleo.io.outputs.output import Verbosity 10 | from poetry.console.application import Application as PoetryApplication 11 | from poetry.utils.env import VirtualEnv 12 | 13 | from poetry_relax import RelaxCommand 14 | 15 | from ._utilities import ( 16 | PoetryCommandTester, 17 | assert_io_contains, 18 | assert_pyproject_matches, 19 | assert_pyproject_unchanged, 20 | check_paths_relative, 21 | get_dependency_group, 22 | load_lockfile_packages, 23 | tmpchdir, 24 | update_pyproject, 25 | ) 26 | 27 | 28 | @pytest.fixture 29 | def relax_command(poetry_application: PoetryApplication): 30 | """ 31 | Return a cleo command tester for the `poetry relax` command. 32 | """ 33 | # Using a command tester is significantly faster than running subprocesses but 34 | # requires a little more setup 35 | command = RelaxCommand() 36 | tester = PoetryCommandTester(command, poetry_application) 37 | 38 | yield tester 39 | 40 | # Display output for debugging tests 41 | print(tester.io.fetch_output(), end="") 42 | print(tester.io.fetch_error(), file=sys.stderr, end="") 43 | 44 | 45 | @pytest.fixture 46 | def seeded_relax_command( 47 | relax_command: PoetryCommandTester, 48 | seeded_poetry_project_path: Path, 49 | poetry_application_factory: Callable[[], PoetryApplication], 50 | seeded_project_venv: VirtualEnv, 51 | ): 52 | # Update the application for the command to the seeded version 53 | application = poetry_application_factory() 54 | relax_command.configure_for_application(application) 55 | 56 | # Assert that the update above was successful 57 | assert check_paths_relative( 58 | relax_command.command.poetry.file.path, seeded_poetry_project_path 59 | ), f""" 60 | The poetry application's config file should be relative to the test project path: 61 | {seeded_poetry_project_path} 62 | but the following path was found: 63 | {relax_command.command.poetry.file.path}" 64 | """ 65 | 66 | yield relax_command 67 | 68 | 69 | @pytest.fixture(autouse=True) 70 | def autouse_poetry_project_path(poetry_project_path): 71 | # All tests in this module should auto-use the project path for isolation 72 | yield poetry_project_path 73 | 74 | 75 | @pytest.mark.parametrize("extra_options", ["", "--update", "--lock"]) 76 | def test_newly_initialized_project( 77 | relax_command: PoetryCommandTester, extra_options: str 78 | ): 79 | with assert_pyproject_unchanged(): 80 | relax_command.execute(extra_options) 81 | 82 | assert relax_command.status_code == 0 83 | assert_io_contains("No dependencies to relax", relax_command.io) 84 | 85 | 86 | def test_group_does_not_exist(relax_command: PoetryCommandTester): 87 | with assert_pyproject_unchanged(): 88 | relax_command.execute("--only iamnotagroup") 89 | 90 | assert relax_command.status_code == 1 91 | assert_io_contains("Group(s) not found: iamnotagroup", relax_command.io) 92 | 93 | 94 | def test_with_no_pyproject_toml( 95 | relax_command: PoetryCommandTester, poetry_project_path: Path 96 | ): 97 | os.remove(poetry_project_path / "pyproject.toml") 98 | 99 | # The error type differs depending on the test fixture used to set up the 100 | # command, so we cover both 101 | with pytest.raises((RuntimeError, FileNotFoundError), match="pyproject.toml"): 102 | relax_command.execute("--check") 103 | 104 | 105 | def test_help(relax_command: PoetryCommandTester): 106 | with assert_pyproject_unchanged(): 107 | relax_command.execute("--help") 108 | 109 | assert relax_command.status_code == 0 110 | assert_io_contains("No dependencies to relax", relax_command.io) 111 | 112 | 113 | def test_available_in_poetry_cli(): 114 | output = subprocess.check_output(["poetry", "relax", "--help"]).decode() 115 | assert "Relax project dependencies" in output 116 | assert "Usage:" in output 117 | assert "Options:" in output 118 | 119 | 120 | @pytest.mark.parametrize("version", ["1", "1.0", "1.0b1", "2.0.0"]) 121 | def test_single_simple_dependency_updated( 122 | relax_command: PoetryCommandTester, version: str 123 | ): 124 | # Add test package with pin 125 | with update_pyproject() as pyproject: 126 | pyproject["tool"]["poetry"]["dependencies"]["test"] = f"^{version}" 127 | 128 | with assert_pyproject_matches() as expected_config: 129 | relax_command.execute() 130 | 131 | expected_config["tool"]["poetry"]["dependencies"]["test"] = f">={version}" 132 | 133 | assert relax_command.status_code == 0 134 | 135 | 136 | def test_multiple_dependencies_updated(relax_command: PoetryCommandTester): 137 | with update_pyproject() as pyproject: 138 | pyproject["tool"]["poetry"]["dependencies"]["foo"] = "^1.0" 139 | pyproject["tool"]["poetry"]["dependencies"]["bar"] = "^2.0" 140 | 141 | with assert_pyproject_matches() as expected_config: 142 | relax_command.execute() 143 | 144 | expected_config["tool"]["poetry"]["dependencies"]["foo"] = ">=1.0" 145 | expected_config["tool"]["poetry"]["dependencies"]["bar"] = ">=2.0" 146 | 147 | assert relax_command.status_code == 0 148 | 149 | 150 | def test_single_dependency_updated_in_group(relax_command: PoetryCommandTester): 151 | # Add test package with pin 152 | with update_pyproject() as config: 153 | get_dependency_group(config, "dev")["test"] = "^1.0" 154 | 155 | relax_command.command.reset_poetry() 156 | 157 | with assert_pyproject_matches() as expected_config: 158 | relax_command.execute("--only dev") 159 | 160 | get_dependency_group(expected_config, "dev")["test"] = ">=1.0" 161 | 162 | assert relax_command.status_code == 0 163 | 164 | 165 | def test_single_dependency_updated_in_group_with_deprecated_option( 166 | relax_command: PoetryCommandTester, 167 | ): 168 | # Add test package with pin 169 | with update_pyproject() as config: 170 | get_dependency_group(config, "dev")["test"] = "^1.0" 171 | 172 | relax_command.command.reset_poetry() 173 | 174 | with assert_pyproject_matches() as expected_config: 175 | # Uses `--group` instead of `--only` 176 | relax_command.execute("--group dev") 177 | 178 | get_dependency_group(expected_config, "dev")["test"] = ">=1.0" 179 | 180 | assert relax_command.status_code == 0 181 | assert_io_contains( 182 | "The `--group` option is deprecated; use `--only` instead.", relax_command.io 183 | ) 184 | 185 | 186 | def test_single_dependency_updated_in_multiple_groups( 187 | relax_command: PoetryCommandTester, 188 | ): 189 | with update_pyproject() as config: 190 | get_dependency_group(config)["test"] = "^1.0" 191 | get_dependency_group(config, "foo")["test"] = "^2.0" 192 | get_dependency_group(config, "bar")["test"] = "^3.0" 193 | 194 | # Cover inclusion of optional groups 195 | config["tool"]["poetry"]["group"]["bar"]["optional"] = True 196 | 197 | with assert_pyproject_matches() as expected_config: 198 | relax_command.execute() 199 | 200 | get_dependency_group(expected_config)["test"] = ">=1.0" 201 | get_dependency_group(expected_config, "foo")["test"] = ">=2.0" 202 | get_dependency_group(expected_config, "bar")["test"] = ">=3.0" 203 | 204 | assert relax_command.status_code == 0 205 | 206 | 207 | def test_group_with_no_dependencies_is_skipped( 208 | relax_command: PoetryCommandTester, 209 | ): 210 | with update_pyproject() as config: 211 | get_dependency_group(config, "foo") 212 | get_dependency_group(config, "bar")["test"] = "^3.0" 213 | 214 | with assert_pyproject_matches() as expected_config: 215 | relax_command.execute() 216 | 217 | get_dependency_group(expected_config, "foo") 218 | get_dependency_group(expected_config, "bar")["test"] = ">=3.0" 219 | 220 | assert relax_command.status_code == 0 221 | assert_io_contains("No dependencies to relax in group 'foo'", relax_command.io) 222 | 223 | 224 | def test_multiple_dependencies_updated_in_multiple_groups( 225 | relax_command: PoetryCommandTester, 226 | ): 227 | with update_pyproject() as config: 228 | get_dependency_group(config)["a"] = "^1.0" 229 | get_dependency_group(config, "foo")["b"] = "^2.0" 230 | get_dependency_group(config, "bar")["c"] = "^3.0" 231 | 232 | # Cover inclusion of optional groups 233 | config["tool"]["poetry"]["group"]["bar"]["optional"] = True 234 | 235 | with assert_pyproject_matches() as expected_config: 236 | relax_command.execute() 237 | 238 | get_dependency_group(expected_config)["a"] = ">=1.0" 239 | get_dependency_group(expected_config, "foo")["b"] = ">=2.0" 240 | get_dependency_group(expected_config, "bar")["c"] = ">=3.0" 241 | 242 | assert relax_command.status_code == 0 243 | 244 | 245 | @pytest.mark.parametrize( 246 | "input_version,output_version", 247 | [ 248 | ("^1.4,!=1.5", ">=1.4,!=1.5"), 249 | ("!=1.5,^1.4", "!=1.5,>=1.4"), 250 | ("^1.4 || !=1.5", ">=1.4 || !=1.5"), 251 | ("^1.4, !=1.5", ">=1.4, !=1.5"), 252 | ("^1.4, !=1.5", ">=1.4, !=1.5"), 253 | (">=1.4, !=1.5", ">=1.4, !=1.5"), 254 | ("^1.4, <= 2.5", ">=1.4, <= 2.5"), 255 | ], 256 | ) 257 | def test_multiple_constraint_dependency_only_updates_caret( 258 | relax_command: PoetryCommandTester, input_version, output_version 259 | ): 260 | with update_pyproject() as pyproject: 261 | pyproject["tool"]["poetry"]["dependencies"]["prefect"] = input_version 262 | 263 | with assert_pyproject_matches() as expected_config: 264 | relax_command.execute() 265 | 266 | expected_config["tool"]["poetry"]["dependencies"]["prefect"] = output_version 267 | 268 | assert relax_command.status_code == 0 269 | 270 | 271 | @pytest.mark.parametrize("version", ["==1", ">=1.0", ">=1.0b1,<=2.0", "<=2.0.0"]) 272 | def test_single_dependency_without_caret_constraint_not_updated( 273 | relax_command: PoetryCommandTester, version: str 274 | ): 275 | # Add test package with pin 276 | with update_pyproject() as pyproject: 277 | pyproject["tool"]["poetry"]["dependencies"]["test"] = version 278 | 279 | with assert_pyproject_unchanged(): 280 | relax_command.execute() 281 | 282 | assert relax_command.status_code == 0 283 | 284 | 285 | def test_dependency_updated_in_one_group_does_not_affect_other_groups( 286 | relax_command: PoetryCommandTester, 287 | ): 288 | with update_pyproject() as config: 289 | get_dependency_group(config)["test"] = "^1.0" 290 | get_dependency_group(config, "foo")["test"] = "^2.0" 291 | get_dependency_group(config, "bar")["test"] = "^3.0" 292 | 293 | with assert_pyproject_matches() as expected_config: 294 | relax_command.execute("--only foo") 295 | 296 | get_dependency_group(expected_config)["test"] = "^1.0" 297 | get_dependency_group(expected_config, "foo")["test"] = ">=2.0" 298 | get_dependency_group(expected_config, "bar")["test"] = "^3.0" 299 | 300 | assert relax_command.status_code == 0 301 | 302 | 303 | def test_group_excluded_with_without_is_not_affected( 304 | relax_command: PoetryCommandTester, 305 | ): 306 | with update_pyproject() as config: 307 | get_dependency_group(config)["test"] = "^1.0" 308 | get_dependency_group(config, "foo")["test"] = "^2.0" 309 | get_dependency_group(config, "bar")["test"] = "^3.0" 310 | 311 | with assert_pyproject_matches() as expected_config: 312 | relax_command.execute("--without foo") 313 | 314 | get_dependency_group(expected_config)["test"] = ">=1.0" 315 | get_dependency_group(expected_config, "foo")["test"] = "^2.0" 316 | get_dependency_group(expected_config, "bar")["test"] = ">=3.0" 317 | 318 | assert relax_command.status_code == 0 319 | 320 | 321 | def test_dependency_with_additional_options(relax_command: PoetryCommandTester): 322 | with update_pyproject() as pyproject: 323 | pyproject["tool"]["poetry"]["dependencies"]["test"] = { 324 | "version": "^1.0", 325 | "allow-prereleases": True, 326 | } 327 | 328 | with assert_pyproject_matches() as expected_config: 329 | relax_command.execute() 330 | 331 | expected_config["tool"]["poetry"]["dependencies"]["test"] = { 332 | "version": ">=1.0", 333 | "allow-prereleases": True, 334 | } 335 | 336 | assert relax_command.status_code == 0 337 | 338 | 339 | def test_dependency_with_git_url(relax_command: PoetryCommandTester): 340 | with update_pyproject() as pyproject: 341 | pyproject["tool"]["poetry"]["dependencies"]["test"] = { 342 | "git": "https://github.com/zanieb/test.git" 343 | } 344 | 345 | with assert_pyproject_matches() as expected_config: 346 | relax_command.execute() 347 | 348 | expected_config["tool"]["poetry"]["dependencies"]["test"] = { 349 | "git": "https://github.com/zanieb/test.git" 350 | } 351 | 352 | assert relax_command.status_code == 0 353 | 354 | 355 | def test_dependency_with_multiple_conditional_versions( 356 | relax_command: PoetryCommandTester, 357 | ): 358 | # Add test package with multiple versions conditional on the Python version 359 | with update_pyproject() as pyproject: 360 | pyproject["tool"]["poetry"]["dependencies"]["test"] = [ 361 | {"version": "<=1.9", "python": ">=3.6,<3.8"}, 362 | {"version": "^2.0", "python": ">=3.8"}, 363 | ] 364 | 365 | with assert_pyproject_matches() as expected_config: 366 | relax_command.execute() 367 | 368 | expected_config["tool"]["poetry"]["dependencies"]["test"] = [ 369 | {"version": "<=1.9", "python": ">=3.6,<3.8"}, 370 | {"version": ">=2.0", "python": ">=3.8"}, 371 | ] 372 | assert relax_command.status_code == 0 373 | 374 | 375 | def test_dependency_relax_with_validity_check( 376 | seeded_relax_command: PoetryCommandTester, 377 | seeded_project_venv: VirtualEnv, 378 | seeded_cloudpickle_version: str, 379 | ): 380 | with update_pyproject() as config: 381 | config["tool"]["poetry"]["dependencies"]["cloudpickle"] = "^1.0" 382 | get_dependency_group(config, "dev")["cloudpickle"] = "^1.0" 383 | 384 | with assert_pyproject_matches() as expected_config: 385 | seeded_relax_command.execute("--check") 386 | 387 | expected_config["tool"]["poetry"]["dependencies"]["cloudpickle"] = ">=1.0" 388 | get_dependency_group(expected_config, "dev")["cloudpickle"] = ">=1.0" 389 | 390 | assert seeded_relax_command.status_code == 0 391 | new_cloudpickle_version = seeded_project_venv.run_python_script( 392 | "import cloudpickle; print(cloudpickle.__version__)" 393 | ).strip() 394 | assert ( 395 | new_cloudpickle_version == seeded_cloudpickle_version 396 | ), f"The dependency should not be updated but has version {new_cloudpickle_version}" 397 | 398 | 399 | def test_dependency_relax_aborted_when_constraint_is_not_satisfiable( 400 | seeded_relax_command: PoetryCommandTester, 401 | ): 402 | with update_pyproject() as pyproject: 403 | # Configure the pyproject with a version that does not exist 404 | pyproject["tool"]["poetry"]["dependencies"]["cloudpickle"] = "^999.0" 405 | 406 | # Configure a valid version in another group — should not be relaxed 407 | get_dependency_group(pyproject, "dev")["cloudpickle"] = "^1.0" 408 | 409 | with assert_pyproject_unchanged(): 410 | seeded_relax_command.execute("--check") 411 | 412 | assert seeded_relax_command.status_code == 1 413 | assert_io_contains( 414 | "Aborted relax due to failure during dependency update", 415 | seeded_relax_command.io, 416 | ) 417 | 418 | 419 | def test_dependency_relax_aborted_when_complex_constraint_is_not_satisfiable( 420 | seeded_relax_command: PoetryCommandTester, 421 | ): 422 | # Add test package with multiple versions conditional on the Python version 423 | with update_pyproject() as pyproject: 424 | pyproject["tool"]["poetry"]["dependencies"]["test"] = [ 425 | {"version": "<=1.9", "python": ">=3.6,<3.10"}, 426 | # Use a version that does not exist 427 | {"version": "^999.0", "python": ">=3.10"}, 428 | ] 429 | 430 | with assert_pyproject_unchanged(): 431 | seeded_relax_command.execute("--check") 432 | 433 | assert seeded_relax_command.status_code == 1 434 | assert_io_contains( 435 | "Aborted relax due to failure during dependency update", 436 | seeded_relax_command.io, 437 | ) 438 | 439 | 440 | def test_dependency_relax_aborted_when_package_does_not_exist( 441 | seeded_relax_command: PoetryCommandTester, 442 | ): 443 | fake_name = uuid.uuid4().hex 444 | 445 | with update_pyproject() as pyproject: 446 | pyproject["tool"]["poetry"]["dependencies"][fake_name] = "^1.0" 447 | 448 | # Configure a valid dependency in another group — should not be relaxed 449 | get_dependency_group(pyproject, "dev")["cloudpickle"] = "^1.0" 450 | 451 | with assert_pyproject_unchanged(): 452 | seeded_relax_command.execute("--check") 453 | 454 | assert seeded_relax_command.status_code == 1 455 | assert_io_contains( 456 | "Aborted relax due to failure during dependency update", 457 | seeded_relax_command.io, 458 | ) 459 | 460 | 461 | def test_update_flag_upgrades_dependency_after_relax( 462 | seeded_relax_command: PoetryCommandTester, 463 | seeded_project_venv: VirtualEnv, 464 | seeded_cloudpickle_version: str, 465 | ): 466 | with update_pyproject() as pyproject: 467 | pyproject["tool"]["poetry"]["dependencies"]["cloudpickle"] = "^1.0" 468 | 469 | with assert_pyproject_matches() as expected_config: 470 | seeded_relax_command.execute("--update", verbosity=Verbosity.DEBUG) 471 | 472 | expected_config["tool"]["poetry"]["dependencies"]["cloudpickle"] = ">=1.0" 473 | 474 | assert seeded_relax_command.status_code == 0 475 | 476 | new_cloudpickle_version = seeded_project_venv.run_python_script( 477 | "import cloudpickle; print(cloudpickle.__version__)" 478 | ).strip() 479 | 480 | assert ( 481 | new_cloudpickle_version != seeded_cloudpickle_version 482 | ), f"The dependency should be updated but has initial version {new_cloudpickle_version}" 483 | assert ( 484 | int(new_cloudpickle_version[0]) > 1 485 | ), f"The dependency should be updated to the next major version but has version {new_cloudpickle_version}" 486 | 487 | 488 | def test_update_flag_upgrades_dependencies_across_groups_after_relax( 489 | seeded_relax_command: PoetryCommandTester, 490 | seeded_project_venv: VirtualEnv, 491 | seeded_cloudpickle_version: str, 492 | seeded_typing_extensions_version: str, 493 | ): 494 | # Regression test for https://github.com/zanieb/poetry-relax/issues/50 495 | 496 | with update_pyproject() as pyproject: 497 | pyproject["tool"]["poetry"]["dependencies"]["cloudpickle"] = "^1.0" 498 | get_dependency_group(pyproject, "dev")["typing-extensions"] = "^3.0" 499 | 500 | with assert_pyproject_matches() as expected_config: 501 | seeded_relax_command.execute("--update", verbosity=Verbosity.DEBUG) 502 | expected_config["tool"]["poetry"]["dependencies"]["cloudpickle"] = ">=1.0" 503 | get_dependency_group(expected_config, "dev")["typing-extensions"] = ">=3.0" 504 | 505 | assert seeded_relax_command.status_code == 0 506 | 507 | new_cloudpickle_version = seeded_project_venv.run_python_script( 508 | "import cloudpickle; print(cloudpickle.__version__)" 509 | ).strip() 510 | 511 | assert ( 512 | new_cloudpickle_version != seeded_cloudpickle_version 513 | ), f"The dependency should be updated but has initial version {new_cloudpickle_version}" 514 | assert ( 515 | int(new_cloudpickle_version[0]) > 1 516 | ), f"The dependency should be updated to the next major version but has version {new_cloudpickle_version}" 517 | 518 | new_typing_extensions_version = seeded_project_venv.run_python_script( 519 | "import importlib_metadata; print(importlib_metadata.version('typing_extensions'))" 520 | ).strip() 521 | 522 | assert ( 523 | new_typing_extensions_version != seeded_typing_extensions_version 524 | ), f"The dependency should be updated but has initial version {new_typing_extensions_version}" 525 | assert ( 526 | int(new_typing_extensions_version[0]) > 1 527 | ), f"The dependency should be updated to the next major version but has version {new_typing_extensions_version}" 528 | 529 | 530 | def test_lock_flag_only_updates_lockfile_after_relax( 531 | seeded_relax_command: PoetryCommandTester, 532 | seeded_project_venv: VirtualEnv, 533 | seeded_cloudpickle_version: str, 534 | ): 535 | with update_pyproject() as pyproject: 536 | pyproject["tool"]["poetry"]["dependencies"]["cloudpickle"] = "^1.0" 537 | 538 | with assert_pyproject_matches() as expected_config: 539 | seeded_relax_command.execute("--lock", verbosity=Verbosity.DEBUG) 540 | 541 | expected_config["tool"]["poetry"]["dependencies"]["cloudpickle"] = ">=1.0" 542 | 543 | assert seeded_relax_command.status_code == 0 544 | 545 | lockfile_pkgs = load_lockfile_packages() 546 | lock_cloudpickle_version = lockfile_pkgs["cloudpickle"]["version"] 547 | assert ( 548 | lock_cloudpickle_version != seeded_cloudpickle_version 549 | ), f"The dependency should be updated in the lockfile but has version {lock_cloudpickle_version}" 550 | assert ( 551 | int(lock_cloudpickle_version.partition(".")[0]) > 1 552 | ), f"The dependency should be updated to the next major version but has version {lock_cloudpickle_version}" 553 | 554 | new_cloudpickle_version = seeded_project_venv.run_python_script( 555 | "import cloudpickle; print(cloudpickle.__version__)" 556 | ).strip() 557 | assert ( 558 | new_cloudpickle_version == seeded_cloudpickle_version 559 | ), f"The dependency should not be upgraded but has version {new_cloudpickle_version}" 560 | 561 | 562 | @pytest.mark.parametrize("extra_options", ["", "--update", "--lock", "--check"]) 563 | def test_dry_run_flag_prevents_changes( 564 | extra_options: str, 565 | seeded_relax_command: PoetryCommandTester, 566 | seeded_project_venv: VirtualEnv, 567 | seeded_cloudpickle_version: str, 568 | ): 569 | with update_pyproject() as pyproject: 570 | pyproject["tool"]["poetry"]["dependencies"]["cloudpickle"] = "^1.0" 571 | 572 | with assert_pyproject_unchanged(): 573 | seeded_relax_command.execute(f"--dry-run {extra_options}") 574 | 575 | assert seeded_relax_command.status_code == 0 576 | 577 | new_cloudpickle_version = seeded_project_venv.run_python_script( 578 | "import cloudpickle; print(cloudpickle.__version__)" 579 | ).strip() 580 | 581 | assert ( 582 | new_cloudpickle_version == seeded_cloudpickle_version 583 | ), f"The dependency should not be upgraded but has version {new_cloudpickle_version}" 584 | 585 | if "--check" in extra_options: 586 | assert_io_contains( 587 | "Checking dependencies in group 'main' for relaxable constraints", 588 | seeded_relax_command.io, 589 | ) 590 | assert_io_contains( 591 | "Skipped update of config file due to dry-run flag.", seeded_relax_command.io 592 | ) 593 | 594 | 595 | def test_python_dependency_is_ignored(relax_command: PoetryCommandTester): 596 | # TODO: Consider changing this behavior before stable release. 597 | # There are some peculiar issues with this though. 598 | 599 | # Add Python package with pin 600 | with update_pyproject() as config: 601 | config["tool"]["poetry"]["dependencies"]["python"] = "^3.8" 602 | 603 | with assert_pyproject_unchanged(): 604 | relax_command.execute("--check") 605 | 606 | assert relax_command.status_code == 0 607 | 608 | 609 | def test_invoked_from_subdirectory( 610 | relax_command: PoetryCommandTester, poetry_project_path: Path 611 | ): 612 | child_dir = poetry_project_path / "child" 613 | child_dir.mkdir() 614 | 615 | # Add test package with pin 616 | with update_pyproject() as pyproject: 617 | pyproject["tool"]["poetry"]["dependencies"]["test"] = "^1.0" 618 | 619 | with assert_pyproject_matches() as expected_config: 620 | # Change directories for execution of the package 621 | with tmpchdir(child_dir): 622 | relax_command.execute() 623 | 624 | expected_config["tool"]["poetry"]["dependencies"]["test"] = ">=1.0" 625 | 626 | assert relax_command.status_code == 0 627 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "build" 5 | version = "1.0.3" 6 | description = "A simple, correct Python build frontend" 7 | optional = false 8 | python-versions = ">= 3.7" 9 | files = [ 10 | {file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"}, 11 | {file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"}, 12 | ] 13 | 14 | [package.dependencies] 15 | colorama = {version = "*", markers = "os_name == \"nt\""} 16 | importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} 17 | packaging = ">=19.0" 18 | pyproject_hooks = "*" 19 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 20 | 21 | [package.extras] 22 | docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] 23 | test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] 24 | typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"] 25 | virtualenv = ["virtualenv (>=20.0.35)"] 26 | 27 | [[package]] 28 | name = "cachecontrol" 29 | version = "0.14.0" 30 | description = "httplib2 caching for requests" 31 | optional = false 32 | python-versions = ">=3.7" 33 | files = [ 34 | {file = "cachecontrol-0.14.0-py3-none-any.whl", hash = "sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0"}, 35 | {file = "cachecontrol-0.14.0.tar.gz", hash = "sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938"}, 36 | ] 37 | 38 | [package.dependencies] 39 | filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} 40 | msgpack = ">=0.5.2,<2.0.0" 41 | requests = ">=2.16.0" 42 | 43 | [package.extras] 44 | dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "furo", "mypy", "pytest", "pytest-cov", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] 45 | filecache = ["filelock (>=3.8.0)"] 46 | redis = ["redis (>=2.10.5)"] 47 | 48 | [[package]] 49 | name = "certifi" 50 | version = "2023.11.17" 51 | description = "Python package for providing Mozilla's CA Bundle." 52 | optional = false 53 | python-versions = ">=3.6" 54 | files = [ 55 | {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, 56 | {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, 57 | ] 58 | 59 | [[package]] 60 | name = "cffi" 61 | version = "1.16.0" 62 | description = "Foreign Function Interface for Python calling C code." 63 | optional = false 64 | python-versions = ">=3.8" 65 | files = [ 66 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 67 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 68 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 69 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 70 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 71 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 72 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 73 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 74 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 75 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 76 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 77 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 78 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 79 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 80 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 81 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 82 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 83 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 84 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 85 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 86 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 87 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 88 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 89 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 90 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 91 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 92 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 93 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 94 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 95 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 96 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 97 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 98 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 99 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 100 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 101 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 102 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 103 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 104 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 105 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 106 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 107 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 108 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 109 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 110 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 111 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 112 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 113 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 114 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 115 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 116 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 117 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 118 | ] 119 | 120 | [package.dependencies] 121 | pycparser = "*" 122 | 123 | [[package]] 124 | name = "charset-normalizer" 125 | version = "3.3.2" 126 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 127 | optional = false 128 | python-versions = ">=3.7.0" 129 | files = [ 130 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 131 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 132 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 133 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 134 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 135 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 136 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 137 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 138 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 139 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 140 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 141 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 142 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 143 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 144 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 145 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 146 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 147 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 148 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 149 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 150 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 151 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 152 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 153 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 154 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 155 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 156 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 157 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 158 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 159 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 160 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 161 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 162 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 163 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 164 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 165 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 166 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 167 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 168 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 169 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 170 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 171 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 172 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 173 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 174 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 175 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 176 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 177 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 178 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 179 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 180 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 181 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 182 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 183 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 184 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 185 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 186 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 187 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 188 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 189 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 190 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 191 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 192 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 193 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 194 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 195 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 196 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 197 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 198 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 199 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 200 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 201 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 202 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 203 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 204 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 205 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 206 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 207 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 208 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 209 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 210 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 211 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 212 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 213 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 214 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 215 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 216 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 217 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 218 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 219 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 220 | ] 221 | 222 | [[package]] 223 | name = "cleo" 224 | version = "2.1.0" 225 | description = "Cleo allows you to create beautiful and testable command-line interfaces." 226 | optional = false 227 | python-versions = ">=3.7,<4.0" 228 | files = [ 229 | {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, 230 | {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, 231 | ] 232 | 233 | [package.dependencies] 234 | crashtest = ">=0.4.1,<0.5.0" 235 | rapidfuzz = ">=3.0.0,<4.0.0" 236 | 237 | [[package]] 238 | name = "colorama" 239 | version = "0.4.6" 240 | description = "Cross-platform colored terminal text." 241 | optional = false 242 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 243 | files = [ 244 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 245 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 246 | ] 247 | 248 | [[package]] 249 | name = "crashtest" 250 | version = "0.4.1" 251 | description = "Manage Python errors with ease" 252 | optional = false 253 | python-versions = ">=3.7,<4.0" 254 | files = [ 255 | {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, 256 | {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, 257 | ] 258 | 259 | [[package]] 260 | name = "cryptography" 261 | version = "41.0.7" 262 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 263 | optional = false 264 | python-versions = ">=3.7" 265 | files = [ 266 | {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, 267 | {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, 268 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, 269 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, 270 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, 271 | {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, 272 | {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, 273 | {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, 274 | {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, 275 | {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, 276 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, 277 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, 278 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, 279 | {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, 280 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, 281 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, 282 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, 283 | {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, 284 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, 285 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, 286 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, 287 | {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, 288 | {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, 289 | ] 290 | 291 | [package.dependencies] 292 | cffi = ">=1.12" 293 | 294 | [package.extras] 295 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 296 | docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] 297 | nox = ["nox"] 298 | pep8test = ["black", "check-sdist", "mypy", "ruff"] 299 | sdist = ["build"] 300 | ssh = ["bcrypt (>=3.1.5)"] 301 | test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] 302 | test-randomorder = ["pytest-randomly"] 303 | 304 | [[package]] 305 | name = "distlib" 306 | version = "0.3.8" 307 | description = "Distribution utilities" 308 | optional = false 309 | python-versions = "*" 310 | files = [ 311 | {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, 312 | {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, 313 | ] 314 | 315 | [[package]] 316 | name = "dulwich" 317 | version = "0.21.7" 318 | description = "Python Git Library" 319 | optional = false 320 | python-versions = ">=3.7" 321 | files = [ 322 | {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, 323 | {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, 324 | {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, 325 | {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, 326 | {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, 327 | {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, 328 | {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, 329 | {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, 330 | {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, 331 | {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, 332 | {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, 333 | {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, 334 | {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, 335 | {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, 336 | {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, 337 | {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, 338 | {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, 339 | {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, 340 | {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, 341 | {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, 342 | {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, 343 | {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, 344 | {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, 345 | {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, 346 | {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, 347 | {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, 348 | {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, 349 | {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, 350 | {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, 351 | {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, 352 | {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, 353 | {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, 354 | {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, 355 | {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, 356 | {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, 357 | {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, 358 | {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, 359 | {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, 360 | {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, 361 | {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, 362 | {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, 363 | {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, 364 | {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, 365 | {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, 366 | {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, 367 | {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, 368 | {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, 369 | {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, 370 | {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, 371 | {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, 372 | {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, 373 | {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, 374 | {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, 375 | {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, 376 | {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, 377 | {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, 378 | {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, 379 | {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, 380 | {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, 381 | {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, 382 | {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, 383 | {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, 384 | {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, 385 | {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, 386 | {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, 387 | {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, 388 | {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, 389 | {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, 390 | {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, 391 | ] 392 | 393 | [package.dependencies] 394 | urllib3 = ">=1.25" 395 | 396 | [package.extras] 397 | fastimport = ["fastimport"] 398 | https = ["urllib3 (>=1.24.1)"] 399 | paramiko = ["paramiko"] 400 | pgp = ["gpg"] 401 | 402 | [[package]] 403 | name = "exceptiongroup" 404 | version = "1.2.0" 405 | description = "Backport of PEP 654 (exception groups)" 406 | optional = false 407 | python-versions = ">=3.7" 408 | files = [ 409 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 410 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 411 | ] 412 | 413 | [package.extras] 414 | test = ["pytest (>=6)"] 415 | 416 | [[package]] 417 | name = "execnet" 418 | version = "2.1.1" 419 | description = "execnet: rapid multi-Python deployment" 420 | optional = false 421 | python-versions = ">=3.8" 422 | files = [ 423 | {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, 424 | {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, 425 | ] 426 | 427 | [package.extras] 428 | testing = ["hatch", "pre-commit", "pytest", "tox"] 429 | 430 | [[package]] 431 | name = "fastjsonschema" 432 | version = "2.19.1" 433 | description = "Fastest Python implementation of JSON schema" 434 | optional = false 435 | python-versions = "*" 436 | files = [ 437 | {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, 438 | {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, 439 | ] 440 | 441 | [package.extras] 442 | devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] 443 | 444 | [[package]] 445 | name = "filelock" 446 | version = "3.13.1" 447 | description = "A platform independent file lock." 448 | optional = false 449 | python-versions = ">=3.8" 450 | files = [ 451 | {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, 452 | {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, 453 | ] 454 | 455 | [package.extras] 456 | docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] 457 | testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] 458 | typing = ["typing-extensions (>=4.8)"] 459 | 460 | [[package]] 461 | name = "idna" 462 | version = "3.6" 463 | description = "Internationalized Domain Names in Applications (IDNA)" 464 | optional = false 465 | python-versions = ">=3.5" 466 | files = [ 467 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 468 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 469 | ] 470 | 471 | [[package]] 472 | name = "importlib-metadata" 473 | version = "7.0.1" 474 | description = "Read metadata from Python packages" 475 | optional = false 476 | python-versions = ">=3.8" 477 | files = [ 478 | {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, 479 | {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, 480 | ] 481 | 482 | [package.dependencies] 483 | zipp = ">=0.5" 484 | 485 | [package.extras] 486 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 487 | perf = ["ipython"] 488 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] 489 | 490 | [[package]] 491 | name = "importlib-resources" 492 | version = "6.1.1" 493 | description = "Read resources from Python packages" 494 | optional = false 495 | python-versions = ">=3.8" 496 | files = [ 497 | {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, 498 | {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, 499 | ] 500 | 501 | [package.dependencies] 502 | zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} 503 | 504 | [package.extras] 505 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 506 | testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] 507 | 508 | [[package]] 509 | name = "iniconfig" 510 | version = "2.0.0" 511 | description = "brain-dead simple config-ini parsing" 512 | optional = false 513 | python-versions = ">=3.7" 514 | files = [ 515 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 516 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 517 | ] 518 | 519 | [[package]] 520 | name = "installer" 521 | version = "0.7.0" 522 | description = "A library for installing Python wheels." 523 | optional = false 524 | python-versions = ">=3.7" 525 | files = [ 526 | {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, 527 | {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, 528 | ] 529 | 530 | [[package]] 531 | name = "jaraco-classes" 532 | version = "3.3.0" 533 | description = "Utility functions for Python class constructs" 534 | optional = false 535 | python-versions = ">=3.8" 536 | files = [ 537 | {file = "jaraco.classes-3.3.0-py3-none-any.whl", hash = "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb"}, 538 | {file = "jaraco.classes-3.3.0.tar.gz", hash = "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621"}, 539 | ] 540 | 541 | [package.dependencies] 542 | more-itertools = "*" 543 | 544 | [package.extras] 545 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 546 | testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 547 | 548 | [[package]] 549 | name = "jeepney" 550 | version = "0.8.0" 551 | description = "Low-level, pure Python DBus protocol wrapper." 552 | optional = false 553 | python-versions = ">=3.7" 554 | files = [ 555 | {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, 556 | {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, 557 | ] 558 | 559 | [package.extras] 560 | test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] 561 | trio = ["async_generator", "trio"] 562 | 563 | [[package]] 564 | name = "keyring" 565 | version = "24.3.0" 566 | description = "Store and access your passwords safely." 567 | optional = false 568 | python-versions = ">=3.8" 569 | files = [ 570 | {file = "keyring-24.3.0-py3-none-any.whl", hash = "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836"}, 571 | {file = "keyring-24.3.0.tar.gz", hash = "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25"}, 572 | ] 573 | 574 | [package.dependencies] 575 | importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} 576 | importlib-resources = {version = "*", markers = "python_version < \"3.9\""} 577 | "jaraco.classes" = "*" 578 | jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} 579 | pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} 580 | SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} 581 | 582 | [package.extras] 583 | completion = ["shtab (>=1.1.0)"] 584 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 585 | testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 586 | 587 | [[package]] 588 | name = "more-itertools" 589 | version = "10.2.0" 590 | description = "More routines for operating on iterables, beyond itertools" 591 | optional = false 592 | python-versions = ">=3.8" 593 | files = [ 594 | {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, 595 | {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, 596 | ] 597 | 598 | [[package]] 599 | name = "msgpack" 600 | version = "1.0.7" 601 | description = "MessagePack serializer" 602 | optional = false 603 | python-versions = ">=3.8" 604 | files = [ 605 | {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04ad6069c86e531682f9e1e71b71c1c3937d6014a7c3e9edd2aa81ad58842862"}, 606 | {file = "msgpack-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cca1b62fe70d761a282496b96a5e51c44c213e410a964bdffe0928e611368329"}, 607 | {file = "msgpack-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e50ebce52f41370707f1e21a59514e3375e3edd6e1832f5e5235237db933c98b"}, 608 | {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b4f35de6a304b5533c238bee86b670b75b03d31b7797929caa7a624b5dda6"}, 609 | {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28efb066cde83c479dfe5a48141a53bc7e5f13f785b92ddde336c716663039ee"}, 610 | {file = "msgpack-1.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb14ce54d9b857be9591ac364cb08dc2d6a5c4318c1182cb1d02274029d590d"}, 611 | {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b573a43ef7c368ba4ea06050a957c2a7550f729c31f11dd616d2ac4aba99888d"}, 612 | {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ccf9a39706b604d884d2cb1e27fe973bc55f2890c52f38df742bc1d79ab9f5e1"}, 613 | {file = "msgpack-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cb70766519500281815dfd7a87d3a178acf7ce95390544b8c90587d76b227681"}, 614 | {file = "msgpack-1.0.7-cp310-cp310-win32.whl", hash = "sha256:b610ff0f24e9f11c9ae653c67ff8cc03c075131401b3e5ef4b82570d1728f8a9"}, 615 | {file = "msgpack-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:a40821a89dc373d6427e2b44b572efc36a2778d3f543299e2f24eb1a5de65415"}, 616 | {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:576eb384292b139821c41995523654ad82d1916da6a60cff129c715a6223ea84"}, 617 | {file = "msgpack-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:730076207cb816138cf1af7f7237b208340a2c5e749707457d70705715c93b93"}, 618 | {file = "msgpack-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:85765fdf4b27eb5086f05ac0491090fc76f4f2b28e09d9350c31aac25a5aaff8"}, 619 | {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3476fae43db72bd11f29a5147ae2f3cb22e2f1a91d575ef130d2bf49afd21c46"}, 620 | {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d4c80667de2e36970ebf74f42d1088cc9ee7ef5f4e8c35eee1b40eafd33ca5b"}, 621 | {file = "msgpack-1.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b0bf0effb196ed76b7ad883848143427a73c355ae8e569fa538365064188b8e"}, 622 | {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f9a7c509542db4eceed3dcf21ee5267ab565a83555c9b88a8109dcecc4709002"}, 623 | {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84b0daf226913133f899ea9b30618722d45feffa67e4fe867b0b5ae83a34060c"}, 624 | {file = "msgpack-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ec79ff6159dffcc30853b2ad612ed572af86c92b5168aa3fc01a67b0fa40665e"}, 625 | {file = "msgpack-1.0.7-cp311-cp311-win32.whl", hash = "sha256:3e7bf4442b310ff154b7bb9d81eb2c016b7d597e364f97d72b1acc3817a0fdc1"}, 626 | {file = "msgpack-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:3f0c8c6dfa6605ab8ff0611995ee30d4f9fcff89966cf562733b4008a3d60d82"}, 627 | {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f0936e08e0003f66bfd97e74ee530427707297b0d0361247e9b4f59ab78ddc8b"}, 628 | {file = "msgpack-1.0.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98bbd754a422a0b123c66a4c341de0474cad4a5c10c164ceed6ea090f3563db4"}, 629 | {file = "msgpack-1.0.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b291f0ee7961a597cbbcc77709374087fa2a9afe7bdb6a40dbbd9b127e79afee"}, 630 | {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebbbba226f0a108a7366bf4b59bf0f30a12fd5e75100c630267d94d7f0ad20e5"}, 631 | {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e2d69948e4132813b8d1131f29f9101bc2c915f26089a6d632001a5c1349672"}, 632 | {file = "msgpack-1.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdf38ba2d393c7911ae989c3bbba510ebbcdf4ecbdbfec36272abe350c454075"}, 633 | {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:993584fc821c58d5993521bfdcd31a4adf025c7d745bbd4d12ccfecf695af5ba"}, 634 | {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:52700dc63a4676669b341ba33520f4d6e43d3ca58d422e22ba66d1736b0a6e4c"}, 635 | {file = "msgpack-1.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e45ae4927759289c30ccba8d9fdce62bb414977ba158286b5ddaf8df2cddb5c5"}, 636 | {file = "msgpack-1.0.7-cp312-cp312-win32.whl", hash = "sha256:27dcd6f46a21c18fa5e5deed92a43d4554e3df8d8ca5a47bf0615d6a5f39dbc9"}, 637 | {file = "msgpack-1.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:7687e22a31e976a0e7fc99c2f4d11ca45eff652a81eb8c8085e9609298916dcf"}, 638 | {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b6ccc0c85916998d788b295765ea0e9cb9aac7e4a8ed71d12e7d8ac31c23c95"}, 639 | {file = "msgpack-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:235a31ec7db685f5c82233bddf9858748b89b8119bf4538d514536c485c15fe0"}, 640 | {file = "msgpack-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cab3db8bab4b7e635c1c97270d7a4b2a90c070b33cbc00c99ef3f9be03d3e1f7"}, 641 | {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bfdd914e55e0d2c9e1526de210f6fe8ffe9705f2b1dfcc4aecc92a4cb4b533d"}, 642 | {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36e17c4592231a7dbd2ed09027823ab295d2791b3b1efb2aee874b10548b7524"}, 643 | {file = "msgpack-1.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38949d30b11ae5f95c3c91917ee7a6b239f5ec276f271f28638dec9156f82cfc"}, 644 | {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ff1d0899f104f3921d94579a5638847f783c9b04f2d5f229392ca77fba5b82fc"}, 645 | {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dc43f1ec66eb8440567186ae2f8c447d91e0372d793dfe8c222aec857b81a8cf"}, 646 | {file = "msgpack-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dd632777ff3beaaf629f1ab4396caf7ba0bdd075d948a69460d13d44357aca4c"}, 647 | {file = "msgpack-1.0.7-cp38-cp38-win32.whl", hash = "sha256:4e71bc4416de195d6e9b4ee93ad3f2f6b2ce11d042b4d7a7ee00bbe0358bd0c2"}, 648 | {file = "msgpack-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:8f5b234f567cf76ee489502ceb7165c2a5cecec081db2b37e35332b537f8157c"}, 649 | {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfef2bb6ef068827bbd021017a107194956918ab43ce4d6dc945ffa13efbc25f"}, 650 | {file = "msgpack-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ae3240666ad34cfa31eea7b8c6cd2f1fdaae21d73ce2974211df099a95d81"}, 651 | {file = "msgpack-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3967e4ad1aa9da62fd53e346ed17d7b2e922cba5ab93bdd46febcac39be636fc"}, 652 | {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd178c4c80706546702c59529ffc005681bd6dc2ea234c450661b205445a34d"}, 653 | {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ffbc252eb0d229aeb2f9ad051200668fc3a9aaa8994e49f0cb2ffe2b7867e7"}, 654 | {file = "msgpack-1.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ea70dc4018c7e6223f13affd1c5c30c0f5c12ac1f96cd8e9949acddb48a61"}, 655 | {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:384d779f0d6f1b110eae74cb0659d9aa6ff35aaf547b3955abf2ab4c901c4819"}, 656 | {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f64e376cd20d3f030190e8c32e1c64582eba56ac6dc7d5b0b49a9d44021b52fd"}, 657 | {file = "msgpack-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ed82f5a7af3697b1c4786053736f24a0efd0a1b8a130d4c7bfee4b9ded0f08f"}, 658 | {file = "msgpack-1.0.7-cp39-cp39-win32.whl", hash = "sha256:f26a07a6e877c76a88e3cecac8531908d980d3d5067ff69213653649ec0f60ad"}, 659 | {file = "msgpack-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:1dc93e8e4653bdb5910aed79f11e165c85732067614f180f70534f056da97db3"}, 660 | {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, 661 | ] 662 | 663 | [[package]] 664 | name = "mypy" 665 | version = "1.13.0" 666 | description = "Optional static typing for Python" 667 | optional = false 668 | python-versions = ">=3.8" 669 | files = [ 670 | {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, 671 | {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, 672 | {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, 673 | {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, 674 | {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, 675 | {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, 676 | {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, 677 | {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, 678 | {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, 679 | {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, 680 | {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, 681 | {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, 682 | {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, 683 | {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, 684 | {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, 685 | {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, 686 | {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, 687 | {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, 688 | {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, 689 | {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, 690 | {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, 691 | {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, 692 | {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, 693 | {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, 694 | {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, 695 | {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, 696 | {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, 697 | {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, 698 | {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, 699 | {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, 700 | {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, 701 | {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, 702 | ] 703 | 704 | [package.dependencies] 705 | mypy-extensions = ">=1.0.0" 706 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 707 | typing-extensions = ">=4.6.0" 708 | 709 | [package.extras] 710 | dmypy = ["psutil (>=4.0)"] 711 | faster-cache = ["orjson"] 712 | install-types = ["pip"] 713 | mypyc = ["setuptools (>=50)"] 714 | reports = ["lxml"] 715 | 716 | [[package]] 717 | name = "mypy-extensions" 718 | version = "1.0.0" 719 | description = "Type system extensions for programs checked with the mypy type checker." 720 | optional = false 721 | python-versions = ">=3.5" 722 | files = [ 723 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 724 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 725 | ] 726 | 727 | [[package]] 728 | name = "packaging" 729 | version = "23.2" 730 | description = "Core utilities for Python packages" 731 | optional = false 732 | python-versions = ">=3.7" 733 | files = [ 734 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 735 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 736 | ] 737 | 738 | [[package]] 739 | name = "pexpect" 740 | version = "4.9.0" 741 | description = "Pexpect allows easy control of interactive console applications." 742 | optional = false 743 | python-versions = "*" 744 | files = [ 745 | {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, 746 | {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, 747 | ] 748 | 749 | [package.dependencies] 750 | ptyprocess = ">=0.5" 751 | 752 | [[package]] 753 | name = "pkginfo" 754 | version = "1.12.0" 755 | description = "Query metadata from sdists / bdists / installed packages." 756 | optional = false 757 | python-versions = ">=3.8" 758 | files = [ 759 | {file = "pkginfo-1.12.0-py3-none-any.whl", hash = "sha256:dcd589c9be4da8973eceffa247733c144812759aa67eaf4bbf97016a02f39088"}, 760 | {file = "pkginfo-1.12.0.tar.gz", hash = "sha256:8ad91a0445a036782b9366ef8b8c2c50291f83a553478ba8580c73d3215700cf"}, 761 | ] 762 | 763 | [package.extras] 764 | testing = ["pytest", "pytest-cov", "wheel"] 765 | 766 | [[package]] 767 | name = "platformdirs" 768 | version = "3.11.0" 769 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 770 | optional = false 771 | python-versions = ">=3.7" 772 | files = [ 773 | {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, 774 | {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, 775 | ] 776 | 777 | [package.extras] 778 | docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] 779 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] 780 | 781 | [[package]] 782 | name = "pluggy" 783 | version = "1.5.0" 784 | description = "plugin and hook calling mechanisms for python" 785 | optional = false 786 | python-versions = ">=3.8" 787 | files = [ 788 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 789 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 790 | ] 791 | 792 | [package.extras] 793 | dev = ["pre-commit", "tox"] 794 | testing = ["pytest", "pytest-benchmark"] 795 | 796 | [[package]] 797 | name = "poetry" 798 | version = "1.8.5" 799 | description = "Python dependency management and packaging made easy." 800 | optional = false 801 | python-versions = "<4.0,>=3.8" 802 | files = [ 803 | {file = "poetry-1.8.5-py3-none-any.whl", hash = "sha256:5505fba69bf2a792b5d7402d21839c853644337392b745109b86a23010cce5f3"}, 804 | {file = "poetry-1.8.5.tar.gz", hash = "sha256:eb2c88d224f58f36df8f7b36d6c380c07d1001bca28bde620f68fc086e881b70"}, 805 | ] 806 | 807 | [package.dependencies] 808 | build = ">=1.0.3,<2.0.0" 809 | cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]} 810 | cleo = ">=2.1.0,<3.0.0" 811 | crashtest = ">=0.4.1,<0.5.0" 812 | dulwich = ">=0.21.2,<0.22.0" 813 | fastjsonschema = ">=2.18.0,<3.0.0" 814 | importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} 815 | installer = ">=0.7.0,<0.8.0" 816 | keyring = ">=24.0.0,<25.0.0" 817 | packaging = ">=23.1" 818 | pexpect = ">=4.7.0,<5.0.0" 819 | pkginfo = ">=1.12,<2.0" 820 | platformdirs = ">=3.0.0,<5" 821 | poetry-core = "1.9.1" 822 | poetry-plugin-export = ">=1.6.0,<2.0.0" 823 | pyproject-hooks = ">=1.0.0,<2.0.0" 824 | requests = ">=2.26,<3.0" 825 | requests-toolbelt = ">=1.0.0,<2.0.0" 826 | shellingham = ">=1.5,<2.0" 827 | tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} 828 | tomlkit = ">=0.11.4,<1.0.0" 829 | trove-classifiers = ">=2022.5.19" 830 | virtualenv = ">=20.26.6,<21.0.0" 831 | xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""} 832 | 833 | [[package]] 834 | name = "poetry-core" 835 | version = "1.9.1" 836 | description = "Poetry PEP 517 Build Backend" 837 | optional = false 838 | python-versions = "<4.0,>=3.8" 839 | files = [ 840 | {file = "poetry_core-1.9.1-py3-none-any.whl", hash = "sha256:6f45dd3598e0de8d9b0367360253d4c5d4d0110c8f5c71120a14f0e0f116c1a0"}, 841 | {file = "poetry_core-1.9.1.tar.gz", hash = "sha256:7a2d49214bf58b4f17f99d6891d947a9836c9899a67a5069f52d7b67217f61b8"}, 842 | ] 843 | 844 | [[package]] 845 | name = "poetry-plugin-export" 846 | version = "1.6.0" 847 | description = "Poetry plugin to export the dependencies to various formats" 848 | optional = false 849 | python-versions = ">=3.8,<4.0" 850 | files = [ 851 | {file = "poetry_plugin_export-1.6.0-py3-none-any.whl", hash = "sha256:2dce6204c9318f1f6509a11a03921fb3f461b201840b59f1c237b6ab454dabcf"}, 852 | {file = "poetry_plugin_export-1.6.0.tar.gz", hash = "sha256:091939434984267a91abf2f916a26b00cff4eee8da63ec2a24ba4b17cf969a59"}, 853 | ] 854 | 855 | [package.dependencies] 856 | poetry = ">=1.6.0,<2.0.0" 857 | poetry-core = ">=1.7.0,<2.0.0" 858 | 859 | [[package]] 860 | name = "ptyprocess" 861 | version = "0.7.0" 862 | description = "Run a subprocess in a pseudo terminal" 863 | optional = false 864 | python-versions = "*" 865 | files = [ 866 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 867 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 868 | ] 869 | 870 | [[package]] 871 | name = "pycparser" 872 | version = "2.21" 873 | description = "C parser in Python" 874 | optional = false 875 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 876 | files = [ 877 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 878 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 879 | ] 880 | 881 | [[package]] 882 | name = "pyproject-hooks" 883 | version = "1.0.0" 884 | description = "Wrappers to call pyproject.toml-based build backend hooks." 885 | optional = false 886 | python-versions = ">=3.7" 887 | files = [ 888 | {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, 889 | {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, 890 | ] 891 | 892 | [package.dependencies] 893 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 894 | 895 | [[package]] 896 | name = "pytest" 897 | version = "8.3.4" 898 | description = "pytest: simple powerful testing with Python" 899 | optional = false 900 | python-versions = ">=3.8" 901 | files = [ 902 | {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, 903 | {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, 904 | ] 905 | 906 | [package.dependencies] 907 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 908 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 909 | iniconfig = "*" 910 | packaging = "*" 911 | pluggy = ">=1.5,<2" 912 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 913 | 914 | [package.extras] 915 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 916 | 917 | [[package]] 918 | name = "pytest-xdist" 919 | version = "3.6.1" 920 | description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" 921 | optional = false 922 | python-versions = ">=3.8" 923 | files = [ 924 | {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, 925 | {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, 926 | ] 927 | 928 | [package.dependencies] 929 | execnet = ">=2.1" 930 | pytest = ">=7.0.0" 931 | 932 | [package.extras] 933 | psutil = ["psutil (>=3.0)"] 934 | setproctitle = ["setproctitle"] 935 | testing = ["filelock"] 936 | 937 | [[package]] 938 | name = "pywin32-ctypes" 939 | version = "0.2.2" 940 | description = "A (partial) reimplementation of pywin32 using ctypes/cffi" 941 | optional = false 942 | python-versions = ">=3.6" 943 | files = [ 944 | {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, 945 | {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, 946 | ] 947 | 948 | [[package]] 949 | name = "rapidfuzz" 950 | version = "3.6.1" 951 | description = "rapid fuzzy string matching" 952 | optional = false 953 | python-versions = ">=3.8" 954 | files = [ 955 | {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ac434fc71edda30d45db4a92ba5e7a42c7405e1a54cb4ec01d03cc668c6dcd40"}, 956 | {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a791168e119cfddf4b5a40470620c872812042f0621e6a293983a2d52372db0"}, 957 | {file = "rapidfuzz-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5a2f3e9df346145c2be94e4d9eeffb82fab0cbfee85bd4a06810e834fe7c03fa"}, 958 | {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23de71e7f05518b0bbeef55d67b5dbce3bcd3e2c81e7e533051a2e9401354eb0"}, 959 | {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d056e342989248d2bdd67f1955bb7c3b0ecfa239d8f67a8dfe6477b30872c607"}, 960 | {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01835d02acd5d95c1071e1da1bb27fe213c84a013b899aba96380ca9962364bc"}, 961 | {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f712e0bb5fea327e92aec8a937afd07ba8de4c529735d82e4c4124c10d5a0"}, 962 | {file = "rapidfuzz-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96cd19934f76a1264e8ecfed9d9f5291fde04ecb667faef5f33bdbfd95fe2d1f"}, 963 | {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e06c4242a1354cf9d48ee01f6f4e6e19c511d50bb1e8d7d20bcadbb83a2aea90"}, 964 | {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d73dcfe789d37c6c8b108bf1e203e027714a239e50ad55572ced3c004424ed3b"}, 965 | {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:06e98ff000e2619e7cfe552d086815671ed09b6899408c2c1b5103658261f6f3"}, 966 | {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:08b6fb47dd889c69fbc0b915d782aaed43e025df6979b6b7f92084ba55edd526"}, 967 | {file = "rapidfuzz-3.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1788ebb5f5b655a15777e654ea433d198f593230277e74d51a2a1e29a986283"}, 968 | {file = "rapidfuzz-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c65f92881753aa1098c77818e2b04a95048f30edbe9c3094dc3707d67df4598b"}, 969 | {file = "rapidfuzz-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:4243a9c35667a349788461aae6471efde8d8800175b7db5148a6ab929628047f"}, 970 | {file = "rapidfuzz-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:f59d19078cc332dbdf3b7b210852ba1f5db8c0a2cd8cc4c0ed84cc00c76e6802"}, 971 | {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbc07e2e4ac696497c5f66ec35c21ddab3fc7a406640bffed64c26ab2f7ce6d6"}, 972 | {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40cced1a8852652813f30fb5d4b8f9b237112a0bbaeebb0f4cc3611502556764"}, 973 | {file = "rapidfuzz-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82300e5f8945d601c2daaaac139d5524d7c1fdf719aa799a9439927739917460"}, 974 | {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf97c321fd641fea2793abce0e48fa4f91f3c202092672f8b5b4e781960b891"}, 975 | {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7420e801b00dee4a344ae2ee10e837d603461eb180e41d063699fb7efe08faf0"}, 976 | {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:060bd7277dc794279fa95522af355034a29c90b42adcb7aa1da358fc839cdb11"}, 977 | {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7e3375e4f2bfec77f907680328e4cd16cc64e137c84b1886d547ab340ba6928"}, 978 | {file = "rapidfuzz-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a490cd645ef9d8524090551016f05f052e416c8adb2d8b85d35c9baa9d0428ab"}, 979 | {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2e03038bfa66d2d7cffa05d81c2f18fd6acbb25e7e3c068d52bb7469e07ff382"}, 980 | {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b19795b26b979c845dba407fe79d66975d520947b74a8ab6cee1d22686f7967"}, 981 | {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:064c1d66c40b3a0f488db1f319a6e75616b2e5fe5430a59f93a9a5e40a656d15"}, 982 | {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3c772d04fb0ebeece3109d91f6122b1503023086a9591a0b63d6ee7326bd73d9"}, 983 | {file = "rapidfuzz-3.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:841eafba6913c4dfd53045835545ba01a41e9644e60920c65b89c8f7e60c00a9"}, 984 | {file = "rapidfuzz-3.6.1-cp311-cp311-win32.whl", hash = "sha256:266dd630f12696ea7119f31d8b8e4959ef45ee2cbedae54417d71ae6f47b9848"}, 985 | {file = "rapidfuzz-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:d79aec8aeee02ab55d0ddb33cea3ecd7b69813a48e423c966a26d7aab025cdfe"}, 986 | {file = "rapidfuzz-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:484759b5dbc5559e76fefaa9170147d1254468f555fd9649aea3bad46162a88b"}, 987 | {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b2ef4c0fd3256e357b70591ffb9e8ed1d439fb1f481ba03016e751a55261d7c1"}, 988 | {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:588c4b20fa2fae79d60a4e438cf7133d6773915df3cc0a7f1351da19eb90f720"}, 989 | {file = "rapidfuzz-3.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7142ee354e9c06e29a2636b9bbcb592bb00600a88f02aa5e70e4f230347b373e"}, 990 | {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1dfc557c0454ad22382373ec1b7df530b4bbd974335efe97a04caec936f2956a"}, 991 | {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:03f73b381bdeccb331a12c3c60f1e41943931461cdb52987f2ecf46bfc22f50d"}, 992 | {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b0ccc2ec1781c7e5370d96aef0573dd1f97335343e4982bdb3a44c133e27786"}, 993 | {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da3e8c9f7e64bb17faefda085ff6862ecb3ad8b79b0f618a6cf4452028aa2222"}, 994 | {file = "rapidfuzz-3.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde9b14302a31af7bdafbf5cfbb100201ba21519be2b9dedcf4f1048e4fbe65d"}, 995 | {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1a23eee225dfb21c07f25c9fcf23eb055d0056b48e740fe241cbb4b22284379"}, 996 | {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e49b9575d16c56c696bc7b06a06bf0c3d4ef01e89137b3ddd4e2ce709af9fe06"}, 997 | {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:0a9fc714b8c290261669f22808913aad49553b686115ad0ee999d1cb3df0cd66"}, 998 | {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:a3ee4f8f076aa92184e80308fc1a079ac356b99c39408fa422bbd00145be9854"}, 999 | {file = "rapidfuzz-3.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f056ba42fd2f32e06b2c2ba2443594873cfccc0c90c8b6327904fc2ddf6d5799"}, 1000 | {file = "rapidfuzz-3.6.1-cp312-cp312-win32.whl", hash = "sha256:5d82b9651e3d34b23e4e8e201ecd3477c2baa17b638979deeabbb585bcb8ba74"}, 1001 | {file = "rapidfuzz-3.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:dad55a514868dae4543ca48c4e1fc0fac704ead038dafedf8f1fc0cc263746c1"}, 1002 | {file = "rapidfuzz-3.6.1-cp312-cp312-win_arm64.whl", hash = "sha256:3c84294f4470fcabd7830795d754d808133329e0a81d62fcc2e65886164be83b"}, 1003 | {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e19d519386e9db4a5335a4b29f25b8183a1c3f78cecb4c9c3112e7f86470e37f"}, 1004 | {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01eb03cd880a294d1bf1a583fdd00b87169b9cc9c9f52587411506658c864d73"}, 1005 | {file = "rapidfuzz-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:be368573255f8fbb0125a78330a1a40c65e9ba3c5ad129a426ff4289099bfb41"}, 1006 | {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3e5af946f419c30f5cb98b69d40997fe8580efe78fc83c2f0f25b60d0e56efb"}, 1007 | {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f382f7ffe384ce34345e1c0b2065451267d3453cadde78946fbd99a59f0cc23c"}, 1008 | {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be156f51f3a4f369e758505ed4ae64ea88900dcb2f89d5aabb5752676d3f3d7e"}, 1009 | {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1936d134b6c513fbe934aeb668b0fee1ffd4729a3c9d8d373f3e404fbb0ce8a0"}, 1010 | {file = "rapidfuzz-3.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ff8eaf4a9399eb2bebd838f16e2d1ded0955230283b07376d68947bbc2d33d"}, 1011 | {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae598a172e3a95df3383634589660d6b170cc1336fe7578115c584a99e0ba64d"}, 1012 | {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd4ba4c18b149da11e7f1b3584813159f189dc20833709de5f3df8b1342a9759"}, 1013 | {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:0402f1629e91a4b2e4aee68043a30191e5e1b7cd2aa8dacf50b1a1bcf6b7d3ab"}, 1014 | {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1e12319c6b304cd4c32d5db00b7a1e36bdc66179c44c5707f6faa5a889a317c0"}, 1015 | {file = "rapidfuzz-3.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bbfae35ce4de4c574b386c43c78a0be176eeddfdae148cb2136f4605bebab89"}, 1016 | {file = "rapidfuzz-3.6.1-cp38-cp38-win32.whl", hash = "sha256:7fec74c234d3097612ea80f2a80c60720eec34947066d33d34dc07a3092e8105"}, 1017 | {file = "rapidfuzz-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:a553cc1a80d97459d587529cc43a4c7c5ecf835f572b671107692fe9eddf3e24"}, 1018 | {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:757dfd7392ec6346bd004f8826afb3bf01d18a723c97cbe9958c733ab1a51791"}, 1019 | {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2963f4a3f763870a16ee076796be31a4a0958fbae133dbc43fc55c3968564cf5"}, 1020 | {file = "rapidfuzz-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2f0274595cc5b2b929c80d4e71b35041104b577e118cf789b3fe0a77b37a4c5"}, 1021 | {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f211e366e026de110a4246801d43a907cd1a10948082f47e8a4e6da76fef52"}, 1022 | {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a59472b43879012b90989603aa5a6937a869a72723b1bf2ff1a0d1edee2cc8e6"}, 1023 | {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a03863714fa6936f90caa7b4b50ea59ea32bb498cc91f74dc25485b3f8fccfe9"}, 1024 | {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd95b6b7bfb1584f806db89e1e0c8dbb9d25a30a4683880c195cc7f197eaf0c"}, 1025 | {file = "rapidfuzz-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7183157edf0c982c0b8592686535c8b3e107f13904b36d85219c77be5cefd0d8"}, 1026 | {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ad9d74ef7c619b5b0577e909582a1928d93e07d271af18ba43e428dc3512c2a1"}, 1027 | {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b53137d81e770c82189e07a8f32722d9e4260f13a0aec9914029206ead38cac3"}, 1028 | {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:49b9ed2472394d306d5dc967a7de48b0aab599016aa4477127b20c2ed982dbf9"}, 1029 | {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:dec307b57ec2d5054d77d03ee4f654afcd2c18aee00c48014cb70bfed79597d6"}, 1030 | {file = "rapidfuzz-3.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4381023fa1ff32fd5076f5d8321249a9aa62128eb3f21d7ee6a55373e672b261"}, 1031 | {file = "rapidfuzz-3.6.1-cp39-cp39-win32.whl", hash = "sha256:8d7a072f10ee57c8413c8ab9593086d42aaff6ee65df4aa6663eecdb7c398dca"}, 1032 | {file = "rapidfuzz-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:ebcfb5bfd0a733514352cfc94224faad8791e576a80ffe2fd40b2177bf0e7198"}, 1033 | {file = "rapidfuzz-3.6.1-cp39-cp39-win_arm64.whl", hash = "sha256:1c47d592e447738744905c18dda47ed155620204714e6df20eb1941bb1ba315e"}, 1034 | {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eef8b346ab331bec12bbc83ac75641249e6167fab3d84d8f5ca37fd8e6c7a08c"}, 1035 | {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53251e256017e2b87f7000aee0353ba42392c442ae0bafd0f6b948593d3f68c6"}, 1036 | {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dede83a6b903e3ebcd7e8137e7ff46907ce9316e9d7e7f917d7e7cdc570ee05"}, 1037 | {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4da90e4c2b444d0a171d7444ea10152e07e95972bb40b834a13bdd6de1110c"}, 1038 | {file = "rapidfuzz-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ca3dfcf74f2b6962f411c33dd95b0adf3901266e770da6281bc96bb5a8b20de9"}, 1039 | {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bcc957c0a8bde8007f1a8a413a632a1a409890f31f73fe764ef4eac55f59ca87"}, 1040 | {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c9a50bea7a8537442834f9bc6b7d29d8729a5b6379df17c31b6ab4df948c2"}, 1041 | {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c23ceaea27e790ddd35ef88b84cf9d721806ca366199a76fd47cfc0457a81b"}, 1042 | {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b155e67fff215c09f130555002e42f7517d0ea72cbd58050abb83cb7c880cec"}, 1043 | {file = "rapidfuzz-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3028ee8ecc48250607fa8a0adce37b56275ec3b1acaccd84aee1f68487c8557b"}, 1044 | {file = "rapidfuzz-3.6.1.tar.gz", hash = "sha256:35660bee3ce1204872574fa041c7ad7ec5175b3053a4cb6e181463fc07013de7"}, 1045 | ] 1046 | 1047 | [package.extras] 1048 | full = ["numpy"] 1049 | 1050 | [[package]] 1051 | name = "requests" 1052 | version = "2.31.0" 1053 | description = "Python HTTP for Humans." 1054 | optional = false 1055 | python-versions = ">=3.7" 1056 | files = [ 1057 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 1058 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 1059 | ] 1060 | 1061 | [package.dependencies] 1062 | certifi = ">=2017.4.17" 1063 | charset-normalizer = ">=2,<4" 1064 | idna = ">=2.5,<4" 1065 | urllib3 = ">=1.21.1,<3" 1066 | 1067 | [package.extras] 1068 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 1069 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 1070 | 1071 | [[package]] 1072 | name = "requests-toolbelt" 1073 | version = "1.0.0" 1074 | description = "A utility belt for advanced users of python-requests" 1075 | optional = false 1076 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 1077 | files = [ 1078 | {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, 1079 | {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, 1080 | ] 1081 | 1082 | [package.dependencies] 1083 | requests = ">=2.0.1,<3.0.0" 1084 | 1085 | [[package]] 1086 | name = "ruff" 1087 | version = "0.8.3" 1088 | description = "An extremely fast Python linter and code formatter, written in Rust." 1089 | optional = false 1090 | python-versions = ">=3.7" 1091 | files = [ 1092 | {file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"}, 1093 | {file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"}, 1094 | {file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"}, 1095 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"}, 1096 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"}, 1097 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"}, 1098 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"}, 1099 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"}, 1100 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"}, 1101 | {file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"}, 1102 | {file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"}, 1103 | {file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"}, 1104 | {file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"}, 1105 | {file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"}, 1106 | {file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"}, 1107 | {file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"}, 1108 | {file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"}, 1109 | {file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"}, 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "secretstorage" 1114 | version = "3.3.3" 1115 | description = "Python bindings to FreeDesktop.org Secret Service API" 1116 | optional = false 1117 | python-versions = ">=3.6" 1118 | files = [ 1119 | {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, 1120 | {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, 1121 | ] 1122 | 1123 | [package.dependencies] 1124 | cryptography = ">=2.0" 1125 | jeepney = ">=0.6" 1126 | 1127 | [[package]] 1128 | name = "shellingham" 1129 | version = "1.5.4" 1130 | description = "Tool to Detect Surrounding Shell" 1131 | optional = false 1132 | python-versions = ">=3.7" 1133 | files = [ 1134 | {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, 1135 | {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, 1136 | ] 1137 | 1138 | [[package]] 1139 | name = "tomli" 1140 | version = "2.0.1" 1141 | description = "A lil' TOML parser" 1142 | optional = false 1143 | python-versions = ">=3.7" 1144 | files = [ 1145 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1146 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "tomlkit" 1151 | version = "0.12.3" 1152 | description = "Style preserving TOML library" 1153 | optional = false 1154 | python-versions = ">=3.7" 1155 | files = [ 1156 | {file = "tomlkit-0.12.3-py3-none-any.whl", hash = "sha256:b0a645a9156dc7cb5d3a1f0d4bab66db287fcb8e0430bdd4664a095ea16414ba"}, 1157 | {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "trove-classifiers" 1162 | version = "2024.1.8" 1163 | description = "Canonical source for classifiers on PyPI (pypi.org)." 1164 | optional = false 1165 | python-versions = "*" 1166 | files = [ 1167 | {file = "trove-classifiers-2024.1.8.tar.gz", hash = "sha256:6e36caf430ff6485c4b57a4c6b364a13f6a898d16b9417c6c37467e59c14b05a"}, 1168 | {file = "trove_classifiers-2024.1.8-py3-none-any.whl", hash = "sha256:3c1ff4deb10149c7e39ede6e5bbc107def64362ef1ee7590ec98d71fb92f1b6a"}, 1169 | ] 1170 | 1171 | [[package]] 1172 | name = "types-setuptools" 1173 | version = "75.6.0.20241126" 1174 | description = "Typing stubs for setuptools" 1175 | optional = false 1176 | python-versions = ">=3.8" 1177 | files = [ 1178 | {file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"}, 1179 | {file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"}, 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "typing-extensions" 1184 | version = "4.9.0" 1185 | description = "Backported and Experimental Type Hints for Python 3.8+" 1186 | optional = false 1187 | python-versions = ">=3.8" 1188 | files = [ 1189 | {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, 1190 | {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "urllib3" 1195 | version = "2.1.0" 1196 | description = "HTTP library with thread-safe connection pooling, file post, and more." 1197 | optional = false 1198 | python-versions = ">=3.8" 1199 | files = [ 1200 | {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, 1201 | {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, 1202 | ] 1203 | 1204 | [package.extras] 1205 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 1206 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 1207 | zstd = ["zstandard (>=0.18.0)"] 1208 | 1209 | [[package]] 1210 | name = "virtualenv" 1211 | version = "20.27.0" 1212 | description = "Virtual Python Environment builder" 1213 | optional = false 1214 | python-versions = ">=3.8" 1215 | files = [ 1216 | {file = "virtualenv-20.27.0-py3-none-any.whl", hash = "sha256:44a72c29cceb0ee08f300b314848c86e57bf8d1f13107a5e671fb9274138d655"}, 1217 | {file = "virtualenv-20.27.0.tar.gz", hash = "sha256:2ca56a68ed615b8fe4326d11a0dca5dfbe8fd68510fb6c6349163bed3c15f2b2"}, 1218 | ] 1219 | 1220 | [package.dependencies] 1221 | distlib = ">=0.3.7,<1" 1222 | filelock = ">=3.12.2,<4" 1223 | platformdirs = ">=3.9.1,<5" 1224 | 1225 | [package.extras] 1226 | 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)"] 1227 | 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)"] 1228 | 1229 | [[package]] 1230 | name = "xattr" 1231 | version = "1.1.0" 1232 | description = "Python wrapper for extended filesystem attributes" 1233 | optional = false 1234 | python-versions = ">=3.8" 1235 | files = [ 1236 | {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef2fa0f85458736178fd3dcfeb09c3cf423f0843313e25391db2cfd1acec8888"}, 1237 | {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccab735d0632fe71f7d72e72adf886f45c18b7787430467ce0070207882cfe25"}, 1238 | {file = "xattr-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9013f290387f1ac90bccbb1926555ca9aef75651271098d99217284d9e010f7c"}, 1239 | {file = "xattr-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd5dfbcee73c7be057676ecb900cabb46c691aff4397bf48c579ffb30bb963"}, 1240 | {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6480589c1dac7785d1f851347a32c4a97305937bf7b488b857fe8b28a25de9e9"}, 1241 | {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08f61cbed52dc6f7c181455826a9ff1e375ad86f67dd9d5eb7663574abb32451"}, 1242 | {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:918e1f83f2e8a072da2671eac710871ee5af337e9bf8554b5ce7f20cdb113186"}, 1243 | {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0f06e0c1e4d06b4e0e49aaa1184b6f0e81c3758c2e8365597918054890763b53"}, 1244 | {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a641ac038a9f53d2f696716147ca4dbd6a01998dc9cd4bc628801bc0df7f4d"}, 1245 | {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e4ca0956fd11679bb2e0c0d6b9cdc0f25470cc00d8da173bb7656cc9a9cf104"}, 1246 | {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6881b120f9a4b36ccd8a28d933bc0f6e1de67218b6ce6e66874e0280fc006844"}, 1247 | {file = "xattr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab29d9288aa28e68a6f355ddfc3f0a7342b40c9012798829f3e7bd765e85c2c"}, 1248 | {file = "xattr-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c80bbf55339c93770fc294b4b6586b5bf8e85ec00a4c2d585c33dbd84b5006"}, 1249 | {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1418705f253b6b6a7224b69773842cac83fcbcd12870354b6e11dd1cd54630f"}, 1250 | {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687e7d18611ef8d84a6ecd8f4d1ab6757500c1302f4c2046ce0aa3585e13da3f"}, 1251 | {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6ceb9efe0657a982ccb8b8a2efe96b690891779584c901d2f920784e5d20ae3"}, 1252 | {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b489b7916f239100956ea0b39c504f3c3a00258ba65677e4c8ba1bd0b5513446"}, 1253 | {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0a9c431b0e66516a078125e9a273251d4b8e5ba84fe644b619f2725050d688a0"}, 1254 | {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a5921ea3313cc1c57f2f53b63ea8ca9a91e48f4cc7ebec057d2447ec82c7efe"}, 1255 | {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6ad2a7bd5e6cf71d4a862413234a067cf158ca0ae94a40d4b87b98b62808498"}, 1256 | {file = "xattr-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0683dae7609f7280b0c89774d00b5957e6ffcb181c6019c46632b389706b77e6"}, 1257 | {file = "xattr-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cb15cd94e5ef8a0ef02309f1bf973ba0e13c11e87686e983f371948cfee6af"}, 1258 | {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff6223a854229055e803c2ad0c0ea9a6da50c6be30d92c198cf5f9f28819a921"}, 1259 | {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d44e8f955218638c9ab222eed21e9bd9ab430d296caf2176fb37abe69a714e5c"}, 1260 | {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:caab2c2986c30f92301f12e9c50415d324412e8e6a739a52a603c3e6a54b3610"}, 1261 | {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d6eb7d5f281014cd44e2d847a9107491af1bf3087f5afeded75ed3e37ec87239"}, 1262 | {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:47a3bdfe034b4fdb70e5941d97037405e3904accc28e10dbef6d1c9061fb6fd7"}, 1263 | {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00d2b415cf9d6a24112d019e721aa2a85652f7bbc9f3b9574b2d1cd8668eb491"}, 1264 | {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:78b377832dd0ee408f9f121a354082c6346960f7b6b1480483ed0618b1912120"}, 1265 | {file = "xattr-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6461a43b585e5f2e049b39bcbfcb6391bfef3c5118231f1b15d10bdb89ef17fe"}, 1266 | {file = "xattr-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24d97f0d28f63695e3344ffdabca9fcc30c33e5c8ccc198c7524361a98d526f2"}, 1267 | {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad47d89968c9097900607457a0c89160b4771601d813e769f68263755516065"}, 1268 | {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc53cab265f6e8449bd683d5ee3bc5a191e6dd940736f3de1a188e6da66b0653"}, 1269 | {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cd11e917f5b89f2a0ad639d9875943806c6c9309a3dd02da5a3e8ef92db7bed9"}, 1270 | {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5a78c7558989492c4cb7242e490ffb03482437bf782967dfff114e44242343"}, 1271 | {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cebcf8a303a44fbc439b68321408af7267507c0d8643229dbb107f6c132d389c"}, 1272 | {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b0d73150f2f9655b4da01c2369eb33a294b7f9d56eccb089819eafdbeb99f896"}, 1273 | {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:793c01deaadac50926c0e1481702133260c7cb5e62116762f6fe1543d07b826f"}, 1274 | {file = "xattr-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e189e440bcd04ccaad0474720abee6ee64890823ec0db361fb0a4fb5e843a1bf"}, 1275 | {file = "xattr-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afacebbc1fa519f41728f8746a92da891c7755e6745164bd0d5739face318e86"}, 1276 | {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b1664edf003153ac8d1911e83a0fc60db1b1b374ee8ac943f215f93754a1102"}, 1277 | {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda2684228798e937a7c29b0e1c7ef3d70e2b85390a69b42a1c61b2039ba81de"}, 1278 | {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b735ac2625a4fc2c9343b19f806793db6494336338537d2911c8ee4c390dda46"}, 1279 | {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa6a7af7a4ada43f15ccc58b6f9adcdbff4c36ba040013d2681e589e07ae280a"}, 1280 | {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1059b2f726e2702c8bbf9bbf369acfc042202a4cc576c2dec6791234ad5e948"}, 1281 | {file = "xattr-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2255f36ebf2cb2dbf772a7437ad870836b7396e60517211834cf66ce678b595"}, 1282 | {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba4f80b9855cc98513ddf22b7ad8551bc448c70d3147799ea4f6c0b758fb466"}, 1283 | {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb70c16e7c3ae6ba0ab6c6835c8448c61d8caf43ea63b813af1f4dbe83dd156"}, 1284 | {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83652910ef6a368b77b00825ad67815e5c92bfab551a848ca66e9981d14a7519"}, 1285 | {file = "xattr-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a92aff66c43fa3e44cbeab7cbeee66266c91178a0f595e044bf3ce51485743b"}, 1286 | {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f71b673339aeaae1f6ea9ef8ea6c9643c8cd0df5003b9a0eaa75403e2e06c"}, 1287 | {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a20de1c47b5cd7b47da61799a3b34e11e5815d716299351f82a88627a43f9a96"}, 1288 | {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23705c7079b05761ff2fa778ad17396e7599c8759401abc05b312dfb3bc99f69"}, 1289 | {file = "xattr-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27272afeba8422f2a9d27e1080a9a7b807394e88cce73db9ed8d2dde3afcfb87"}, 1290 | {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd43978966de3baf4aea367c99ffa102b289d6c2ea5f3d9ce34a203dc2f2ab73"}, 1291 | {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded771eaf27bb4eb3c64c0d09866460ee8801d81dc21097269cf495b3cac8657"}, 1292 | {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca300c0acca4f0cddd2332bb860ef58e1465d376364f0e72a1823fdd58e90d"}, 1293 | {file = "xattr-1.1.0.tar.gz", hash = "sha256:fecbf3b05043ed3487a28190dec3e4c4d879b2fcec0e30bafd8ec5d4b6043630"}, 1294 | ] 1295 | 1296 | [package.dependencies] 1297 | cffi = ">=1.16.0" 1298 | 1299 | [package.extras] 1300 | test = ["pytest"] 1301 | 1302 | [[package]] 1303 | name = "zipp" 1304 | version = "3.17.0" 1305 | description = "Backport of pathlib-compatible object wrapper for zip files" 1306 | optional = false 1307 | python-versions = ">=3.8" 1308 | files = [ 1309 | {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, 1310 | {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, 1311 | ] 1312 | 1313 | [package.extras] 1314 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 1315 | testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 1316 | 1317 | [metadata] 1318 | lock-version = "2.0" 1319 | python-versions = "^3.8" 1320 | content-hash = "9a995bc2aa0e66506d24ae948a9c70c0109e733161911e84f3d1321cc9ec816a" 1321 | --------------------------------------------------------------------------------