pax_global_header00006660000000000000000000000064152370627660014530gustar00rootroot0000000000000052 comment=df50df2b836e06fcecc6adbb615e95fb63afffe2 DeepL-deepl-python-df50df2/000077500000000000000000000000001523706276600156235ustar00rootroot00000000000000DeepL-deepl-python-df50df2/.bumpversion.toml000066400000000000000000000014441523706276600211520ustar00rootroot00000000000000# Configuration file for bumpversion GitHub action # See https://github.com/callowayproject/bump-my-version [tool.bumpversion] current_version = "1.32.0" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" replace = "{new_version}" regex = false ignore_missing_version = false tag = false allow_dirty = false commit = false [[tool.bumpversion.files]] filename = "deepl/version.py" [[tool.bumpversion.files]] filename = "tests/test_general.py" search = "\"{current_version}\" == deepl.__version__" replace = "\"{new_version}\" == deepl.__version__" [[tool.bumpversion.files]] filename = "pyproject.toml" search = "name = \"deepl\"\nversion = \"{current_version}\"" replace = "name = \"deepl\"\nversion = \"{new_version}\"" DeepL-deepl-python-df50df2/.github/000077500000000000000000000000001523706276600171635ustar00rootroot00000000000000DeepL-deepl-python-df50df2/.github/workflows/000077500000000000000000000000001523706276600212205ustar00rootroot00000000000000DeepL-deepl-python-df50df2/.github/workflows/add_issues_to_kanban.yml000066400000000000000000000005351523706276600261050ustar00rootroot00000000000000name: Add bugs to bugs project on: issues: types: - opened jobs: add-to-project: name: Add issue to project runs-on: ubuntu-latest steps: - uses: actions/add-to-project@v0.5.0 with: project-url: https://github.com/orgs/DeepLcom/projects/1 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} DeepL-deepl-python-df50df2/.github/workflows/bumpversion.yml000066400000000000000000000152331523706276600243200ustar00rootroot00000000000000# GitHub action to bump version, update changelog, and create pull request for review. name: Bump Version run-name: Bump version (${{ inputs.bump-type }}) by @${{ github.actor }} # Note: Enable GitHub Actions to create pull requests in repository settings: # Settings -> Actions -> General -> Workflow permissions -> Allow GitHub Actions to create and approve pull requests permissions: {} concurrency: group: bumpversion cancel-in-progress: false on: workflow_dispatch: inputs: bump-type: description: 'Version bump type' required: true default: 'patch' type: choice options: - major - minor - patch fail-on-empty-changelog: description: 'Fail if changelog is empty' required: false default: true type: boolean jobs: bump_version: runs-on: ubuntu-latest permissions: contents: write pull-requests: write outputs: version: ${{ steps.bump.outputs.current-version }} pr-number: ${{ steps.create_pr.outputs.pull-request-number }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 https://github.com/actions/checkout/releases/tag/v6.0.2 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Python environment uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 https://github.com/astral-sh/setup-uv/releases/tag/v7.6.0 - name: Install bump-my-version run: uv tool install bump-my-version - name: Bump version id: bump shell: bash run: | echo "::notice::Bumping version with type: ${{ inputs.bump-type }}" bump-my-version bump ${{ inputs.bump-type }} CURRENT_VERSION=$(bump-my-version show current_version) echo "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT echo "::notice::Current version: $CURRENT_VERSION" - name: Update changelog and extract release notes id: changelog shell: bash run: | VERSION="${{ steps.bump.outputs.current-version }}" # Stamp Unreleased section with version number and todays date npx --yes keep-a-changelog@2.8.0 --release "$VERSION" # Add new Unreleased section npx --yes keep-a-changelog@2.8.0 --create RELEASE_NOTES=$(npx --yes keep-a-changelog@2.8.0 --latest-release-full) RELEASE_BODY=$(echo "$RELEASE_NOTES" | tail -n +2) # omits ## [x.y.z] line if ${{ inputs.fail-on-empty-changelog }} && [ -z "$(echo "$RELEASE_BODY" | tr -d '[:space:]')" ]; then echo "::error::Changelog for $VERSION is empty" exit 1 fi echo "release-notes<> $GITHUB_OUTPUT echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT echo "EOF_BUMPVERSION" >> $GITHUB_OUTPUT - name: Create Pull Request id: create_pr uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 https://github.com/peter-evans/create-pull-request/releases/tag/v8.1.0 with: commit-message: "chore: bump version to ${{ steps.bump.outputs.current-version }} (${{ inputs.bump-type }} bump)" title: "Bump version to ${{ steps.bump.outputs.current-version }} (${{ inputs.bump-type }} bump)" body: | This PR **${{ inputs.bump-type }}** bumps the version to `${{ steps.bump.outputs.current-version }}` and updates the changelog. ### Reviewer Checklist Please verify the following before merging: - [ ] **Version bump type is appropriate**: Confirm that the `${{ inputs.bump-type }}` bump matches the nature of changes since the last version: - **Major**: Breaking changes or significant API modifications - **Minor**: New features or functionality additions (backward compatible) - **Patch**: Bug fixes, documentation updates, or minor improvements - **no bump necessary**: Changes only to tests, CI, documentation, or examples. If the bump type is not appropriate, close the PR and re-run the workflow with the correct bump type. - [ ] **Changelog accuracy**: Review that the changelog entries accurately reflect the changes being released, and are understandable to end users. If not, edit the changelog before merging. ### Next Steps After merging this PR, **trigger a release** to publish version `${{ steps.bump.outputs.current-version }}`. ### Changelog (automatically extracted from Unreleased section) ${{ steps.changelog.outputs.release-notes }} --- *This PR was automatically created by the Bump Version workflow* branch: bumpversion-${{ steps.bump.outputs.current-version }} token: ${{ secrets.GITHUB_TOKEN }} delete-branch: true - name: Summary if: always() run: | echo "## Workflow Summary" >> $GITHUB_STEP_SUMMARY echo "- **Bump Type:** ${{ inputs.bump-type }}" >> $GITHUB_STEP_SUMMARY echo "- **Version:** ${{ steps.bump.outputs.current-version }}" >> $GITHUB_STEP_SUMMARY if [[ "${{ steps.create_pr.outputs.pull-request-number }}" != "" ]]; then PR_NUMBER="${{ steps.create_pr.outputs.pull-request-number }}" PR_URL="https://github.com/${{ github.repository }}/pull/$PR_NUMBER" echo "- **Pull Request:** [#$PR_NUMBER]($PR_URL)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Next Steps" >> $GITHUB_STEP_SUMMARY echo "1. Review and merge [Pull Request #$PR_NUMBER]($PR_URL)" >> $GITHUB_STEP_SUMMARY echo "2. **Trigger a release** to publish version \`${{ steps.bump.outputs.current-version }}\`" >> $GITHUB_STEP_SUMMARY else echo "" >> $GITHUB_STEP_SUMMARY echo "### ⚠️ Warning" >> $GITHUB_STEP_SUMMARY echo "Pull request creation failed. Please check the workflow logs." >> $GITHUB_STEP_SUMMARY fi echo "" >> $GITHUB_STEP_SUMMARY echo "### 📋 Workflow Details" >> $GITHUB_STEP_SUMMARY echo "- **Triggered by:** @${{ github.actor }}" >> $GITHUB_STEP_SUMMARY echo "- **Repository:** ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY echo "- **Run ID:** [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY DeepL-deepl-python-df50df2/.github/workflows/run_ci.yml000066400000000000000000000162531523706276600232310ustar00rootroot00000000000000name: Python on: push: branches: [ "main" ] pull_request: branches: [ "main" ] schedule: - cron: '10 0 * * *' env: PIP_CACHE_DIR: "${{ github.workspace }}/.cache/pip" XDG_CACHE_HOME: "${{ github.workspace }}/.cache" POETRY_VIRTUALENVS_IN_PROJECT: "true" REQUESTS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt" SECRET_DETECTION_JSON_REPORT_FILE: "gitleaks.json" jobs: black: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install dependencies run: | python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install poetry==2.1.2 poetry install -vv -E keyring - name: Run black check run: | source .venv/bin/activate poetry run black --check . flake8: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install dependencies run: | python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install poetry==2.1.2 poetry install -vv -E keyring - name: Run flake8 check run: | source .venv/bin/activate poetry run flake8 deepl tests license_check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Run license check run: | ./license_checker.sh '*.py' | tee license_check_output.txt [ ! -s license_check_output.txt ] mypy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install dependencies run: | python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install poetry==2.1.2 poetry install -vv -E keyring - name: Run mypy check run: | source .venv/bin/activate poetry run mypy --exclude 'examples/' . secret_detection: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install and run secret detection run: | wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.4/gitleaks_8.18.4_linux_x64.tar.gz tar -xzf gitleaks_8.18.4_linux_x64.tar.gz EXITCODE=0 ./gitleaks detect -r ${SECRET_DETECTION_JSON_REPORT_FILE} --source . --log-opts="--all --full-history" || EXITCODE=$? if [[ $EXITCODE -ne 0 ]]; then exit $EXITCODE fi - name: Upload secret detection artifact uses: actions/upload-artifact@v4 with: name: secret-detection-results path: gitleaks.json package: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install dependencies run: | python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install poetry==2.1.2 poetry install -vv -E keyring - name: Build package run: | source .venv/bin/activate poetry build --verbose --no-interaction - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: build-artifacts path: dist/ # Test and `pypi upload` stage are disabled/missing for now. Code needs to be tested ####################################################### # test: # runs-on: ${{ matrix.config.docker-image }} # strategy: # matrix: # config: # - docker-image: python:3.6 # use-mock-server: use mock server # use-old-poetry-version: use old poetry version # - docker-image: python:3.7 # use-mock-server: use mock server # - docker-image: python:3.8 # use-mock-server: use mock server # - docker-image: python:3.9 # use-mock-server: use mock server # - docker-image: python:3.10 # use-mock-server: use mock server # - docker-image: python:3.11 # use-mock-server: use mock server # - docker-image: python:3.11 # - docker-image: python:3.6 # use-mock-server: use mock server # use-old-poetry-version: use old poetry version # extra-poetry-add-argument: requests@2.0 # - docker-image: python:3.9 # use-mock-server: use mock server # extra-poetry-add-argument: requests@2.0 # # Set minimum possible requests and urllib3 versions to work with Python 3.11 # - docker-image: python:3.11 # use-mock-server: use mock server # extra-poetry-add-argument: requests@2.20 urllib3@1.23 # steps: # - name: Checkout # uses: actions/checkout@v2 # - name: Run tests # run: | # if [[ ! -z "${{ matrix.config.extra-poetry-add-argument }}" ]]; then # echo "Running poetry add ${{ matrix.config.extra-poetry-add-argument }}" # poetry add ${{ matrix.config.extra-poetry-add-argument }} # fi # if [[ ! -z "${{ matrix.config.use-mock-server }}" ]]; then # echo "Using mock server" # export DEEPL_SERVER_URL=http://deepl-mock:3000 # export DEEPL_MOCK_SERVER_PORT=3000 # export DEEPL_PROXY_URL=http://deepl-mock:3001 # export DEEPL_MOCK_PROXY_SERVER_PORT=3001 # fi # poetry run coverage run -m pytest --junit-xml test_report.xml # poetry run coverage report # poetry run coverage xml # - name: Upload test results # uses: actions/upload-artifact@v2 # with: # name: test-results # path: test_report.xml # mustache_example: # runs-on: ubuntu-latest # steps: # - name: Checkout # uses: actions/checkout@v2 # - name: Install dependencies # run: | # python3 -m venv .venv # source .venv/bin/activate # pip install --upgrade pip # pip install poetry==2.1.2 # poetry install -vv -E keyring # - name: Run mustache example # run: | # cd examples/mustache # pip install deepl # python . --help # python . --from en --to de "Hello {{user}}, your balance is {{{balance}}} dollars." > mustache_result.txt # cat mustache_result.txt # grep -q "{{user}}" mustache_result.txt # grep -q "{{{balance}}}" mustache_result.txt # basic_usage_example: # runs-on: ubuntu-latest # steps: # - name: Checkout # uses: actions/checkout@v2 # - name: Install dependencies # run: | # python3 -m venv .venv # source .venv/bin/activate # pip install --upgrade pip # pip install poetry==2.1.2 # poetry install -vv -E keyring # - name: Run basic usage example # run: | # poetry build --verbose --no-interaction # cd examples/basic_usage # python3 -m venv .examplevenv # source .examplevenv/bin/activate # pip install ../../dist/deepl-*.tar.gz # set -o pipefail # python . 2>&1 | tee basic_usage_result.txt # grep -q "Success" basic_usage_result.txt # pip install mypy # mypy . DeepL-deepl-python-df50df2/.gitignore000066400000000000000000000001371523706276600176140ustar00rootroot00000000000000__pycache__ .idea/ .venv*/ venv*/ dist/ .pytest_cache/ htmlcov/ .coverage .python-version .tox/DeepL-deepl-python-df50df2/.gitlab-ci.yml000066400000000000000000000147221523706276600202650ustar00rootroot00000000000000# Note: This GitLab CI configuration is used for internal testing, users can ignore it. include: - project: '${CI_PROJECT_NAMESPACE}/ci-libs-for-client-libraries' file: - '/${CI_PROJECT_NAME}/.gitlab-ci.yml' - project: 'deepl-org/deepl/devex/gitlab-ci-lib' file: - '/templates/.buildkit.yml' - '/templates/.secret-detection.yml' - '/templates/.gitlab-release.yml' - template: Security/SAST.gitlab-ci.yml # Global -------------------------- image: python:3.13 variables: PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip" XDG_CACHE_HOME: "${CI_PROJECT_DIR}/.cache" POETRY_VIRTUALENVS_IN_PROJECT: "true" REQUESTS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt" GITLAB_ADVANCED_SAST_ENABLED: 'true' cache: key: prefix: ${CI_JOB_IMAGE} files: - pyproject.toml - poetry.lock paths: - .cache/pip - .venv workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_TAG - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' stages: - check - build - test - publish before_script: - python3 -m venv .venv - source .venv/bin/activate - pip install --upgrade pip - pip install poetry==2.1.2 - poetry --version - poetry install -vv -E keyring # stage: check ---------------------- .black_base: stage: check script: poetry run black --check . black_scheduled: extends: .black_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 black_manual: extends: .black_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" .flake8_base: stage: check script: poetry run flake8 deepl tests flake8_scheduled: extends: .flake8_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 flake8_manual: extends: .flake8_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" .license_check_base: stage: check script: - ./license_checker.sh '*.py' | tee license_check_output.txt - '[ ! -s license_check_output.txt ]' license_check_scheduled: extends: .license_check_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 license_check_manual: extends: .license_check_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" secret_detection: extends: .secret-detection stage: check image: !reference [.secret-detection, image] before_script: - echo "overriding default before_script..." rules: - if: $CI_MERGE_REQUEST_ID .mypy_base: stage: check script: poetry run mypy --exclude 'examples/' . mypy_scheduled: extends: .mypy_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 mypy_manual: extends: .mypy_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" gitlab-advanced-sast: stage: check before_script: - '' rules: - when: always variables: SAST_EXCLUDED_PATHS: '$DEFAULT_SAST_EXCLUDED_PATHS' GIT_STRATEGY: clone semgrep-sast: stage: check before_script: - '' rules: - when: always variables: SAST_EXCLUDED_PATHS: '$DEFAULT_SAST_EXCLUDED_PATHS' GIT_STRATEGY: clone # stage: build ---------------------- package: stage: build script: - poetry build --verbose --no-interaction artifacts: paths: - dist/ # stage: test ------------------------- .test_base: stage: test extends: .test parallel: matrix: - DOCKER_IMAGE: "python:3.13" - DOCKER_IMAGE: "python:3.9" USE_MOCK_SERVER: "use mock server" - DOCKER_IMAGE: "python:3.10" USE_MOCK_SERVER: "use mock server" - DOCKER_IMAGE: "python:3.11" USE_MOCK_SERVER: "use mock server" - DOCKER_IMAGE: "python:3.12" USE_MOCK_SERVER: "use mock server" - DOCKER_IMAGE: "python:3.13" USE_MOCK_SERVER: "use mock server" image: ${DOCKER_IMAGE} script: - > if [[ ! -z "${EXTRA_POETRY_ADD_ARGUMENT}" ]]; then echo "Running poetry add ${EXTRA_POETRY_ADD_ARGUMENT}" poetry add ${EXTRA_POETRY_ADD_ARGUMENT} fi - > if [[ ! -z "${USE_MOCK_SERVER}" ]]; then echo "Using mock server" export DEEPL_SERVER_URL=http://deepl-mock:3000 export DEEPL_MOCK_SERVER_PORT=3000 export DEEPL_PROXY_URL=http://deepl-mock:3001 export DEEPL_MOCK_PROXY_SERVER_PORT=3001 fi - poetry run coverage run -m pytest --junit-xml test_report.xml - poetry run coverage report - poetry run coverage xml artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml junit: - test_report.xml when: always test_scheduled: extends: .test_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 test_manual: stage: test extends: .test_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" .mustache_example_base: stage: test extends: .test script: - cd examples/mustache - pip install deepl - python . --help - python . --from en --to de "Hello {{user}}, your balance is {{{balance}}} dollars." > mustache_result.txt - cat mustache_result.txt - grep -q "{{user}}" mustache_result.txt - grep -q "{{{balance}}}" mustache_result.txt mustache_example_scheduled: extends: .mustache_example_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 mustache_example_manual: stage: test extends: .mustache_example_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" .basic_usage_example_base: stage: test extends: .test script: - poetry build --verbose --no-interaction - cd examples/basic_usage - python3 -m venv .examplevenv - source .examplevenv/bin/activate - pip install ../../dist/deepl-*.tar.gz - set -o pipefail - python . 2>&1 | tee basic_usage_result.txt - grep -q "Success" basic_usage_result.txt - pip install mypy - mypy . basic_usage_example_scheduled: extends: .basic_usage_example_base rules: - if: $CI_PIPELINE_SOURCE == "schedule" retry: 2 basic_usage_example_manual: stage: test extends: .basic_usage_example_base rules: - if: $CI_PIPELINE_SOURCE != "schedule" # stage: publish ------------------------- pypi upload: extends: .publish stage: publish dependencies: - package rules: - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/' script: - poetry publish --verbose --no-interaction gitlab release: stage: publish image: registry.gitlab.com/gitlab-org/release-cli:latest extends: .create_gitlab_release before_script: - echo "overriding default before_script..." rules: - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/' DeepL-deepl-python-df50df2/CHANGELOG.md000066400000000000000000000524701523706276600174440ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [1.32.0] - 2026-08-12 ### Added - Added `correct_text()` method to the `DeepLClient` class for fixing spelling and grammar errors in text(s) via the `/v2/write/correct` endpoint. ## [1.31.0] - 2026-08-11 ### Added - Added support for using multiple glossaries via the `glossary_ids` parameter (up to 5 glossaries) in `translate_text()`, `translate_document()`, `translate_document_upload()`, and `translate_document_from_filepath()`. - Added support for style rules in document translation via the `style_rule` parameter in `translate_document()`, `translate_document_upload()`, and `translate_document_from_filepath()`. - Added support for translation memories in document translation via the `translation_memory` and `translation_memory_threshold` parameters in `translate_document()`, `translate_document_upload()`, and `translate_document_from_filepath()`. - Added `--glossary-ids` CLI argument for the `text` and `document` commands, allowing up to 5 glossaries to be used per translation. - Added `--style-id`, `--translation-memory-id`, and `--translation-memory-threshold` CLI arguments to the `document` command. - Added support for the translation memory management APIs: `get_translation_memory()`, `list_translation_memory_segments()`, `delete_translation_memory()`, `create_translation_memory_import()`, `upload_translation_memory_file()`, `create_translation_memory_export()`, `get_translation_memory_job()`, `wait_until_translation_memory_job_done()`, and `download_translation_memory_export()`, along with the `import_translation_memory_from_filepath()` and `export_translation_memory_to_filepath()` convenience functions that create the job, transfer the TMX file, and wait for the job to finish. Because the API detects the file upload asynchronously, an import job keeps reporting `awaiting_input` for a while after the upload; the wait loop polls through that status, so pass `timeout_s` to bound the wait. - Added the `TranslationMemorySegments`, `TranslationMemorySegment`, `TranslationMemoryTargetSegment`, `TranslationMemoryImport`, `TranslationMemoryExport`, `TranslationMemoryJob`, and `TranslationMemoryJobResult` types. - Added `creation_time` and `updated_time` properties to `TranslationMemoryInfo`. - Added the `translation-memory` CLI command with the `list`, `get`, `segments`, `import`, `export`, `job`, and `delete` subcommands. ## [1.30.0] - 2026-04-09 ### Added - Added support for translation memories in text translation via `translation_memory` and `translation_memory_threshold` parameters in `translate_text()`. - Added `list_translation_memories()` method to the `DeepLClient` class for listing available translation memories. - Added `--translation-memory-id` and `--translation-memory-threshold` CLI arguments. ### Fixed - Fixed "gitlab release" CI job to explicitly set the release-cli image, avoiding issues with missing `$HOME` from the global image. ## [1.29.0] - 2026-03-26 ### Added - Added new language constants from January 2026 API release of 81 new languages. - Added support for style rules CRUD endpoints in the `DeepLClient` class: `create_style_rule()`, `get_style_rule()`, `update_style_rule_name()`, `update_style_rule_configured_rules()`, and `delete_style_rule()`. - Added support for style rule custom instruction CRUD endpoints in the `DeepLClient` class: `create_style_rule_custom_instruction()`, `get_style_rule_custom_instruction()`, `update_style_rule_custom_instruction()`, and `delete_style_rule_custom_instruction()`. Please refer to the README for usage instructions. ### Changed - Updated formality tests to accept either formal or informal output when using default formality, since the default formality is automatic. ## [1.28.0] - 2026-02-05 ### Changed - Updated three tests to be less flakey and reflect new behavior regarding the model type. - Improved `NotFoundException` error message by removing the misleading "check server_url" suggestion. ### Fixed - Fixed a bug where translating with style rules would sometimes incorrectly result in a "target_lang must match style rule language" error ### Security - Updated dependencies to fix CVE-2025-66418 and CVE-2025-66471 ## [1.27.0] - 2025-12-10 ### Added - Added `tag_handling_version` parameter to `translate_text()` to specify which version of the tag handling algorithm to use. Options are `v1` and `v2`. - Added an example CLI script for realtime audio translation using DeepL's Voice API. ## [1.26.0] - 2025-12-03 ### Added - Added `custom_instructions` parameter to `translate_text()` to customize translation behavior with up to 10 instructions (max 300 characters each). Only supported for target languages: `de`, `en`, `es`, `fr`, `it`, `ja`, `ko`, `zh` and their variants. Note: using the `custom_instructions` parameter will use the `quality_optimized` model type as the default. Requests combining `custom_instructions` and the `latency_optimized` model type will be rejected. ### Fixed - Fixed the cleanup of multilingual glossaries in the test suite where glossaries were not properly deleted after the test execution ## [1.25.0] - 2025-11-12 ### Added - Added support for the `GET /v3/style_rules` endpoint in the client library, the implementation can be found in the `DeepLClient` class. Please refer to the README for usage instructions - Added `style_id` option to `translate_text()` which allows text translation with style rules. ## [1.24.0] - 2025-11-04 ### Changed - `extra_body_parameters` will now overwrite already set HTTP parameters ## [1.23.0] - 2025-09-30 ### Added - Added feature to add arbitrary body parameters in `translate_text` ### Deprecated - Dropped support for Python 3.8 and older. ### Security - Updated `requests` minimum version to 2.32.4 to resolve security advisories CVE-2024-47081 and CVE-2024-35195. ## [1.22.0] - 2025-04-30 ### Added - Added support for the /v3 Glossary APIs in the client library while providing backwards compatability for the previous /v2 Glossary endpoints. Please refer to the README for usage instructions. ## [1.21.1] - 2025-03-12 ### Added - (beta) optional parameter to specify timeout for document translation calls ## [1.21.0] - 2025-01-15 ### Added - Added support for the Write API in the client library, the implementation can be found in the `DeepLClient` class. Please refer to the README for usage instructions. ### Changed - The main functionality of the library is now also exposed via the `DeepLClient` class. Please change your code to use this over the `Translator` class whenever convenient. ## [1.20.0] - 2024-11-15 ### Added - Added `model_type` option to `translate_text()` to use models with higher translation quality (available for some language pairs), or better latency. Options are `'quality_optimized'`, `'latency_optimized'`, and `'prefer_quality_optimized'` - Added the `model_type_used` field to `translate_text()` response, that indicates the translation model used when the `model_type` option is specified. ## [1.19.1] - 2024-09-17 ### Fixed - Fixed typing issues, black-formatting and a test case failure. ## [1.19.0] - 2024-09-17 ### Added - Added `billed_characters` to response from `translate_text()`. ## [1.18.0] - 2024-04-26 ### Added - New language available: Arabic (`'ar'`). Add language code constants and tests. Arabic is currently supported only for text translation; document translation support for Arabic is coming soon. Note: older library versions also support the new language, this update only adds new code constants. - Added a section in the readme on how to configure retry options in `http_client.py` ## [1.17.0] - 2024-02-06 ### Added - Added `output_format` parameter for document upload function, that indicates the file extension of the desired file format for the translated document. - Added basic usage example of the library ### Fixed - Fixed typechecking errors when using `mypy`'s `strict` mode * Thanks to [derlikh-smart](https://github.com/derlikh-smart) and [vad](https://github.com/vad) for the report in [#82](https://github.com/DeepLcom/deepl-python/issues/82) ## [1.16.1] - 2023-11-07 ### Fixed - Fixed typo in error message when no auth key is provided. - Removed dependency on `packaging` by replacing version comparison. ## [1.16.0] - 2023-11-03 ### Added - Add optional `context` parameter for text translation, that specifies additional context to influence translations, that is not translated itself. ### Changed - Added notice in Readme that starting in 2024 the library will drop support for Python versions that are officially end-of-life. - DeepL API calls now send requests with JSON-encoded bodies where possible. ### Fixed - Catch failures while constructing the user agent string. - Added `py.typed` to the package so that our package is typed for our users. - Fixed most type errors in the package. ## [1.15.0] - 2023-06-09 ### Fixed - Removed `CHANGELOG.md` and `SECURITY.md` from the python package. The `LICENSE` file is no longer installed, but still included in package tarball and wheel files. * Thanks to [TurtleWilly](https://github.com/TurtleWilly) for the report in [#66](https://github.com/DeepLcom/deepl-python/issues/66). - Fix a dependency error in our CI by updating to a higher poetry version in most cases. - Fix getUsage request to be a HTTP GET request, not POST. - Change document translation to poll the server every 5 seconds. This should greatly reduce observed document translation processing time. ## [1.14.0] - 2023-02-21 ### Added - Add [example](examples/json) to translate JSON inputs. - Added platform and python version information to the user-agent string that is sent with API calls, along with an opt-out. - Added method for applications that use this library to identify themselves in API requests they make. - Added `verify_ssl` option to `Translator` to control underlying `requests` session. * Thanks to [andrefloriani](https://github.com/andrefloriani) for the suggestion in [#60](https://github.com/DeepLcom/deepl-python/issues/60). ## [1.13.0] - 2023-01-26 ### Added - Add [example script](examples/mustache) to translate Mustache templates. - Add support for storing your API Key in a keyring via the `keyring` module. - Added a CI check for copyright headers. - New languages available: Korean (`'ko'`) and Norwegian (bokmål) (`'nb'`). Add language code constants and tests. Note: older library versions also support the new languages, this update only adds new code constants. ### Fixed - Copyright headers are updated for 2023 ### Security - Update `certifi` to resolve security advisory. * `certifi` is a development-only dependency; library users are unaffected. ## [1.12.0] - 2023-01-09 ### Added - State explicitly that this library supports Python 3.11. - Added the `should_retry` and `http_status_code` properties to all exceptions thrown by the library. ### Fixed - Fix `py` dependency by upgrading `pytest` version to 7.2.0 for Python 3.7+. For Python 3.6 tests `pytest` needs to be added manually. - Remove unused `tox` dependency. - Update `coverage` dependency. - Also send options in API requests even if they are default values. ## [1.11.0] - 2022-09-26 ### Added - Add formality options `PREFER_LESS` and `PREFER_MORE`. ### Changed - Requests resulting in `503 Service Unavailable` errors are now retried. Attempting to download a document before translation is completed will now wait and retry (up to 5 times by default), rather than raising an exception. ## [1.10.0] - 2022-09-09 ### Added - New language available: Ukrainian (`'uk'`). Add language code constant and tests. Note: older library versions also support new languages, this update only adds new code constant. ### Changed - Add note and workaround to README about Poetry error on Ubuntu 22.04. * Pull request [#48](https://github.com/DeepLcom/deepl-python/pull/48) thanks to [FOehlschlaeger](https://github.com/FOehlschlaeger). ## [1.9.0] - 2022-07-07 ### Added - Add `Translator.create_glossary_from_csv()` allowing glossaries downloaded from website to be easily uploaded to API. ## [1.8.0] - 2022-06-10 ### Added - Optional `filename` parameter added to `Translator.translate_document()`, only required if uploading string or bytes containing file content. * Pull request [#30](https://github.com/DeepLcom/deepl-python/pull/30) thanks to [seratch](https://github.com/seratch). ### Changed - Update contributing guidelines, we can now accept Pull Requests. ### Fixed - Fix GitLab CI config. ## [1.7.0] - 2022-05-18 ### Added - New languages available: Indonesian (`'id'`) and Turkish (`'tr'`). Add language code constants and tests. Note: older library versions also support the new languages, this update only adds new code constants. - Add `limit_reached` and `any_limit_reached` properties to `Usage` object returned by `get_usage()`. - Add `Translator.translate_document_wait_until_done()` to poll translation status until translation is complete or fails. - Add `auth_key_is_free_account()` utility function. ### Changed - Improve readme usage examples. ### Deprecated - Deprecate `limit_exceeded` and `any_limit_exceeded` properties of `Usage` object returned by `get_usage()`, use `limit_reached` and `any_limit_reached` instead. ## [1.6.0] - 2022-04-12 ### Added - Add `error_message` property to `DocumentStatus`, describing the error in case of document translation failure. ### Changed - Improve error message if `translate_text_with_glossary` is called without an instance of `GlossaryInfo`. - `translate_document` and `translate_document_from_filepath` return final `DocumentStatus`, allowing the number of billed characters to be queried. ## [1.5.1] - 2022-04-11 ### Fixed - Fix bug in CLI languages command causing some target languages to be omitted. - Fix some tests that intermittently failed. ## [1.5.0] - 2022-02-28 ### Added - Add support for HTML tag handling in `translate_text()`. ### Deprecated - `DocumentTranslationException.document_request` is deprecated, use `document_handle` instead. ## [1.4.1] - 2022-02-04 ### Fixed - Fix bug in `DocumentTranslationException` stringize function. ## [1.4.0] - 2022-01-19 ### Added - Add contribution guidelines -- currently we are unable to accept Pull Requests. - Add `--glossary-id` argument for CLI document command. ### Changed - Improve README. - Raise `DocumentNotReadyException` when attempting to download a document before it has been translated. Previously the base class `DeepLException` was thrown. ### Fixed - Add optional filename argument to `translate_document_upload()` to fix uploading file content as string or bytes. ## [1.3.1] - 2021-11-15 ### Changed - Limit document status update wait time to 60 seconds, and log wait times. ## [1.3.0] - 2021-11-15 Note: the PyPI package for 1.3.0 included changes from 1.3.1, so it has been yanked. ### Added - Add glossary support for document translation. - Add proxy support. ### Fixed - Fix issues with parallelized tests by changing how test glossaries are created and deleted. ## [1.2.1] - 2021-10-19 ### Added - Add support for Python 3.10. ### Fixed - Fix bug that caused User-Agent header to be omitted from HTTP requests. - Fix glossary name prefix used in unit-tests to match git repository name. - Add workaround for possible issue in datetime.strptime in Python 3.6. ## [1.2.0] - 2021-10-07 ### Added - Add `Translator.get_glossary_languages()` to query language pairs supported for glossaries. - Add constants for all supported languages codes, for example: `Language.GERMAN`. ### Changed - Internal language caching and client-side checking of language codes are removed. ### Deprecated - Some optional arguments related to language caching are now deprecated, and will be removed in a future version: * `Translator()`: the `skip_language_check` argument * `Translator.get_source_languages()` and `Translator.get_target_languages()`: the `skip_cache` argument ### Fixed - Fix HTTP request retries for document uploads. ## [1.1.3] - 2021-09-27 ### Changed - Loosen requirement for requests to 2.0 or higher. ## [1.1.2] - 2021-09-21 ### Changed - Improve request exception messages and include exception stacktraces. - Update unit tests for server error message changes. ## [1.1.1] - 2021-09-13 ### Fixed - Fix typing.List issue on Python 3.6. - Add workaround for datetime.strptime bug in Python 3.6. ## [1.1.0] - 2021-09-13 ### Added - Add security policy. - Add support for glossary API functions. ### Fixed - README and comments improvements, type hints and other minor fixes. ## [1.0.1] - 2021-08-13 ### Added - Add explicit copyright notice to all source files. ### Fixed - Force response encoding to UTF-8 to avoid issues with older versions of requests package. ## [1.0.0] - 2021-08-12 ### Changed - All API calls use Authorization header instead of auth_key parameter. ## [0.4.1] - 2021-08-10 ### Changed - Minor updates to pyproject.toml and README.md. ## [0.4.0] - 2021-08-05 Version increased to avoid conflicts with old packages on PyPI. ## [0.3.0] - 2021-08-05 ### Added - Package uploaded to PyPI. Thanks to [Adrian Freund](mailto:mail@freundtech.com) for transferring the deepl package name. - Clarify minimum version of requests module to 2.18. ## [0.2.0] - 2021-07-28 ### Changed - Improve exception hierarchy. - Translator() server_url argument works with and without trailing slash. - Translator.translate_text() accepts a single text argument, which may be a list or other iterable. ### Fixed - Fix examples in readme to match function interface changes. ## [0.1.0] - 2021-07-26 Initial version. [Unreleased]: https://github.com/DeepLcom/deepl-python/compare/v1.32.0...HEAD [1.32.0]: https://github.com/DeepLcom/deepl-python/compare/v1.31.0...v1.32.0 [1.31.0]: https://github.com/DeepLcom/deepl-python/compare/v1.30.0...v1.31.0 [1.30.0]: https://github.com/DeepLcom/deepl-python/compare/v1.29.0...v1.30.0 [1.29.0]: https://github.com/DeepLcom/deepl-python/compare/v1.28.0...v1.29.0 [1.28.0]: https://github.com/DeepLcom/deepl-python/compare/v1.27.0...v1.28.0 [1.27.0]: https://github.com/DeepLcom/deepl-python/compare/v1.26.0...v1.27.0 [1.26.0]: https://github.com/DeepLcom/deepl-python/compare/v1.25.0...v1.26.0 [1.25.0]: https://github.com/DeepLcom/deepl-python/compare/v1.24.0...v1.25.0 [1.24.0]: https://github.com/DeepLcom/deepl-python/compare/v1.23.0...v1.24.0 [1.23.0]: https://github.com/DeepLcom/deepl-python/compare/v1.22.0...v1.23.0 [1.22.0]: https://github.com/DeepLcom/deepl-python/compare/v1.21.1...v1.22.0 [1.21.1]: https://github.com/DeepLcom/deepl-python/compare/v1.21.0...v1.21.1 [1.21.0]: https://github.com/DeepLcom/deepl-python/compare/v1.20.0...v1.21.0 [1.20.0]: https://github.com/DeepLcom/deepl-python/compare/v1.19.1...v1.20.0 [1.19.1]: https://github.com/DeepLcom/deepl-python/compare/v1.19.0...v1.19.1 [1.19.0]: https://github.com/DeepLcom/deepl-python/compare/v1.18.0...v1.19.0 [1.18.0]: https://github.com/DeepLcom/deepl-python/compare/v1.17.0...v1.18.0 [1.17.0]: https://github.com/DeepLcom/deepl-python/compare/v1.16.1...v1.17.0 [1.16.1]: https://github.com/DeepLcom/deepl-python/compare/v1.16.0...v1.16.1 [1.16.0]: https://github.com/DeepLcom/deepl-python/compare/v1.15.0...v1.16.0 [1.15.0]: https://github.com/DeepLcom/deepl-python/compare/v1.14.0...v1.15.0 [1.14.0]: https://github.com/DeepLcom/deepl-python/compare/v1.13.0...v1.14.0 [1.13.0]: https://github.com/DeepLcom/deepl-python/compare/v1.12.0...v1.13.0 [1.12.0]: https://github.com/DeepLcom/deepl-python/compare/v1.11.0...v1.12.0 [1.11.0]: https://github.com/DeepLcom/deepl-python/compare/v1.10.0...v1.11.0 [1.10.0]: https://github.com/DeepLcom/deepl-python/compare/v1.9.0...v1.10.0 [1.9.0]: https://github.com/DeepLcom/deepl-python/compare/v1.8.0...v1.9.0 [1.8.0]: https://github.com/DeepLcom/deepl-python/compare/v1.7.0...v1.8.0 [1.7.0]: https://github.com/DeepLcom/deepl-python/compare/v1.6.0...v1.7.0 [1.6.0]: https://github.com/DeepLcom/deepl-python/compare/v1.5.1...v1.6.0 [1.5.1]: https://github.com/DeepLcom/deepl-python/compare/v1.5.0...v1.5.1 [1.5.0]: https://github.com/DeepLcom/deepl-python/compare/v1.4.1...v1.5.0 [1.4.1]: https://github.com/DeepLcom/deepl-python/compare/v1.4.0...v1.4.1 [1.4.0]: https://github.com/DeepLcom/deepl-python/compare/v1.3.1...v1.4.0 [1.3.1]: https://github.com/DeepLcom/deepl-python/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/DeepLcom/deepl-python/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/DeepLcom/deepl-python/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/DeepLcom/deepl-python/compare/v1.1.3...v1.2.0 [1.1.3]: https://github.com/DeepLcom/deepl-python/compare/v1.1.2...v1.1.3 [1.1.2]: https://github.com/DeepLcom/deepl-python/compare/v1.1.1...v1.1.2 [1.1.1]: https://github.com/DeepLcom/deepl-python/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/DeepLcom/deepl-python/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/DeepLcom/deepl-python/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/DeepLcom/deepl-python/compare/v0.4.1...v1.0.0 [0.4.1]: https://github.com/DeepLcom/deepl-python/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/DeepLcom/deepl-python/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/DeepLcom/deepl-python/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/DeepLcom/deepl-python/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/DeepLcom/deepl-python/releases/tag/v0.1.0 DeepL-deepl-python-df50df2/CODE_OF_CONDUCT.md000066400000000000000000000126201523706276600204230ustar00rootroot00000000000000 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [open-source@deepl.com](mailto:open-source@deepl.com). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations DeepL-deepl-python-df50df2/CONTRIBUTING.md000066400000000000000000000025041523706276600200550ustar00rootroot00000000000000# Contributing guidelines * [Code of Conduct](#code-of-conduct) * [Issues](#issues) * [Pull Requests](#pull-requests) ## Code of Conduct This project has a [Code of Conduct](CODE_OF_CONDUCT.md) to which all contributors must adhere when participating in the project. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting [open-source@deepl.com](mailto:open-source@deepl.com) and/or a project maintainer. ## Issues If you experience problems using the library, or would like to request a new feature, please open an [issue][issues]. Please provide as much context as possible when you open an issue. The information you provide must be comprehensive enough for us to reproduce the issue. ## Pull Requests You are welcome to contribute code and/or documentation in order to fix a bug or to implement a new feature. Before beginning work, you should create an issue describing the changes you plan to contribute, to avoid wasting or duplicating effort. We will then let you know whether we would accept the changes. Contributions must be licensed under the same license as the project: [MIT License](LICENSE). Currently automated testing is implemented internally at DeepL, however we plan to implement publicly visible testing soon. [issues]: https://www.github.com/DeepLcom/deepl-python/issues DeepL-deepl-python-df50df2/LICENSE000066400000000000000000000020741523706276600166330ustar00rootroot00000000000000MIT License Copyright 2022 DeepL SE (https://www.deepl.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.DeepL-deepl-python-df50df2/README.md000066400000000000000000001343761523706276600171200ustar00rootroot00000000000000# DeepL Python Library [![PyPI version](https://img.shields.io/pypi/v/deepl.svg)](https://pypi.org/project/deepl/) [![Supported Python versions](https://img.shields.io/pypi/pyversions/deepl.svg)](https://pypi.org/project/deepl/) [![License: MIT](https://img.shields.io/badge/license-MIT-blueviolet.svg)](https://github.com/DeepLcom/deepl-python/blob/main/LICENSE) [![Downloads](https://img.shields.io/pypi/dm/deepl)](https://pypistats.org/packages/deepl) The [DeepL API][api-docs] is a language AI API that allows other computer programs to send texts and documents to DeepL's servers and receive high-quality translations and improvements to the text. This opens a whole universe of opportunities for developers: any translation product you can imagine can now be built on top of DeepL's best-in-class translation technology. The DeepL Python library offers a convenient way for applications written in Python to interact with the DeepL API. We intend to support all API functions with the library, though support for new features may be added to the library after they’re added to the API. ## Getting an authentication key To use the DeepL Python Library, you'll need an API authentication key. To get a key, [please create an account here][create-account]. With a DeepL API Free account you can consume up to 500,000 characters/month for free. ## Installation The library can be installed from [PyPI][pypi-project] using pip: ```shell pip install --upgrade deepl ``` If you need to modify this source code, install the dependencies using poetry: ```shell poetry install ``` On Ubuntu 22.04 an error might occur: `ModuleNotFoundError: No module named 'cachecontrol'`. Use the workaround `sudo apt install python3-cachecontrol` as explained in this [bug report][bug-report-ubuntu-2204]. ### Requirements The library is tested with Python versions 3.9 to 3.13. The `requests` module is used to perform HTTP requests; the minimum is version 2.32.4. We periodically drop support for older Python versions that have reached official end-of-life. You can find the Python versions and support timelines [here][python-version-list]. ## Usage Import the package and construct a `DeepLClient`. The first argument is a string containing your API authentication key as found in your [DeepL Pro Account][pro-account]. Be careful not to expose your key, for example when sharing source code. ```python import deepl auth_key = "f63c02c5-f056-..." # Replace with your key deepl_client = deepl.DeepLClient(auth_key) result = deepl_client.translate_text("Hello, world!", target_lang="FR") print(result.text) # "Bonjour, le monde !" ``` This example is for demonstration purposes only. In production code, the authentication key should not be hard-coded, but instead fetched from a configuration file or environment variable. `DeepLClient` accepts additional options, see [Configuration](#configuration) for more information. ### Translating text To translate text, call `translate_text()`. The first argument is a string containing the text you want to translate, or a list of strings if you want to translate multiple texts. `source_lang` and `target_lang` specify the source and target language codes respectively. The `source_lang` is optional, if it is unspecified the source language will be auto-detected. Language codes are **case-insensitive** strings according to ISO 639-1, for example `'DE'`, `'FR'`, `'JA''`. Some target languages also include the regional variant according to ISO 3166-1, for example `'EN-US'`, or `'PT-BR'`. The full list of supported languages is in the [API documentation][api-docs-lang-list]. There are additional optional arguments to control translation, see [Text translation options](#text-translation-options) below. `translate_text()` returns a `TextResult`, or a list of `TextResult`s corresponding to your input text(s). `TextResult` has the following properties: - `text` is the translated text, - `detected_source_lang` is the detected source language code, - `billed_characters` is the number of characters billed for the translation. - `model_type_used` indicates the translation model used, but is `None` unless the `model_type` option is specified. ```python # Translate text into a target language, in this case, French: result = deepl_client.translate_text("Hello, world!", target_lang="FR") print(result.text) # "Bonjour, le monde !" # Translate multiple texts into British English result = deepl_client.translate_text( ["お元気ですか?", "¿Cómo estás?"], target_lang="EN-GB", ) print(result[0].text) # "How are you?" print(result[0].detected_source_lang) # "JA" the language code for Japanese print(result[0].billed_characters) # 7 - the number of characters in the source text "お元気ですか?" print(result[1].text) # "How are you?" print(result[1].detected_source_lang) # "ES" the language code for Spanish print(result[1].billed_characters) # 12 - the number of characters in the source text "¿Cómo estás?" # Translate into German with less and more Formality: print( deepl_client.translate_text( "How are you?", target_lang="DE", formality="less" ) ) # 'Wie geht es dir?' print( deepl_client.translate_text( "How are you?", target_lang="DE", formality="more" ) ) # 'Wie geht es Ihnen?' ``` #### Text translation options In addition to the input text(s) argument, the available `translate_text()` arguments are: - `source_lang`: Specifies the source language code, but may be omitted to auto-detect the source language. - `target_lang`: Required. Specifies the target language code. - `split_sentences`: specify how input text should be split into sentences, default: `'on'`. - `'on''` (`SplitSentences.ON`): input text will be split into sentences using both newlines and punctuation. - `'off'` (`SplitSentences.OFF`): input text will not be split into sentences. Use this for applications where each input text contains only one sentence. - `'nonewlines'` (`SplitSentences.NO_NEWLINES`): input text will be split into sentences using punctuation but not newlines. - `preserve_formatting`: controls automatic-formatting-correction. Set to `True` to prevent automatic-correction of formatting, default: `False`. - `formality`: controls whether translations should lean toward informal or formal language. This option is only available for some target languages, see [Listing available languages](#listing-available-languages). - `'less'` (`Formality.LESS`): use informal language. - `'more'` (`Formality.MORE`): use formal, more polite language. - `glossary`: specifies a glossary to use with translation, either as a string containing the glossary ID, or a `GlossaryInfo` as returned by `get_glossary()`. - `glossary_ids`: specifies a list of up to 5 glossaries to use with translation, applied in order. Each entry may be a string containing the glossary ID or a `GlossaryInfo` object. Requires `source_lang` to be set and cannot be combined with the `glossary` parameter. On the command line, use the repeatable `--glossary-ids` argument (available on both the `text` and `document` commands), for example: `--from=EN --to=DE --glossary-ids=ID1 --glossary-ids=ID2`. - `context`: specifies additional context to influence translations, that is not translated itself. Characters in the `context` parameter are not counted toward billing. See the [API documentation][api-docs-context-param] for more information and example usage. - `model_type`: specifies the type of translation model to use, options are: - `'quality_optimized'` (`ModelType.QUALITY_OPTIMIZED`): use a translation model that maximizes translation quality, at the cost of response time. This option may be unavailable for some language pairs. - `'prefer_quality_optimized'` (`ModelType.PREFER_QUALITY_OPTIMIZED`): use the highest-quality translation model for the given language pair. - `'latency_optimized'` (`ModelType.LATENCY_OPTIMIZED`): use a translation model that minimizes response time, at the cost of translation quality. - `tag_handling`: type of tags to parse before translation, options are `'html'` and `'xml'`. - `tag_handling_version`: specifies which version of the tag handling algorithm to use, options are `'v1'` and `'v2'`. - `style_rule`: specifies a style rule to use with translation, either as a string containing the ID of the style rule, or a `StyleRuleInfo` object. - `translation_memory`: specifies a translation memory to use with translation, either as a string containing the ID of the translation memory, or a `TranslationMemoryInfo` object. - `translation_memory_threshold`: the minimum matching percentage for fuzzy matches from the translation memory (0-100). We recommend a minimum threshold of 75%. - `custom_instructions`: an array of instructions to customize the text translation behavior. Up to 10 custom instructions can be specified, each with a maximum of 300 characters. Important: The target language must be `de`, `en`, `es`, `fr`, `it`, `ja`, `ko`, `zh` or any variants of these languages. Note: Any request with the `custom_instructions` parameter enabled will use the `quality_optimized` model type as the default. Requests combining `custom_instructions` and `model_type: latency_optimized` will be rejected. - `extra_body_parameters`: Dictionary of extra parameters to pass in the body of the HTTP request. Mostly used by DeepL employees to test functionality, or for beta programs. The following options are only used if `tag_handling` is `'xml'`: - `outline_detection`: specify `False` to disable automatic tag detection, default is `True`. - `splitting_tags`: list of XML tags that should be used to split text into sentences. Tags may be specified as an array of strings (`['tag1', 'tag2']`), or a comma-separated list of strings (`'tag1,tag2'`). The default is an empty list. - `non_splitting_tags`: list of XML tags that should not be used to split text into sentences. Format and default are the same as for `splitting_tags`. - `ignore_tags`: list of XML tags that containing content that should not be translated. Format and default are the same as for `splitting_tags`. For a detailed explanation of the XML handling options, see the [API documentation][api-docs-xml-handling]. ### Improving text (Write API) You can use the Write API to improve or rephrase text. This is implemented in the `rephrase_text()` method. The first argument is a string containing the text you want to translate, or a list of strings if you want to translate multiple texts. `target_lang` optionally specifies the target language, e.g. when you want to change the variant of a text (for example, you can send an english text to the write API and use `target_lang` to turn it into British or American English). Please note that the Write API itself does NOT translate. If you wish to translate and improve a text, you will need to make multiple calls in a chain. Language codes are the same as for translating text. Example call: ```python result = deepl_client.rephrase_text("A rainbouw has seven colours.", target_lang="EN-US") print(result.text) ``` Additionally, you can optionally specify a style OR a tone (not both at once) that the improvement should be in. The following styles are supported (`default` will be used if nothing is selected): - `academic` - `business` - `casual` - `default` - `simple` The following tones are supported (`default` will be used if nothing is selected): - `confident` - `default` - `diplomatic` - `enthusiastic` - `friendly` You can also prefix any non-default style or tone with `prefer_` (`prefer_academic`, etc.), in which case the style/tone will only be applied if the language supports it. If you do not use `prefer_`, requests with `target_lang`s or detected languages that do not support styles and tones will fail. The current list of supported languages can be found in our [API documentation][api-docs]. We plan to also expose this information via an API endpoint in the future. You can use the predefined constants in the library to use a style: ```python result = deepl_client.rephrase_text( "A rainbouw has seven colours.", target_lang="EN-US", style=WritingStyle.BUSINESS.value ) print(result.text) ``` ### Translating documents To translate documents, you may call either `translate_document()` using file IO objects, or `translate_document_from_filepath()` using file paths. For both functions, the first and second arguments correspond to the input and output files respectively. Just as for the `translate_text()` function, the `source_lang` and `target_lang` arguments specify the source and target language codes. There are additional optional arguments to control translation, see [Document translation options](#document-translation-options) below. ```python # Translate a formal document from English to German input_path = "/path/to/Instruction Manual.docx" output_path = "/path/to/Bedienungsanleitung.docx" try: # Using translate_document_from_filepath() with file paths deepl_client.translate_document_from_filepath( input_path, output_path, target_lang="DE", formality="more" ) # Alternatively you can use translate_document() with file IO objects with open(input_path, "rb") as in_file, open(output_path, "wb") as out_file: deepl_client.translate_document( in_file, out_file, target_lang="DE", formality="more" ) except deepl.DocumentTranslationException as error: # If an error occurs during document translation after the document was # already uploaded, a DocumentTranslationException is raised. The # document_handle property contains the document handle that may be used to # later retrieve the document from the server, or contact DeepL support. doc_id = error.document_handle.id doc_key = error.document_handle.key print(f"Error after uploading ${error}, id: ${doc_id} key: ${doc_key}") except deepl.DeepLException as error: # Errors during upload raise a DeepLException print(error) ``` `translate_document()` and `translate_document_from_filepath()` are convenience functions that wrap multiple API calls: uploading, polling status until the translation is complete, and downloading. If your application needs to execute these steps individually, you can instead use the following functions directly: - `translate_document_upload()`, - `translate_document_get_status()` (or `translate_document_wait_until_done()`), and - `translate_document_download()` #### Document translation options In addition to the input file, output file, `source_lang` and `target_lang` arguments, the available `translate_document()` and `translate_document_from_filepath()` arguments are: - `formality`: same as in [Text translation options](#text-translation-options). - `glossary`: same as in [Text translation options](#text-translation-options). - `glossary_ids`: same as in [Text translation options](#text-translation-options). - `style_rule`: same as in [Text translation options](#text-translation-options). - `translation_memory`: same as in [Text translation options](#text-translation-options). - `translation_memory_threshold`: same as in [Text translation options](#text-translation-options). - `output_format`: (`translate_document()` only) file extension of desired format of translated file, for example: `'pdf'`. If unspecified, by default the translated file will be in the same format as the input file. ### Glossaries Glossaries allow you to customize your translations using user-defined terms. Multiple glossaries can be stored with your account, each with a user-specified name and a uniquely-assigned ID. #### v2 versus v3 glossary APIs The newest version of the glossary APIs are the `/v3` endpoints, allowing both editing functionality plus support for multilingual glossaries. New methods and objects have been created to support interacting with these new glossaries. Due to this new functionality, users are recommended to utilize these multilingual glossary methods. However, to continue using the `v2` glossary API endpoints, please continue to use the existing endpoints in the `translator.py` (e.g. `create_glossary()`, `get_glossary()`, etc). To migrate to use the new multilingual glossary methods from the current monolingual glossary methods, please refer to [this migration guide](upgrading_to_multilingual_glossaries.md). The following sections describe how to interact with multilingual glossaries using the new functionality: #### Creating a glossary You can create a multi-lingual glossary with your desired terms and name using `create_multilingual_glossary()`. Glossaries created via the /v3 endpoints can now support multiple source-target language pairs. Note: Glossaries are only supported for some language pairs, see [Listing available glossary languages](#listing-available-glossary-languages) for more information. The entries should be specified as a dictionary. If successful, the glossary is created and stored with your DeepL account, and a `MultilingualGlossaryInfo` object is returned including the ID, name, and glossary dictionaries. The glossary dictionaries would be an array of `MultilingualGlossaryDictionaryInfo` objects, each containing its own languages and entry count. ```python # Create a glossary with an English to German dictionary containing two terms: entries = {"artist": "Maler", "prize": "Gewinn"} dictionaries = [MultilingualGlossaryDictionaryEntries("EN", "DE", entries)] my_glossary = deepl_client.create_multilingual_glossary( "My glossary", dictionaries ) my_glossary_dict = my_glossary.dictionaries[0] print( f"Created '{my_glossary.name}' ({my_glossary.glossary_id}) " f"with {len(my_glossary.dictionaries)} dictionary where " f"its language pair is {my_glossary_dict.source_lang}->" f"{my_glossary_dict.target_lang} containing " f"{my_glossary.entry_count} entries" ) # Example: Created 'My glossary' (559192ed-8e23-...) with 1 dictionary where # its language pair is EN->DE containing 2 entries ``` You can also upload a glossary downloaded from the DeepL website using `create_multilingual_glossary_from_csv()`. Instead of supplying the entries as a dictionary within a MultilingualGlossaryDictionaryEntries object, you can specify the CSV data as `csv_data` either as a file-like object or string or bytes containing file content: ```python # Open the CSV file assuming UTF-8 encoding. If your file contains a BOM, # consider using encoding='utf-8-sig' instead. with open('/path/to/glossary_file.csv', 'r', encoding='utf-8') as csv_file: csv_data = csv_file.read() # Read the file contents as a string my_csv_glossary = deepl_client.create_multilingual_glossary_from_csv( "CSV glossary", source_lang="EN", target_lang="DE", csv_data=csv_data, ) ``` The [API documentation][api-docs-csv-format] explains the expected CSV format in detail. #### Getting, listing, and deleting stored glossaries Functions to get, list, and delete stored glossaries are also provided: - `get_multilingual_glossary()` takes a glossary ID and returns a `MultilingualGlossaryInfo` object for a stored glossary, or raises an exception if no such glossary is found. - `list_multilingual_glossaries()` returns a list of `MultilingualGlossaryInfo` objects corresponding to all of your stored glossaries. - `delete_multilingual_glossary()` takes a glossary ID or `MultilingualGlossaryInfo` object and deletes the stored glossary from the server, or raises an exception if no such glossary is found. - `delete_multilingual_glossary_dictionary()` takes a glossary ID or `GlossaryInfo` object to identify the glossary. Additionally takes in a source and target language or a `MultilingualGlossaryDictionaryInfo` object and deletes the stored dictionary from the server, or raises an exception if no such glossary dictionary is found. ```python # Retrieve a stored glossary using the ID glossary_id = "559192ed-8e23-..." my_glossary = deepl_client.get_multilingual_glossary(glossary_id) # Delete a glossary dictionary from a stored glossary deepl_client.delete_multilingual_glossary_dictionary(my_glossary, my_glossary.dictionaries[0]) # Find and delete glossaries named 'Old glossary' glossaries = deepl_client.list_multilingual_glossaries() for glossary in glossaries: if glossary.name == "Old glossary": deepl_client.delete_multilingual_glossary(glossary) ``` #### Listing entries in a stored glossary The `MultilingualGlossaryInfo` object does not contain the glossary entries, but instead only the number of entries in the `entry_count` property. To list the entries contained within a stored glossary, use `get_multilingual_glossary_entries()` providing either the `MultilingualGlossaryInfo` object or glossary ID and either a `MultilingualGlossaryDictionaryInfo` or source and target language pair: ```python entries = deepl_client.get_multilingual_glossary_entries(my_glossary, "EN", "DE") print(entries.dictionaries[0]) # "{'artist': 'Maler', 'prize': 'Gewinn'}" ``` #### Editing a glossary Functions to edit stored glossaries are also provided: - `update_multilingual_glossary_dictionary()` takes a glossary ID or `MultilingualGlossaryInfo` object, plus a source language, target language, and a dictionary of entries. It will then either update the list of entries for that dictionary (either inserting new entires or replacing the target phrase for any existing entries) or will insert a new glossary dictionary if that language pair is not currently in the stored glossary. - `replace_multilingual_glossary_dictionary()` takes a glossary ID or `MultilingualGlossaryInfo` object, plus a source language, target language, and a dictionary of entries. It will then either set the entries to the parameter value, completely replacing any pre-existing entries for that language pair. - `update_multilingual_glossary_name()` takes a glossary ID or `MultilingualGlossaryInfo` object, plus the new name of the glossary. ```python # Update glossary dictionary entries = {"artist": "Maler", "hello": "guten tag"} dictionaries = [MultilingualGlossaryDictionaryEntries("EN", "DE", entries)] my_glossary = deepl_client.create_multilingual_glossary( "My glossary", dictionaries ) new_entries = {"hello": "hallo", "prize": "Gewinn"} glossary_dict = MultilingualGlossaryDictionaryEntries("EN", "DE", new_entries) updated_glossary = deepl_client.update_multilingual_glossary_dictionary( my_glossary, glossary_dict ) entries_response = deepl_client.get_multilingual_glossary_entries(my_glossary, "EN", "DE") print(entries_response.dictionaries[0]) # "{'artist': 'Maler', 'hello': 'hallo', 'prize': 'Gewinn'}" # Update a glossary dictionary from CSV # Open the CSV file assuming UTF-8 encoding. If your file contains a BOM, # consider using encoding='utf-8-sig' instead. with open('/path/to/glossary_file.csv', 'r', encoding='utf-8') as csv_file: csv_data = csv_file.read() # Read the file contents as a string my_csv_glossary = deepl_client.update_multilingual_glossary_dictionary_from_csv( glossary="4c81ffb4-2e...", source_lang="EN", target_lang="DE", csv_data=csv_data, ) # Replace glossary dictionary replacement_entries = {"goodbye": "Auf Wiedersehen"} glossary_dict = MultilingualGlossaryDictionaryEntries("EN", "DE", replacement_entries) updated_glossary = deepl_client.replace_multilingual_glossary_dictionary( my_glossary, glossary_dict) entries_response = deepl_client.get_multilingual_glossary_entries(my_glossary, "EN", "DE") print(entries_response.dictionaries[0]) # "{'goodbye': 'Auf Wiedersehen'}" # Replace a glossary dictionary from CSV # Open the CSV file assuming UTF-8 encoding. If your file contains a BOM, # consider using encoding='utf-8-sig' instead. with open('/path/to/glossary_file.csv', 'r', encoding='utf-8') as csv_file: csv_data = csv_file.read() # Read the file contents as a string my_csv_glossary = deepl_client.replace_multilingual_glossary_dictionary_from_csv( glossary="4c81ffb4-2e...", source_lang="EN", target_lang="DE", csv_data=csv_data, ) # Update the glossary name updated_glossary = deepl_client.update_multilingual_glossary_name( my_glossary, "My new glossary name" ) print(updated_glossary.name) # 'My new glossary name' ``` #### Using a stored glossary You can use a stored glossary for text translation by setting the `glossary` argument to either the glossary ID or `GlossaryInfo`/`MultilingualGlossaryInfo` object. You must also specify the `source_lang` argument (it is required when using a glossary): ```python text = "The artist was awarded a prize." with_glossary = deepl_client.translate_text( text, source_lang="EN", target_lang="DE", glossary=my_glossary, ) print(with_glossary) # "Der Maler wurde mit einem Gewinn ausgezeichnet." # For comparison, the result without a glossary: without_glossary = deepl_client.translate_text(text, target_lang="DE") print(without_glossary) # "Der Künstler wurde mit einem Preis ausgezeichnet." ``` Using a stored glossary for document translation is the same: set the `glossary` argument and specify the `source_lang` argument: ```python deepl_client.translate_document( in_file, out_file, source_lang="EN", target_lang="DE", glossary=my_glossary, ) ``` The `translate_document()`, `translate_document_from_filepath()` and `translate_document_upload()` functions all support the `glossary` argument. ### Checking account usage To check account usage, use the `get_usage()` function. The returned `Usage` object contains three usage subtypes: `character`, `document` and `team_document`. Depending on your account type, some usage subtypes may be invalid; this can be checked using the `valid` property. For API accounts: - `usage.character.valid` is `True`, - `usage.document.valid` and `usage.team_document.valid` are `False`. Each usage subtype (if valid) has `count` and `limit` properties giving the amount used and maximum amount respectively, and the `limit_reached` property that checks if the usage has reached the limit. The top level `Usage` object has the `any_limit_reached` property to check all usage subtypes. ```python usage = deepl_client.get_usage() if usage.any_limit_reached: print('Translation limit reached.') if usage.character.valid: print( f"Character usage: {usage.character.count} of {usage.character.limit}") if usage.document.valid: print(f"Document usage: {usage.document.count} of {usage.document.limit}") ``` ### Listing available languages You can request the list of languages supported by DeepL for text and documents using the `get_source_languages()` and `get_target_languages()` functions. They both return a list of `Language` objects. The `name` property gives the name of the language in English, and the `code` property gives the language code. The `supports_formality` property only appears for target languages, and indicates whether the target language supports the optional `formality` parameter. ```python print("Source languages:") for language in deepl_client.get_source_languages(): print(f"{language.name} ({language.code})") # Example: "German (DE)" print("Target languages:") for language in deepl_client.get_target_languages(): if language.supports_formality: print(f"{language.name} ({language.code}) supports formality") # Example: "Italian (IT) supports formality" else: print(f"{language.name} ({language.code})") # Example: "Lithuanian (LT)" ``` #### Listing available glossary languages Glossaries are supported for a subset of language pairs. To retrieve those languages use the `get_glossary_languages()` function, which returns an array of `GlossaryLanguagePair` objects. Each has `source_lang` and `target_lang` properties indicating that that pair of language codes is supported. ```python glossary_languages = deepl_client.get_glossary_languages() for language_pair in glossary_languages: print(f"{language_pair.source_lang} to {language_pair.target_lang}") # Example: "EN to DE", "DE to EN", etc. ``` You can also find the list of supported glossary language pairs in the [API documentation][api-docs-glossary-lang-list]. Note that glossaries work for all target regional-variants: a glossary for the target language English (`"EN"`) supports translations to both American English (`"EN-US"`) and British English (`"EN-GB"`). ### Style Rules Style rules allow you to customize your translations using a managed, shared list of rules for style, formatting, and more. Multiple style rules can be stored with your account, each with a user-specified name and a uniquely-assigned ID. #### Creating a style rule Use `create_style_rule()` to create a new style rule with a name and language code. You can optionally provide `configured_rules` and `custom_instructions`. ```python style_rule = deepl_client.create_style_rule( name="My Style Rule", language="en", ) print(f"Created: {style_rule.name} ({style_rule.style_id})") ``` #### Retrieving and listing style rules Use `get_style_rule()` to retrieve a single style rule by ID, or `get_all_style_rules()` to list all style rules. `get_all_style_rules()` returns a list of `StyleRuleInfo` objects corresponding to all of your stored style rules. The method accepts optional parameters: `page` (page number for pagination, 0-indexed), `page_size` (number of items per page), and `detailed`. When `True`, the response includes `configured_rules` and `custom_instructions` for each style rule. When `False` (default), these fields are omitted for faster responses. ```python # Get a single style rule by ID style_rule = deepl_client.get_style_rule("YOUR_STYLE_ID") print(f"{style_rule.name} ({style_rule.language})") # List all style rules style_rules = deepl_client.get_all_style_rules() for rule in style_rules: print(f"{rule.name} ({rule.style_id})") # List with detailed configuration style_rules = deepl_client.get_all_style_rules(detailed=True) for rule in style_rules: if rule.configured_rules: print(f" Number formatting: {rule.configured_rules.numbers}") ``` #### Updating a style rule Use `update_style_rule_name()` to rename a style rule, and `update_style_rule_configured_rules()` to update its configured rules. ```python # Update the name updated = deepl_client.update_style_rule_name("YOUR_STYLE_ID", "New Name") # Update configured rules updated = deepl_client.update_style_rule_configured_rules( "YOUR_STYLE_ID", {"style_and_tone": {"formality": "formal"}}, ) ``` #### Managing custom instructions Custom instructions allow you to add free-text prompts to a style rule. Use `create_style_rule_custom_instruction()`, `get_style_rule_custom_instruction()`, `update_style_rule_custom_instruction()`, and `delete_style_rule_custom_instruction()` to manage them. ```python # Create a custom instruction instruction = deepl_client.create_style_rule_custom_instruction( "YOUR_STYLE_ID", label="Formal tone", prompt="Always use formal language", ) print(f"Created instruction: {instruction.id}") # Get a custom instruction instruction = deepl_client.get_style_rule_custom_instruction( "YOUR_STYLE_ID", instruction.id ) # Update a custom instruction updated = deepl_client.update_style_rule_custom_instruction( "YOUR_STYLE_ID", instruction.id, label="Updated label", prompt="Use very formal language", ) # Delete a custom instruction deepl_client.delete_style_rule_custom_instruction( "YOUR_STYLE_ID", instruction.id ) ``` #### Deleting a style rule Use `delete_style_rule()` to delete a style rule by ID. ```python deepl_client.delete_style_rule("YOUR_STYLE_ID") ``` #### Using a style rule in translations Style rules can also be used with the command line interface for text translation: ```bash python3 -m deepl --auth-key=YOUR_AUTH_KEY text --to=DE --style-id=YOUR_STYLE_ID "Text to translate" ``` The `document` command also accepts `--style-id` for document translation: ```bash python3 -m deepl --auth-key=YOUR_AUTH_KEY document --to=DE --style-id=YOUR_STYLE_ID /path/to/document.docx /path/to/output_dir ``` ### Translation Memories Translation memories allow you to store and reuse previously created translations. They can be used in text translation requests to improve consistency by matching against stored segments. Multiple translation memories can be stored with your account, each with a source language and one or more target languages. #### Listing translation memories `list_translation_memories()` returns a list of `TranslationMemoryInfo` objects for your stored translation memories. The number of translation memories returned is controlled by `page_size` (max 25). The method accepts optional parameters: `page` (page number for pagination, 0-indexed) and `page_size` (number of items per page). ```python # List translation memories translation_memories = deepl_client.list_translation_memories() for tm in translation_memories: print(f"{tm.name} ({tm.translation_memory_id})") print(f" Source: {tm.source_language}, Targets: {tm.target_languages}") print(f" Segments: {tm.segment_count}") ``` #### Retrieving a translation memory Use `get_translation_memory()` to retrieve a single translation memory by ID. It accepts either a translation memory ID string or a `TranslationMemoryInfo` object. ```python tm = deepl_client.get_translation_memory("YOUR_TM_ID") print(f"{tm.name}: {tm.segment_count} segments, updated {tm.updated_time}") ``` #### Listing the segments of a translation memory `list_translation_memory_segments()` returns one page of segments as a `TranslationMemorySegments` object. Pagination is cursor-based: omit `page_cursor` on the first call, then pass the previous response's `next_page_cursor` until it is `None`. Optionally filter with `filter_text` (at least 2 characters, matched against both source and target text) and `filter_case_sensitive`. Note that `segment_count` is the translation-memory total and is not reduced by the filter. ```python page_cursor = None while True: page = deepl_client.list_translation_memory_segments( "YOUR_TM_ID", page_size=50, page_cursor=page_cursor ) for segment in page.segments: print(segment.source_text) for target in segment.targets: print(f" {target.target_language}: {target.target_text}") page_cursor = page.next_page_cursor if not page_cursor: break ``` #### Importing a translation memory `import_translation_memory_from_filepath()` imports a TMX file as a new translation memory: it creates the import job, uploads the file, and waits for processing to finish. The returned `TranslationMemoryJob` carries the ID of the new translation memory. ```python job = deepl_client.import_translation_memory_from_filepath( "/path/to/legal.tmx", display_name="Legal TM", timeout_s=300 ) print(f"Created translation memory {job.result.translation_memory_id}") print(f"Skipped segments: {job.result.skipped_segment_count}") ``` Importing takes a while: the API detects the uploaded file asynchronously, so the job keeps reporting `awaiting_input` for roughly half a minute after the upload before completing. Pass `timeout_s` to bound how long to wait. The three steps are also available separately, for example to upload the file yourself or to poll for progress. `create_translation_memory_import()` returns an upload URL that the file must be uploaded to before processing starts, then `get_translation_memory_job()` reports the status. ```python created = deepl_client.create_translation_memory_import( file_name="legal.tmx", content_length=os.path.getsize("/path/to/legal.tmx"), display_name="Legal TM", ) with open("/path/to/legal.tmx", "rb") as input_file: deepl_client.upload_translation_memory_file(created, input_file) # The job reports "awaiting_input" both before the upload and for a while # afterwards, until the API detects it; this polls through that status. job = deepl_client.wait_until_translation_memory_job_done( created.job_id, timeout_s=300 ) ``` #### Exporting a translation memory `export_translation_memory_to_filepath()` exports a translation memory to a TMX file: it creates the export job, waits for it to finish, and writes the result. ```python job = deepl_client.export_translation_memory_to_filepath( "YOUR_TM_ID", "/path/to/exported.tmx" ) ``` As with import, the individual steps are available separately. Note that the API may reuse a previously completed export of an unchanged translation memory, indicated by `reused_existing`. ```python created = deepl_client.create_translation_memory_export("YOUR_TM_ID") job = deepl_client.wait_until_translation_memory_job_done(created.job_id) with open("/path/to/exported.tmx", "wb") as output_file: deepl_client.download_translation_memory_export( job, output_file, chunk_size=8192 ) ``` #### Deleting a translation memory Use `delete_translation_memory()` to delete a translation memory by ID. ```python deepl_client.delete_translation_memory("YOUR_TM_ID") ``` #### Managing translation memories from the command line The `translation-memory` command exposes the same operations: ```bash python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory list python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory get YOUR_TM_ID python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory segments YOUR_TM_ID --all python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory import /path/to/legal.tmx --name "Legal TM" python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory export YOUR_TM_ID /path/to/exported.tmx python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory job YOUR_JOB_ID python3 -m deepl --auth-key=YOUR_AUTH_KEY translation-memory delete YOUR_TM_ID ``` #### Using a translation memory in translations Pass the `translation_memory` parameter to `translate_text()` to use a translation memory. You can pass either a string containing the translation memory ID, or a `TranslationMemoryInfo` object. Use `translation_memory_threshold` to control the minimum matching percentage for fuzzy matches (0-100, recommended minimum of 75%). ```python # Translate with a translation memory ID result = deepl_client.translate_text( "Hello, world!", target_lang="DE", translation_memory="YOUR_TM_ID", translation_memory_threshold=80, ) # Or use a TranslationMemoryInfo object translation_memories = deepl_client.list_translation_memories() result = deepl_client.translate_text( "Hello, world!", target_lang="DE", translation_memory=translation_memories[0], ) ``` Translation memories can also be used with the command line interface: ```bash python3 -m deepl --auth-key=YOUR_AUTH_KEY text --to=DE --translation-memory-id=YOUR_TM_ID --translation-memory-threshold=75 "Text to translate" ``` The `document` command also accepts `--translation-memory-id` and `--translation-memory-threshold` for document translation: ```bash python3 -m deepl --auth-key=YOUR_AUTH_KEY document --to=DE --translation-memory-id=YOUR_TM_ID --translation-memory-threshold=75 /path/to/document.docx /path/to/output_dir ``` ### Writing a Plugin If you use this library in an application, please identify the application with `deepl.DeepLClient.set_app_info`, which needs the name and version of the app: ```python deepl_client = deepl.DeepLClient(...).set_app_info("sample_python_plugin", "1.0.2") ``` This information is passed along when the library makes calls to the DeepL API. Both name and version are required. Please note that setting the `User-Agent` header via `deepl.http_client.user_agent` will override this setting, if you need to use this, please manually identify your Application in the `User-Agent` header. ### Exceptions All module functions may raise `deepl.DeepLException` or one of its subclasses. If invalid arguments are provided, they may raise the standard exceptions `ValueError` and `TypeError`. ### Configuration #### Logging Logging can be enabled to see the HTTP requests sent and responses received by the library. Enable and control logging using Python's `logging` module, for example: ```python import logging logging.basicConfig() logging.getLogger('deepl').setLevel(logging.DEBUG) ``` #### Server URL configuration You can override the URL of the DeepL API by specifying the `server_url` argument when constructing a `deepl.DeepLClient`. This may be useful for testing purposes. You **do not** need to specify the URL to distinguish API Free and API Pro accounts, the library selects the correct URL automatically. ```python server_url = "http://user:pass@localhost:3000" deepl_client = deepl.DeepLClient(..., server_url=server_url) ``` #### Proxy configuration You can configure a proxy by specifying the `proxy` argument when constructing a `deepl.DeepLClient`: ```python proxy = "http://user:pass@10.10.1.10:3128" deepl_client = deepl.DeepLClient(..., proxy=proxy) ``` The proxy argument is passed to the underlying `requests` session, see the [documentation for requests][requests-proxy-docs]; a dictionary of schemes to proxy URLs is also accepted. #### Override SSL verification You can control how `requests` performs SSL verification by specifying the `verify_ssl` option when constructing a `deepl.DeepLClient`, for example to disable SSL certificate verification: ```python deepl_client = deepl.DeepLClient(..., verify_ssl=False) ``` This option is passed to the underlying `requests` session as the `verify` option, see the [documentation for requests][requests-verify-ssl-docs]. #### Configure automatic retries This SDK will automatically retry failed HTTP requests (if the failures could be transient, e.g. a HTTP 429 status code). This behaviour can be configured in `http_client.py`, for example by default the number of retries is 5. This can be changed to 3 as follows: ```python import deepl deepl.http_client.max_network_retries = 3 c = deepl.DeepLClient(...) c.translate_text(...) ``` You can configure the timeout `min_connection_timeout` the same way, as well as set a custom `user_agent`, see the next section. #### Anonymous platform information By default, we send some basic information about the platform the client library is running on with each request, see [here for an explanation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent). This data is completely anonymous and only used to improve our product, not track any individual users. If you do not wish to send this data, you can opt-out when creating your `deepl.DeepLClient` object by setting the `send_platform_info` flag like so: ```python deepl_client = deepl.DeepLClient(..., send_platform_info=False) ``` You can also customize the `user_agent` by setting its value explicitly before constructing your `deepl.DeepLClient` object. ```python deepl.http_client.user_agent = 'my custom user agent' deepl_client = deepl.DeepLClient(os.environ["DEEPL_AUTH_KEY"]) ``` ## Command Line Interface The library can be run on the command line supporting all API functions. Use the `--help` option for usage information: ```shell python3 -m deepl --help ``` The CLI requires your DeepL authentication key specified either as the `DEEPL_AUTH_KEY` environment variable, through the `keyring` module, or using the `--auth-key` option, for example: ```shell python3 -m deepl --auth-key=YOUR_AUTH_KEY usage ``` Note that the `--auth-key` argument must appear *before* the command argument. To use the [keyring](https://pypi.org/project/keyring/) module, set the *DEEPL_AUTH_KEY* field in the service *deepl* to your API key. The recognized commands are: | Command | Description | | :-------- | :----------------------------------------------------- | | text | translate text(s) | | document | translate document(s) | | usage | print usage information for the current billing period | | languages | print available languages | | glossary | create, list, and remove glossaries | For example, to translate text: ```shell python3 -m deepl --auth-key=YOUR_AUTH_KEY text --to=DE "Text to be translated." ``` Wrap text arguments in quotes to prevent the shell from splitting sentences into words. ## Issues If you experience problems using the library, or would like to request a new feature, please open an [issue][issues]. ## Development We welcome Pull Requests, please read the [contributing guidelines](CONTRIBUTING.md). ### Tests Execute the tests using `pytest`. The tests communicate with the DeepL API using the auth key defined by the `DEEPL_AUTH_KEY` environment variable. Be aware that the tests make DeepL API requests that contribute toward your API usage. The test suite may instead be configured to communicate with the mock-server provided by [deepl-mock][deepl-mock]. Although most test cases work for either, some test cases work only with the DeepL API or the mock-server and will be otherwise skipped. The test cases that require the mock-server trigger server errors and test the client error-handling. To execute the tests using deepl-mock, run it in another terminal while executing the tests. Execute the tests using `pytest` with the `DEEPL_MOCK_SERVER_PORT` and `DEEPL_SERVER_URL` environment variables defined referring to the mock-server. [api-docs]: https://www.deepl.com/docs-api?utm_source=github&utm_medium=github-python-readme [api-docs-csv-format]: https://www.deepl.com/docs-api/managing-glossaries/supported-glossary-formats/?utm_source=github&utm_medium=github-python-readme [api-docs-xml-handling]: https://www.deepl.com/docs-api/handling-xml/?utm_source=github&utm_medium=github-python-readme [api-docs-context-param]: https://www.deepl.com/docs-api/translating-text/?utm_source=github&utm_medium=github-python-readme [api-docs-lang-list]: https://www.deepl.com/docs-api/translating-text/?utm_source=github&utm_medium=github-python-readme [api-docs-glossary-lang-list]: https://www.deepl.com/docs-api/managing-glossaries/?utm_source=github&utm_medium=github-python-readme [bug-report-ubuntu-2204]: https://bugs.launchpad.net/ubuntu/+source/poetry/+bug/1958227 [create-account]: https://www.deepl.com/pro?utm_source=github&utm_medium=github-python-readme#developer [deepl-mock]: https://www.github.com/DeepLcom/deepl-mock [issues]: https://www.github.com/DeepLcom/deepl-python/issues [pypi-project]: https://pypi.org/project/deepl/ [pro-account]: https://www.deepl.com/pro-account/?utm_source=github&utm_medium=github-python-readme [python-version-list]: https://devguide.python.org/versions/ [requests-proxy-docs]: https://docs.python-requests.org/en/latest/user/advanced/#proxies [requests-verify-ssl-docs]: https://docs.python-requests.org/en/latest/user/advanced/#ssl-cert-verification DeepL-deepl-python-df50df2/SECURITY.md000066400000000000000000000064271523706276600174250ustar00rootroot00000000000000To report security concerns or vulnerabilities within deepl-python, please email us at [security@deepl.com](mailto:security@deepl.com). You can send us PGP-encrypted email using the following PGP public key: ``` -----BEGIN PGP PUBLIC KEY BLOCK----- mQINBF7WSmABEADzRUp22VY7bVfUWScKLi9o8BRSEL4u3aPn9WOQoRLQH0j3dNNQ FQlwTPn/Ez6qreEl8mX0aE+eLCEykXqsrU/UaTSTslF+H6UQyuGLXkRm8Lblt93I OEhL069fC7rm+/zJq72+hishBF8DXqa+WtFd8VfK3i211vRhU/teKeAKT0xiuN/5 EQl1Bn7jR7mmQtNbPBhCsAlaC/tNUQ3Lyj6LYnnco7ums5Q/gCvfs2HM3mXJyvnG 1MC2IrECPowTt04W3V1uXuMcm766orTG/AmtBIbPmOzao4sfqwRVHGvc8zcr1az9 0nVyEJXx1eUVRDU1GAQuMjEkGgwvTd+nt6sHpn8C+9hMYJhon9veDSupViBuvNRC p1PnpSLYYy7tA7DPXMhP3cMXe+Z4bYzgwi3xjOwh6SDyB4OFIxtGyuMrLGfZnd6A hDH4H9zHPpciD5MxcOaqlKdgABQALvc6MvJ1Guf1ckGTbfHz1brtR1LPMK8rrnNu kwQzgkogYV6YAnt8LPXMPa2Vgy8TAiby7GPaATPeSWdNHtkuYGhWNVbnb60kEWiJ /RgHFZYfRT1dEcKoQEcDJ7AV14urEFIAfmhlsT8h7iJYUQMa45BakUubi3aWwcme ya+5WXvp2xU14VMfrscApA0e1v0VcTNVwlKambs/lwims0/xiSaXJS6gVwARAQAB tCNEZWVwTCBTZWN1cml0eSA8c2VjdXJpdHlAZGVlcGwuY29tPokCTgQTAQgAOBYh BGvTAPE3gtThLDZ9+ey96Y7yK41BBQJe1kpgAhsDBQsJCAcCBhUKCQgLAgQWAgMB Ah4BAheAAAoJEOy96Y7yK41BHVIP/04R08g4N32c47edY6z3sl3DAf+/6UI4Bc4S Jg5L4JcfrsKaDd55plps8nj31VXrxVBO0NrO6HLC50SXbYVrANyo0occ2mIoU8c2 tNbYCUmJ3QjlUwDjHWlMV2J9FcfZkv7z+2TDY6DF8MKqCMi8j7Pnj0hlY0JytciH SGES1q8+//8tG9z6b6vvxBFfJI+iNXvcbn6uU1WRvGoBqq2A13fXuwTXiNNphsvu kHqBHSxnf/EAmcmBX0tm6yaWDdwy+rrcDNwXiqqvK6DFWEE7+/9t2FhlgzvuCOfx dQVMZL8WH2rr6OPQLDgtGxEUFmD+srmqbVn5NKdY6lQ/BEaraozDkuqJEb0/L/kb Dv+buz8rmKze0XPlrt1XTQ5ZDQp8AMAaPp1UsizVhasZgxxuUa+g5mMbJr7TSNJN CIqidnh1MEyIr3IccOAr2F51hn6keKIdVnO4dWrWNMTfk00dw3fPGFhNTniITTF2 s3oJ8cy2NMNkVMP5XL3bulpgkKN+hXa4IHkTfWRv7hfYJ/3i3yTRNRjYGRoVp7eM iADumKaZy5Szl458txuI+p9DGAEvkSJoF7ptwedSvVZ/FZukS5mwYisRV9shzsXF 3jpcGZ1B3qS68r9ySqnPEWR6oT8p63fpMNVMjz5r4YEbvU0A62OhUk52drLM6SgC mdOZcmnHuQINBF7WSmABEADc6L/wSexm4l1GWZSQpJ35ldlu7jjWQGguQeeG2900 aEI3UcftMCWg+apwf4h4Yj2YjzUncYAM6RenGvXgZUYQe3OHb8uqpkSmYHUdB/Uq I4NPO3e8RMDo9YohPKCpZ7jV70X8F9GOUkUgfp29CjrMOYgSLwkSyWotsQ9KtkEH Sx/h+gviIERe0dkiN9lCsReNigoWLleH4qBSZGPxqF4tzANJ6D2tnAv+6KUQvho3 CdijBiia4o16p9M0altSqsZCEX1Y5BKmWIh9fvvS2uB7SdzS0gcASzlekMGCjG10 dNji+uSNdHExlbl0kUpEL1TuY2hxPBa6lc1hckI3dGng0jIFlio4s8DG3Utmrj3C KQFxnjqtO+uaJ8HdNo8ObtEp/v9TpsHWUchBTrBP4XN5KwqkljF8XVBA6ceh8H38 7/RVWRcWp6h30ROm1DTnAGxJk02fbjpnEO0EvudxKTlnAJXV6z+Tm3yYaR4gQYa3 /zfLZgz0z0MqNUsGephZGPzfUX7Lsz6HGUoo7I1KST6xD2QodJYOhHIEOgsqskk+ cgeXp45X5JLlCQaBLQoL8ut6CTcop1/6U+JZtrm6DdXTZfq57sqfDI+gkG8WljRY yhsCL+xWiwDjtt/8kpk+W75EQmwPuctoS85Rm6hEpffewdQtb2XCEWpbta6hE1r1 kQARAQABiQI2BBgBCAAgFiEEa9MA8TeC1OEsNn357L3pjvIrjUEFAl7WSmACGwwA CgkQ7L3pjvIrjUHFvg/9GnIW9SM/nYJpi1xZVWWGwQ+/kTceD50bv8kyvNaia/9m HG6n83xHNTRBYnt8NtTqHvW0y20Cp3gUs2WxboDgCIb3+srI2ipwiaDJcq+rVr0f XkCe5MryioKRbTFQ8OgvKh9GK/tYtqZakn7Q9596ajUjHOQV1+Uw/jywLYRlcbqI zbxyNVWitxPs3Z7jUDAvhPOIOmhLFc+QxSYrs1W4ZEGnZ3+9utqzlEiMusy9Rq0T /W/wrG6SckebjhrwWZJmy/hkW6V6LUX4++vCVV5+zwsvgEortCV8bhvLfqQDr/WN fnmbNZtXJbyhTYbcYReOLeKidxO2lZEemnX6iOt5xCdoMcYU23xDT9+tE7Eh6Nfw einZemBwfku5vxxPF73pOoQUCRq9tgvUrEq+3BqkqidhnFUOPi0J5726q1PBG65x 5o+SQyvB3NA3al3mEH65z3V3/g0UHnhGcEMwVOXBkffgdKNhWYw59qhSVQnkiq0U MG10g/RL7VdiISAFPTDmKWUaEDYosinKqOMHwcaVdJq9ssvPf89et6yP/ZkbLIHs 2y3oiPonh2RMxi2OedlDz+Jp/A2o3qHmwNvBx/meGB0praGUonFVZTAA1EMS39Bi NhG/L8giTyzA0mMkTJAPXtUVlRe5rEjORgYJsgRqZxEfpsJC9OkvYS4ayO0eCEs= =jVHt -----END PGP PUBLIC KEY BLOCK----- ```DeepL-deepl-python-df50df2/catalog-info.yaml000066400000000000000000000004321523706276600210510ustar00rootroot00000000000000apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: deepl-python title: DeepL Python Library description: Public Python library for interacting with the DeepL API spec: type: library lifecycle: production owner: api-core-team system: oss-client-librariesDeepL-deepl-python-df50df2/deepl/000077500000000000000000000000001523706276600167145ustar00rootroot00000000000000DeepL-deepl-python-df50df2/deepl/__init__.py000066400000000000000000000030101523706276600210170ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from .version import VERSION as __version__ # noqa __author__ = "DeepL SE " from .deepl_client import DeepLClient from .exceptions import ( # noqa AuthorizationException, ConnectionException, DeepLException, DocumentNotReadyException, DocumentTranslationException, GlossaryNotFoundException, TooManyRequestsException, QuotaExceededException, ) from . import http_client # noqa from .translator import ( # noqa DocumentHandle, DocumentStatus, Formality, GlossaryInfo, Language, ModelType, SplitSentences, TextResult, Translator, Usage, ) from .util import ( # noqa auth_key_is_free_account, convert_tsv_to_dict, convert_dict_to_tsv, validate_glossary_term, ) __all__ = [ "__version__", "__author__", "DeepLClient", "DocumentHandle", "DocumentStatus", "Formality", "GlossaryInfo", "Language", "SplitSentences", "TextResult", "Translator", "Usage", "http_client", "AuthorizationException", "ConnectionException", "DeepLException", "DocumentNotReadyException", "DocumentTranslationException", "GlossaryNotFoundException", "TooManyRequestsException", "QuotaExceededException", "auth_key_is_free_account", "convert_tsv_to_dict", "convert_dict_to_tsv", "validate_glossary_term", ] DeepL-deepl-python-df50df2/deepl/__main__.py000066400000000000000000001033361523706276600210140ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import argparse import json import deepl import logging import os import pathlib import sys from typing import List, Optional from deepl.util import _optional_import # Program name for integration with click.testing name = "python -m deepl" env_auth_key = "DEEPL_AUTH_KEY" env_server_url = "DEEPL_SERVER_URL" env_proxy_url = "DEEPL_PROXY_URL" keyring_key_folder = "deepl" keyring_key_name = env_auth_key def action_usage(deepl_client: deepl.DeepLClient): """Action function for the usage command.""" usage_result = deepl_client.get_usage() print(usage_result) def action_languages(deepl_client: deepl.DeepLClient, glossary: bool): """Action function for the languages command.""" if glossary: glossary_languages = deepl_client.get_glossary_languages() print("Language pairs supported for glossaries: (source, target)") for language_pair in glossary_languages: print(f"{language_pair.source_lang}, {language_pair.target_lang}") else: source_languages = deepl_client.get_source_languages() target_languages = deepl_client.get_target_languages() print("Source languages available:") for language in source_languages: print(f"{language.code}: {language.name}") print("Target languages available:") for language in target_languages: if language.supports_formality: print(f"{language.code}: {language.name} (supports formality)") else: print(f"{language.code}: {language.name}") def action_document( deepl_client: deepl.DeepLClient, file: List[str], dest: str, output_format: Optional[str], **kwargs, ): """Action function for the document command.""" if not os.path.exists(dest): os.makedirs(dest, exist_ok=True) elif not os.path.isdir(dest): raise Exception("Destination already exists, and is not a directory") for this_file in file: outfile_name = ( this_file if not output_format else (os.path.splitext(this_file)[0] + "." + output_format) ) output_path = os.path.join(dest, os.path.basename(outfile_name)) deepl_client.translate_document_from_filepath( this_file, output_path, **kwargs ) def action_text( deepl_client: deepl.DeepLClient, show_detected_source: bool = False, show_billed_characters: Optional[bool] = None, show_model_type_used: Optional[bool] = None, **kwargs, ): """Action function for the text command.""" if show_model_type_used and kwargs.get("model_type") is None: # specify model_type so API includes model_type_used response parameter kwargs["model_type"] = deepl.ModelType.LATENCY_OPTIMIZED translation = deepl_client.translate_text(**kwargs) output_list = ( translation if isinstance(translation, List) else [translation] ) for output in output_list: if show_detected_source: print(f"Detected source language: {output.detected_source_lang}") if show_billed_characters: text_value = ( "unknown" if output.billed_characters is None else str(output.billed_characters) ) print(f"Billed characters: {text_value}") if show_model_type_used: text_value = ( "unknown" if output.model_type_used is None else output.model_type_used ) print(f"Model type used: {text_value}") print(output.text) def action_rephrase( deepl_client: deepl.DeepLClient, **kwargs, ): """Action function for the rephrase command.""" improvement = deepl_client.rephrase_text(**kwargs) output_list = ( improvement if isinstance(improvement, List) else [improvement] ) for output in output_list: print(output.text) def action_glossary( deepl_client: deepl.DeepLClient, subcommand: str, **kwargs, ): # Call action function corresponding to command with remaining args globals()[f"action_glossary_{subcommand}"](deepl_client, **kwargs) pass def action_glossary_create( deepl_client: deepl.DeepLClient, entry_list, file, csv, **kwargs ): term_separator = None if file: if entry_list: raise deepl.DeepLException( "The --file argument cannot be used together with " "command-line entries" ) content = pathlib.Path(file).read_text("UTF-8") elif entry_list and entry_list[0] == "-": content = sys.stdin.read() else: content = "\n".join(entry_list) term_separator = "=" if csv: raise Exception( "csv option is not compatible with command-line entries" ) if csv: glossary = deepl_client.create_glossary_from_csv( csv_data=content, **kwargs ) else: if term_separator: entry_dict = deepl.convert_tsv_to_dict(content, term_separator) else: entry_dict = deepl.convert_tsv_to_dict(content) glossary = deepl_client.create_glossary(entries=entry_dict, **kwargs) print(f"Created {glossary}") print_glossaries([glossary]) def print_table(headers, data): data = [headers] + data col_max_widths = [ max(len(row[col_num]) for row in data) for col_num in range(len(headers)) ] for row in data: print( "\t".join( [col.ljust(width) for col, width in zip(row, col_max_widths)] ) ) def print_glossaries(glossaries): print_table( [ "Glossary ID", "Name", "Ready", "Source", "Target", "Count", "Created", ], [ [ glossary.glossary_id, glossary.name, str(glossary.ready), glossary.source_lang, glossary.target_lang, str(glossary.entry_count), str(glossary.creation_time), ] for glossary in glossaries ], ) def action_glossary_list(deepl_client: deepl.DeepLClient): glossaries = deepl_client.list_glossaries() print_glossaries(glossaries) def action_glossary_get(deepl_client: deepl.DeepLClient, **kwargs): glossary = deepl_client.get_glossary(**kwargs) print_glossaries([glossary]) def action_glossary_entries(deepl_client: deepl.DeepLClient, glossary_id): glossary_entries = deepl_client.get_glossary_entries(glossary=glossary_id) print(deepl.convert_dict_to_tsv(glossary_entries)) def action_glossary_delete( deepl_client: deepl.DeepLClient, glossary_id_list: str ): for glossary_id in glossary_id_list: deepl_client.delete_glossary(glossary_id) print(f"Glossary with ID {glossary_id} successfully deleted.") def action_translation_memory( deepl_client: deepl.DeepLClient, subcommand: str, **kwargs, ): # Call action function corresponding to command with remaining args globals()[f"action_translation_memory_{subcommand}"]( deepl_client, **kwargs ) def print_translation_memories(translation_memories): print_table( ["Translation Memory ID", "Name", "Source", "Targets", "Segments"], [ [ tm.translation_memory_id, tm.name, tm.source_language, ",".join(tm.target_languages), str(tm.segment_count), ] for tm in translation_memories ], ) def print_translation_memory_job(job): result = job.result print(f"Job {job.job_id} ({job.operation}): {job.status}") if result is None: return if result.required_action: print(f"Required action: {result.required_action}") if result.translation_memory_id: print(f"Translation memory ID: {result.translation_memory_id}") if result.skipped_segment_count is not None: print(f"Skipped segments: {result.skipped_segment_count}") if result.download_url: print(f"Download URL: {result.download_url}") if result.error_message: print(f"Error: {result.error_message}") def action_translation_memory_list( deepl_client: deepl.DeepLClient, page, page_size ): translation_memories = deepl_client.list_translation_memories( page=page, page_size=page_size ) print_translation_memories(translation_memories) def action_translation_memory_get( deepl_client: deepl.DeepLClient, translation_memory_id ): print_translation_memories( [deepl_client.get_translation_memory(translation_memory_id)] ) def action_translation_memory_segments( deepl_client: deepl.DeepLClient, translation_memory_id, page_size, page_cursor, filter_text, filter_case_sensitive, all_pages, ): rows = [] segment_count = 0 while True: page = deepl_client.list_translation_memory_segments( translation_memory_id, page_size=page_size, page_cursor=page_cursor, filter_text=filter_text, filter_case_sensitive=filter_case_sensitive or None, ) segment_count = page.segment_count for segment in page.segments: for target in segment.targets: rows.append( [ segment.source_segment_id, segment.source_text, target.target_language, target.target_text, ] ) page_cursor = page.next_page_cursor if not all_pages or not page_cursor: break print_table(["Segment ID", "Source", "Target lang", "Target"], rows) print(f"Total segments in translation memory: {segment_count}") if page_cursor: print(f"Next page cursor: {page_cursor}") def action_translation_memory_delete( deepl_client: deepl.DeepLClient, translation_memory_id_list ): for translation_memory_id in translation_memory_id_list: deepl_client.delete_translation_memory(translation_memory_id) print( f"Translation memory with ID {translation_memory_id} " "successfully deleted." ) def action_translation_memory_import( deepl_client: deepl.DeepLClient, file, name, no_wait, timeout ): file_path = pathlib.Path(file) if no_wait: created = deepl_client.create_translation_memory_import( file_name=file_path.name, content_length=file_path.stat().st_size, display_name=name, ) with open(file_path, "rb") as input_file: deepl_client.upload_translation_memory_file(created, input_file) print(f"Uploaded {file}, import job ID {created.job_id}") return job = deepl_client.import_translation_memory_from_filepath( file_path, display_name=name, timeout_s=timeout ) print_translation_memory_job(job) def action_translation_memory_export( deepl_client: deepl.DeepLClient, translation_memory_id, output_file, timeout, ): job = deepl_client.export_translation_memory_to_filepath( translation_memory_id, output_file, timeout_s=timeout ) print(f"Exported translation memory to {output_file}") print_translation_memory_job(job) def action_translation_memory_job(deepl_client: deepl.DeepLClient, job_id): print_translation_memory_job( deepl_client.get_translation_memory_job(job_id) ) def get_parser(prog_name): """Constructs and returns the argument parser for all commands.""" parser = argparse.ArgumentParser( prog=prog_name, description="Translate text using the DeepL API " "(https://www.deepl.com/docs-api).", epilog="If you encounter issues while using this program, please " "report them at https://github.com/DeepLcom/deepl-python/issues", ) parser.add_argument( "--version", action="version", version=f"deepl-python v{deepl.__version__}", ) parser.add_argument( "--verbose", "-v", action="count", dest="verbose", default=0, help="print additional information, can be supplied multiple times " "for more verbose output", ) parser.add_argument( "--no-platform-info", default=False, action="store_true", dest="noplatforminfo", help="if this flag is enabled, do not send additional information " "about the platform with API requests.", ) parser.add_argument( "--auth-key", default=None, help="authentication key as given in your DeepL account; the " f"{env_auth_key} environment variable is used as secondary fallback; " f"the key {keyring_key_name} in {keyring_key_folder} is used " "as tertiary fallback", ) parser.add_argument( "--server-url", default=None, metavar="URL", help=f"alternative server URL for testing; the {env_server_url} " f"environment variable may be used as secondary fallback", ) parser.add_argument( "--proxy-url", default=None, metavar="URL", help="proxy server URL to use for all connections; the " f"{env_proxy_url} environment variable may be used as secondary " "fallback", ) subparsers = parser.add_subparsers( metavar="command", dest="command", required=True ) def add_common_arguments(subparser: argparse.ArgumentParser): """Adds arguments shared between text and document commands to the subparser.""" subparser.add_argument( "--to", "--target-lang", dest="target_lang", required=True, help="language into which the text should be translated", ) subparser.add_argument( "--from", "--source-lang", dest="source_lang", help="language of the text to be translated; unless using a " "glossary, this argument is optional and if it is omitted DeepL " "will auto-detect the source language.", ) subparser.add_argument( "--formality", type=str, choices=[enum.value for enum in deepl.Formality], default=deepl.Formality.DEFAULT.value, help="desired formality for translation", ) subparser.add_argument( "--glossary-id", dest="glossary", type=str, help="ID of glossary to use for translation", ) subparser.add_argument( "--glossary-ids", dest="glossary_ids", action="append", type=str, metavar="id", help="ID of a glossary to use for translation; may be repeated to " "use up to 5 glossaries, applied in order. Requires --from and " "cannot be combined with --glossary-id", ) subparser.add_argument( "--extra-body-parameters", dest="extra_body_parameters", type=json.loads, default=None, help="additional body parameters to include in the API request, " "specified as a JSON object string, for example: " '\'{"tag_handling": "xml", "show_billed_characters": true}\'', ) # create the parser for the "text" command parser_text = subparsers.add_parser( "text", help="translate text(s)", description="translate text(s)", aliases=["translate"], ) add_common_arguments(parser_text) parser_text.add_argument( "--context", type=str, help="additional contextual text to improve translations, see API docs" " for information", ) parser_text.add_argument( "--split-sentences", type=str, choices=[enum.value for enum in deepl.SplitSentences], default=deepl.SplitSentences.DEFAULT.value, help="control sentence splitting before translation, see API for " "information", ) parser_text.add_argument( "--preserve-formatting", action="store_true", help="leave original formatting unchanged during translation", ) parser_text.add_argument( "--show-billed-characters", dest="show_billed_characters", action="store_true", help="print billed characters for each text", ) parser_text.add_argument( "--show-model-type-used", dest="show_model_type_used", action="store_true", help="print the model type used for each text", ) parser_text.add_argument( "--model-type", type=str, choices=[enum.value for enum in deepl.ModelType], default=None, help="control model used for translation, see API for information", ) parser_text.add_argument( "--style-id", dest="style_rule", type=str, help="ID of style rule to use for translation", ) parser_text.add_argument( "--translation-memory-id", dest="translation_memory", type=str, help="ID of translation memory to use for translation", ) parser_text.add_argument( "--translation-memory-threshold", dest="translation_memory_threshold", type=int, help="minimum matching percentage (0-100) for translation memory " "fuzzy matches, recommended minimum is 75", ) parser_text.add_argument( "--custom-instructions", dest="custom_instructions", action="append", type=str, help="custom instructions to guide translation (can be specified " "multiple times, max 10 instructions, each max 300 characters)", ) parser_text.add_argument( "text", nargs="+", type=str, help="text to be translated. Wrap text in quotes to prevent the shell " 'from splitting sentences into words. Alternatively, use "-" to read ' "from standard-input.", ) parser_text.add_argument( "--show-detected-source", action="store_true", help="print detected source language for each text", ) tag_handling_group = parser_text.add_argument_group( "tag-handling", description="Arguments controlling tag handling, for example XML. " "The -tags arguments accept multiple arguments, as comma-" "separated lists and as repeated arguments. For example, these are " 'equivalent: "--ignore-tags a --ignore-tags b,c" and "--ignore-tags ' 'a,b,c".', ) tag_handling_group.add_argument( "--tag-handling", type=str, choices=["xml", "html"], default=None, help="activate processing of formatting tags, for example 'xml'", ) tag_handling_group.add_argument( "--tag-handling-version", type=str, choices=["v1", "v2"], default=None, help="specify which version of the tag handling algorithm to use", ) tag_handling_group.add_argument( "--outline-detection-off", dest="outline_detection", default=True, action="store_false", help="disable automatic tag selection", ) tag_handling_group.add_argument( "--non-splitting-tags", type=str, action="append", metavar="tag", help="specify tags that may occur within sentences", ) tag_handling_group.add_argument( "--splitting-tags", type=str, action="append", metavar="tag", help="specify tags that separate text into sentences", ) tag_handling_group.add_argument( "--ignore-tags", type=str, action="append", metavar="tag", help="specify tags containing text that should not be translated", ) # create the parser for the "rephrase" command parser_rephrase = subparsers.add_parser( "rephrase", help="rephrase text(s)", description="rephrase text(s)" ) parser_rephrase.add_argument( "--to", "--target-lang", dest="target_lang", required=True, help="language into which the text should be rewritten", ) parser_rephrase.add_argument( "text", nargs="+", type=str, help="text to be rewritten. Wrap text in quotes to prevent the shell " 'from splitting sentences into words. Alternatively, use "-" to read ' "from standard-input.", ) parser_rephrase.add_argument( "--show-detected-source", action="store_true", help="print detected source language for each text", ) # create the parser for the "document" command parser_document = subparsers.add_parser( "document", help="translate document(s)", description="translate document(s)", ) add_common_arguments(parser_document) parser_document.add_argument( "--style-id", dest="style_rule", type=str, help="ID of style rule to use for translation", ) parser_document.add_argument( "--translation-memory-id", dest="translation_memory", type=str, help="ID of translation memory to use for translation", ) parser_document.add_argument( "--translation-memory-threshold", dest="translation_memory_threshold", type=int, help="minimum matching percentage (0-100) for translation memory " "fuzzy matches, recommended minimum is 75", ) parser_document.add_argument( "file", nargs="+", help="file(s) to be translated." ) parser_document.add_argument( "--output-format", type=str, default=None, help="output file extension" ) parser_document.add_argument( "dest", help="destination directory to store translated files." ) # create the parser for the "usage" command usage_help_str = "print usage information for the current billing period" subparsers.add_parser( "usage", help=usage_help_str, description=usage_help_str ) # create the parser for the "languages" command languages_help_str = "print available languages" parser_languages = subparsers.add_parser( "languages", help=languages_help_str, description=languages_help_str ) parser_languages.add_argument( "--glossary", help="list language pairs supported for glossaries.", action="store_true", ) # create the parser for the "glossary" command parser_glossary = subparsers.add_parser( "glossary", help="create, list, and remove glossaries", description="manage glossaries using subcommands", ) glossary_subparsers = parser_glossary.add_subparsers( metavar="subcommand", dest="subcommand", required=True ) parser_glossary_create = glossary_subparsers.add_parser( "create", help="create a new glossary", description="create a new glossary using entries provided via command-" "line, standard-input, or specified in a TSV or CSV file", ) parser_glossary_create.add_argument( "--name", required=True, help="name to be associated with glossary." ) parser_glossary_create.add_argument( "--from", "--source-lang", dest="source_lang", required=True, help="language in which source entries of the glossary are specified.", ) parser_glossary_create.add_argument( "--to", "--target-lang", dest="target_lang", required=True, help="language in which target entries of the glossary are specified.", ) parser_glossary_create.add_argument( "entry_list", nargs="*", type=str, metavar="SOURCE=TARGET", help="one or more entries to add to glossary, may be repeated. " 'Alternatively, use "-" to read entries from standard-input in TSV or ' "CSV format (see --file argument for formatting information). These " "arguments cannot be used together with the --file argument.", ) parser_glossary_create.add_argument( "--file", type=str, help="file to read glossary entries from. Unless --csv is specified, " "file format is expected to be tab-separated values (TSV) format: one " "entry-pair per line, each line contains the source entry, a tab, " "then the target entry. Empty lines are ignored.", ) parser_glossary_create.add_argument( "--csv", action="store_true", help="the provided --file option or standard-input should be " "interpreted as a CSV file. Information about the expected CSV format " "can be found in the API documentation: " "https://www.deepl.com/docs-api/managing-glossaries/supported-glossary-formats/.", # noqa ) parser_glossary_list = glossary_subparsers.add_parser( "list", help="list available glossaries", description="list available glossaries", ) _ = parser_glossary_list # Suppress unused variable warning parser_glossary_get = glossary_subparsers.add_parser( "get", help="print details about one glossary", description="print details about one glossary", ) parser_glossary_get.add_argument( "glossary_id", metavar="id", type=str, help="ID of glossary to retrieve", ) parser_glossary_entries = glossary_subparsers.add_parser( "entries", help="get entries contained in a glossary", description="get entries contained in a glossary, and print them to " "standard-output in tab-separated values (TSV) format: one entry-pair " "per line, each line contains the source entry, a tab, then the " "target entry.", ) parser_glossary_entries.add_argument( "glossary_id", metavar="id", type=str, help="ID of glossary to retrieve", ) parser_glossary_delete = glossary_subparsers.add_parser( "delete", help="delete one or more glossaries", description="delete one or more glossaries", ) parser_glossary_delete.add_argument( "glossary_id_list", metavar="id", nargs="+", type=str, help="ID of glossary to delete", ) # create the parser for the "translation-memory" command parser_tm = subparsers.add_parser( "translation-memory", help="list, inspect, import, export, and remove translation memories", description="manage translation memories using subcommands", ) tm_subparsers = parser_tm.add_subparsers( metavar="subcommand", dest="subcommand", required=True ) parser_tm_list = tm_subparsers.add_parser( "list", help="list available translation memories", description="list available translation memories", ) parser_tm_list.add_argument( "--page", type=int, default=None, help="page number to retrieve, 0-indexed", ) parser_tm_list.add_argument( "--page-size", type=int, default=None, help="number of translation memories per page", ) parser_tm_get = tm_subparsers.add_parser( "get", help="print details about one translation memory", description="print details about one translation memory", ) parser_tm_get.add_argument( "translation_memory_id", metavar="id", type=str, help="ID of translation memory to retrieve", ) parser_tm_segments = tm_subparsers.add_parser( "segments", help="list the segments of a translation memory", description="list the segments of a translation memory, one row per " "source segment and target language", ) parser_tm_segments.add_argument( "translation_memory_id", metavar="id", type=str, help="ID of translation memory whose segments to list", ) parser_tm_segments.add_argument( "--page-size", type=int, default=None, help="maximum segments per page (1-100, defaults to 50)", ) parser_tm_segments.add_argument( "--page-cursor", type=str, default=None, help="cursor from a previous response, to fetch the next page", ) parser_tm_segments.add_argument( "--filter-text", type=str, default=None, help="substring filter across source and target text, at least 2 " "characters", ) parser_tm_segments.add_argument( "--filter-case-sensitive", action="store_true", help="make --filter-text case-sensitive", ) parser_tm_segments.add_argument( "--all", dest="all_pages", action="store_true", help="page through all segments instead of returning a single page", ) parser_tm_delete = tm_subparsers.add_parser( "delete", help="delete one or more translation memories", description="delete one or more translation memories", ) parser_tm_delete.add_argument( "translation_memory_id_list", metavar="id", nargs="+", type=str, help="ID of translation memory to delete", ) parser_tm_import = tm_subparsers.add_parser( "import", help="import a TMX file as a new translation memory", description="import a TMX file as a new translation memory: creates " "the import job, uploads the file, and waits for processing to finish", ) parser_tm_import.add_argument( "file", type=str, help="path of the TMX file to import", ) parser_tm_import.add_argument( "--name", type=str, default=None, help="name for the resulting translation memory, defaults to the " "file name", ) parser_tm_import.add_argument( "--no-wait", action="store_true", help="upload the file and print the job ID without waiting for " "processing to finish", ) parser_tm_import.add_argument( "--timeout", type=int, default=None, help="maximum number of seconds to wait for the import to finish", ) parser_tm_export = tm_subparsers.add_parser( "export", help="export a translation memory to a TMX file", description="export a translation memory to a TMX file: creates the " "export job, waits for it to finish, and writes the result", ) parser_tm_export.add_argument( "translation_memory_id", metavar="id", type=str, help="ID of translation memory to export", ) parser_tm_export.add_argument( "output_file", metavar="file", type=str, help="path to write the exported TMX file to", ) parser_tm_export.add_argument( "--timeout", type=int, default=None, help="maximum number of seconds to wait for the export to finish", ) parser_tm_job = tm_subparsers.add_parser( "job", help="print the status of an import or export job", description="print the status of a translation memory import or " "export job", ) parser_tm_job.add_argument( "job_id", metavar="id", type=str, help="ID of the job to query", ) return parser, parser_glossary def main(args=None, prog_name=None): parser, parser_glossary = get_parser(prog_name) args = parser.parse_args(args) logger = logging.getLogger("deepl") if args.verbose == 1: logger.setLevel(logging.INFO) logger.addHandler(logging.StreamHandler()) elif args.verbose >= 2: logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler()) else: logger.setLevel(logging.WARNING) server_url = args.server_url or os.getenv(env_server_url) proxy_url = args.proxy_url or os.getenv(env_proxy_url) auth_key = args.auth_key or os.getenv(env_auth_key) keyring = _optional_import("keyring") if keyring: keyring_pw = None try: keyring_pw = keyring.get_password( keyring_key_folder, keyring_key_name ) except keyring.errors.NoKeyringError: pass auth_key = auth_key or keyring_pw try: if auth_key is None: raise Exception( f"Please provide authentication key via the {env_auth_key} " "environment variable or --auth-key argument or via " f"{keyring_key_name} in {keyring_key_folder} in keyring" ) # Note: the get_languages() call to verify language codes is skipped # because the CLI makes one API call per execution. deepl_client = deepl.DeepLClient( auth_key=auth_key, server_url=server_url, proxy=proxy_url, skip_language_check=True, send_platform_info=not args.noplatforminfo, ) if args.command in ["text", "translate", "rephrase"]: if len(args.text) == 1 and args.text[0] == "-": args.text = [sys.stdin.read()] # Remove global args so they are not unrecognised in action functions del ( args.verbose, args.server_url, args.auth_key, args.proxy_url, args.noplatforminfo, ) args = vars(args) # Call action function corresponding to command with remaining args # ("translation-memory" maps to action_translation_memory). command = args.pop("command").replace("-", "_") globals()[f"action_{command}"](deepl_client, **args) except Exception as exception: sys.stderr.write(f"Error: {exception}\n") sys.exit(1) if __name__ == "__main__": main(prog_name="deepl") DeepL-deepl-python-df50df2/deepl/api_data.py000066400000000000000000001515331523706276600210400ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import datetime from enum import Enum from typing import Dict, List, Optional, Tuple, Union from deepl.util import get_int_safe, parse_timestamp class TextResult: """Holds the result of a text translation request.""" def __init__( self, text: str, detected_source_lang: str, billed_characters: int, model_type_used: Optional[str] = None, ): self.text = text self.detected_source_lang = detected_source_lang self.billed_characters = billed_characters self.model_type_used = model_type_used def __str__(self): return self.text class WriteResult: """Holds the result of a text improvement request.""" def __init__( self, text: str, detected_source_language: str, target_language: str ): self.text = text self.detected_source_language = detected_source_language self.target_language = target_language def __str__(self): return self.text class DocumentHandle: """Handle to an in-progress document translation. :param document_id: ID of associated document request. :param document_key: Key of associated document request. """ def __init__(self, document_id: str, document_key: str): self._document_id = document_id self._document_key = document_key def __str__(self): return f"Document ID: {self.document_id}, key: {self.document_key}" @property def document_id(self) -> str: return self._document_id @property def document_key(self) -> str: return self._document_key class DocumentStatus: """Status of a document translation request. :param status: One of the Status enum values below. :param seconds_remaining: Estimated time until document translation completes in seconds, or None if unknown. :param billed_characters: Number of characters billed for this document, or None if unknown or before translation is complete. :param error_message: A short description of the error, or None if no error has occurred. """ class Status(Enum): QUEUED = "queued" TRANSLATING = "translating" DONE = "done" DOWNLOADED = "downloaded" ERROR = "error" def __init__( self, status: Status, seconds_remaining=None, billed_characters=None, error_message=None, ): self._status = self.Status(status) self._seconds_remaining = seconds_remaining self._billed_characters = billed_characters self._error_message = error_message def __str__(self) -> str: return self.status.value @property def ok(self) -> bool: return self._status != self.Status.ERROR @property def done(self) -> bool: return self._status == self.Status.DONE @property def status(self) -> Status: return self._status @property def seconds_remaining(self) -> Optional[int]: return self._seconds_remaining @property def billed_characters(self) -> Optional[int]: return self._billed_characters @property def error_message(self) -> Optional[int]: return self._error_message class Usage: """Holds the result of a usage request. The character, document and team_document properties provide details about each corresponding usage type. These properties allow each usage type to be checked individually. The any_limit_reached property checks if for any usage type the amount used has reached the allowed amount. """ class Detail: def __init__(self, json: Optional[dict], prefix: str): self._count = get_int_safe(json, f"{prefix}_count") self._limit = get_int_safe(json, f"{prefix}_limit") @property def count(self) -> Optional[int]: """Returns the amount used for this usage type, may be None.""" return self._count @property def limit(self) -> Optional[int]: """Returns the maximum amount for this usage type, may be None.""" return self._limit @property def valid(self) -> bool: """True iff both the count and limit are set for this usage type.""" return self._count is not None and self._limit is not None @property def limit_reached(self) -> bool: """True if this limit is valid and the amount used is greater than or equal to the amount allowed, otherwise False.""" return self.valid and self.count >= self.limit # type: ignore[operator] # noqa: E501 @property def limit_exceeded(self) -> bool: """Deprecated, use limit_reached instead.""" import warnings warnings.warn( "limit_reached is deprecated", DeprecationWarning, stacklevel=2 ) return self.limit_reached def __str__(self) -> str: return f"{self.count} of {self.limit}" if self.valid else "Unknown" def __init__(self, json: Optional[dict]): self._character = self.Detail(json, "character") self._document = self.Detail(json, "document") self._team_document = self.Detail(json, "team_document") @property def any_limit_reached(self) -> bool: """True if for any API usage type, the amount used is greater than or equal to the amount allowed, otherwise False.""" return ( self.character.limit_reached or self.document.limit_reached or self.team_document.limit_reached ) @property def any_limit_exceeded(self) -> bool: """Deprecated, use any_limit_reached instead.""" import warnings warnings.warn( "any_limit_reached is deprecated", DeprecationWarning, stacklevel=2 ) return self.any_limit_reached @property def character(self) -> Detail: """Returns usage details for characters, primarily associated with the translate_text (/translate) function.""" return self._character @property def document(self) -> Detail: """Returns usage details for documents.""" return self._document @property def team_document(self) -> Detail: """Returns usage details for documents shared among your team.""" return self._team_document def __str__(self) -> str: details: List[Tuple[str, Usage.Detail]] = [ ("Characters", self.character), ("Documents", self.document), ("Team documents", self.team_document), ] return "Usage this billing period:\n" + "\n".join( [f"{label}: {detail}" for label, detail in details if detail.valid] ) class Language: """Information about a language supported by DeepL translator. :param code: Language code according to ISO 639-1, for example "EN". Some target languages also include the regional variant according to ISO 3166-1, for example "EN-US". :param name: Name of the language in English. :param supports_formality: (Optional) Specifies whether the formality option is available for this language; target languages only. """ def __init__( self, code: str, name: str, supports_formality: Optional[bool] = None ): self.code = code self.name = name self.supports_formality = supports_formality def __str__(self): return self.code @staticmethod def remove_regional_variant(language: Union[str, "Language"]) -> str: """Removes the regional variant from a language, e.g. EN-US gives EN""" dash_index = str(language).find("-") if dash_index != -1: return str(language).upper()[0:dash_index] else: return str(language).upper() ACEHNESE = "ace" AFRIKAANS = "af" ARAGONESE = "an" ARABIC = "ar" ASSAMESE = "as" AYMARA = "ay" AZERBAIJANI = "az" BASHKIR = "ba" BELARUSIAN = "be" BULGARIAN = "bg" BHOJPURI = "bho" BENGALI = "bn" BRETON = "br" BOSNIAN = "bs" CATALAN = "ca" CEBUANO = "ceb" KURDISH_SORANI = "ckb" CZECH = "cs" WELSH = "cy" DANISH = "da" GERMAN = "de" GREEK = "el" ENGLISH = "en" # Only usable as a source language ENGLISH_BRITISH = "en-GB" # Only usable as a target language ENGLISH_AMERICAN = "en-US" # Only usable as a target language ESPERANTO = "eo" SPANISH = "es" SPANISH_LATIN_AMERICAN = "es-419" # Only usable as a target language ESTONIAN = "et" BASQUE = "eu" PERSIAN = "fa" FINNISH = "fi" FRENCH = "fr" IRISH = "ga" GALICIAN = "gl" GUARANI = "gn" KONKANI = "gom" GUJARATI = "gu" HAUSA = "ha" HEBREW = "he" HINDI = "hi" CROATIAN = "hr" HAITIAN_CREOLE = "ht" HUNGARIAN = "hu" ARMENIAN = "hy" INDONESIAN = "id" IGBO = "ig" ICELANDIC = "is" ITALIAN = "it" JAPANESE = "ja" JAVANESE = "jv" GEORGIAN = "ka" KAZAKH = "kk" KURDISH_KURMANJI = "kmr" KOREAN = "ko" KYRGYZ = "ky" LATIN = "la" LUXEMBOURGISH = "lb" LOMBARD = "lmo" LINGALA = "ln" LITHUANIAN = "lt" LATVIAN = "lv" MAITHILI = "mai" MALAGASY = "mg" MAORI = "mi" MACEDONIAN = "mk" MALAYALAM = "ml" MONGOLIAN = "mn" MARATHI = "mr" MALAY = "ms" MALTESE = "mt" BURMESE = "my" NORWEGIAN = "nb" NEPALI = "ne" DUTCH = "nl" OCCITAN = "oc" OROMO = "om" PUNJABI = "pa" PANGASINAN = "pag" KAPAMPANGAN = "pam" POLISH = "pl" DARI = "prs" PASHTO = "ps" PORTUGUESE = "pt" # Only usable as a source language PORTUGUESE_BRAZILIAN = "pt-BR" # Only usable as a target language PORTUGUESE_EUROPEAN = "pt-PT" # Only usable as a target language QUECHUA = "qu" ROMANIAN = "ro" RUSSIAN = "ru" SANSKRIT = "sa" SICILIAN = "scn" SLOVAK = "sk" SLOVENIAN = "sl" ALBANIAN = "sq" SERBIAN = "sr" SESOTHO = "st" SUNDANESE = "su" SWEDISH = "sv" SWAHILI = "sw" TAMIL = "ta" TELUGU = "te" TAJIK = "tg" THAI = "th" TURKMEN = "tk" TAGALOG = "tl" TSWANA = "tn" TURKISH = "tr" TSONGA = "ts" TATAR = "tt" UKRAINIAN = "uk" URDU = "ur" UZBEK = "uz" VIETNAMESE = "vi" WOLOF = "wo" XHOSA = "xh" YIDDISH = "yi" CANTONESE = "yue" CHINESE = "zh" CHINESE_SIMPLIFIED = "zh-Hans" # Only usable as a target language CHINESE_TRADITIONAL = "zh-Hant" # Only usable as a target language ZULU = "zu" class GlossaryLanguagePair: """Information about a pair of languages supported for DeepL glossaries. :param source_lang: The code of the source language. :param target_lang: The code of the target language. """ def __init__(self, source_lang: str, target_lang: str): self._source_lang = source_lang self._target_lang = target_lang @property def source_lang(self) -> str: """Returns the code of the source language.""" return self._source_lang @property def target_lang(self) -> str: """Returns the code of the target language.""" return self._target_lang class MultilingualGlossaryDictionaryEntries: def __init__( self, source_lang: str, target_lang: str, entries: Dict[str, str], ): self._source_lang = source_lang self._target_lang = target_lang self._entries = entries def __str__(self) -> str: return ( "MultilingualGlossaryDictionaryEntries: Source Language " f"{self._source_lang}, Target Language {self._target_lang} " f"Contents: {self._entries}" ) @staticmethod def from_json(json) -> "MultilingualGlossaryDictionaryEntries": """Create MultilingualGlossaryDictionaryEntries from the given API JSON object. """ return MultilingualGlossaryDictionaryEntries( str(json["source_lang"]), str(json["target_lang"]), json["entries"], ) def to_json(self): """Create API JSON object from MultilingualGlossaryDictionaryEntries """ return { "source_lang": self._source_lang, "target_lang": self._target_lang, "entries": self._entries, } @property def source_lang(self) -> str: return self._source_lang @property def target_lang(self) -> str: return self._target_lang @property def entries(self) -> Dict[str, str]: return self._entries class MultilingualGlossaryDictionaryEntriesResponse: def __init__( self, dictionaries: List[MultilingualGlossaryDictionaryEntries] ): self._dictionaries = dictionaries def __str__(self) -> str: return ( "MultilingualGlossaryDictionaryEntriesResponse: " f"Contents {self._dictionaries}" ) @staticmethod def from_json(json) -> "MultilingualGlossaryDictionaryEntriesResponse": """Create MultilingualGlossaryDictionaryEntriesResponse from the given API JSON object. """ glossary_dicts = list(json["dictionaries"]) serialized_dicts = list( map( lambda glossary_dict: MultilingualGlossaryDictionaryEntries.from_json( # noqa: E501 glossary_dict ), glossary_dicts, ) ) return MultilingualGlossaryDictionaryEntriesResponse(serialized_dicts) @property def dictionaries(self) -> List[MultilingualGlossaryDictionaryEntries]: return self._dictionaries class MultilingualGlossaryDictionaryInfo: def __init__(self, source_lang: str, target_lang: str, entry_count: int): self._source_lang = source_lang self._target_lang = target_lang self._entry_count = entry_count @staticmethod def from_json(json) -> "MultilingualGlossaryDictionaryInfo": """Create MultilingualGlossaryDictionaryInfo from the given API JSON object.""" return MultilingualGlossaryDictionaryInfo( str(json["source_lang"]).upper(), str(json["target_lang"]).upper(), int(json["entry_count"]), ) @property def source_lang(self) -> str: return self._source_lang @property def target_lang(self) -> str: return self._target_lang @property def entry_count(self) -> int: return self._entry_count class MultilingualGlossaryInfo: """Information about a multilingual glossary, excluding the entry list. Used by the /v3/glossaries API endpoints :param glossary_id: Unique ID assigned to the glossary. :param name: User-defined name assigned to the glossary. :param creation_time: Timestamp when the glossary was created. :param dictionaries: Dictionaries contained in this glossary. Each dictionary contains its language pair and the number of entries. """ def __init__( self, glossary_id: str, name: str, creation_time: datetime.datetime, dictionaries: List[MultilingualGlossaryDictionaryInfo], ): self._glossary_id = glossary_id self._name = name self._creation_time = creation_time self._dictionaries = dictionaries def __str__(self) -> str: return f'MultilingualGlossary "{self.name}" ({self.glossary_id})' @staticmethod def from_json(json) -> "MultilingualGlossaryInfo": """Create MultilingualGlossaryInfo from the given API JSON object.""" return MultilingualGlossaryInfo( json["glossary_id"], json["name"], parse_timestamp(json["creation_time"]), list( map( lambda entry: MultilingualGlossaryDictionaryInfo.from_json( entry ), json["dictionaries"], ) ), ) @staticmethod def to_json(self) -> dict: """Create API JSON object from MultilingualGlossaryInfo.""" return { "glossary_id": self._glossary_id, "name": self._name, "creation_time": self._creation_time, "dictionaries": self._dictionaries, } @property def glossary_id(self) -> str: return self._glossary_id @property def name(self) -> str: return self._name @property def dictionaries(self) -> List[MultilingualGlossaryDictionaryInfo]: return self._dictionaries @property def creation_time(self) -> datetime.datetime: return self._creation_time class GlossaryInfo: """Information about a glossary, excluding the entry list. GlossaryInfo is compatible with the /v2 glossary endpoints and can only support mono-lingual glossaries (e.g. a glossary with only one source and target language defined). :param glossary_id: Unique ID assigned to the glossary. :param name: User-defined name assigned to the glossary. :param ready: True iff the glossary may be used for translations. :param source_lang: Source language code of the glossary. :param target_lang: Target language code of the glossary. :param creation_time: Timestamp when the glossary was created. :param entry_count: The number of entries contained in the glossary. """ def __init__( self, glossary_id: str, name: str, ready: bool, source_lang: str, target_lang: str, creation_time: datetime.datetime, entry_count: int, ): self._glossary_id = glossary_id self._name = name self._ready = ready self._source_lang = source_lang self._target_lang = target_lang self._creation_time = creation_time self._entry_count = entry_count def __str__(self) -> str: return f'Glossary "{self.name}" ({self.glossary_id})' @staticmethod def from_json(json) -> "GlossaryInfo": """Create GlossaryInfo from the given API JSON object.""" return GlossaryInfo( json["glossary_id"], json["name"], bool(json["ready"]), str(json["source_lang"]).upper(), str(json["target_lang"]).upper(), parse_timestamp(json["creation_time"]), int(json["entry_count"]), ) @property def glossary_id(self) -> str: return self._glossary_id @property def name(self) -> str: return self._name @property def ready(self) -> bool: return self._ready @property def source_lang(self) -> str: return self._source_lang @property def target_lang(self) -> str: return self._target_lang @property def creation_time(self) -> datetime.datetime: return self._creation_time @property def entry_count(self) -> int: return self._entry_count class Formality(Enum): """Options for formality parameter.""" LESS = "less" """Translate using informal language.""" DEFAULT = "default" """Translate using the default formality.""" MORE = "more" """Translate using formal language.""" PREFER_MORE = "prefer_more" """Translate using formal language if the target language supports formality, otherwise use default formality.""" PREFER_LESS = "prefer_less" """Translate using informal language if the target language supports formality, otherwise use default formality.""" def __str__(self): return self.value class SplitSentences(Enum): """Options for split_sentences parameter. Sets whether the translation engine should first split the input into sentences. This is enabled by default. Possible values are: - OFF: no splitting at all, whole input is treated as one sentence. Use this option if the input text is already split into sentences, to prevent the engine from splitting the sentence unintentionally. - ALL: (default) splits on punctuation and on newlines. - NO_NEWLINES: splits on punctuation only, ignoring newlines. """ OFF = "0" ALL = "1" NO_NEWLINES = "nonewlines" DEFAULT = ALL def __str__(self): return self.value class ModelType(Enum): """Options for model_type parameter. Sets whether the translation engine should use a newer model type that offers higher quality translations at the cost of translation time. """ QUALITY_OPTIMIZED = "quality_optimized" LATENCY_OPTIMIZED = "latency_optimized" PREFER_QUALITY_OPTIMIZED = "prefer_quality_optimized" def __str__(self): return self.value class WritingStyle(Enum): """Options for the `style` parameter of the Write API. Sets the style the improved text should be in. Note that currently, only a style OR a tone is supported. When using a style starting with `prefer_`, the style will only be used if the chosen or detected language supports it. """ ACADEMIC = "academic" BUSINESS = "business" CASUAL = "casual" DEFAULT = "default" PREFER_ACADEMIC = "prefer_academic" PREFER_BUSINESS = "prefer_business" PREFER_CASUAL = "prefer_casual" PREFER_SIMPLE = "prefer_simple" SIMPLE = "simple" def __str__(self): return self.value class WritingTone(Enum): """Options for the `tone` parameter of the Write API. Sets the tone the improved text should be in. Note that currently, only a style OR a tone is supported. When using a tone starting with `prefer_`, the tone will only be used if the chosen or detected language supports it. """ CONFIDENT = "confident" DEFAULT = "default" DIPLOMATIC = "diplomatic" ENTHUSIASTIC = "enthusiastic" FRIENDLY = "friendly" PREFER_CONFIDENT = "prefer_confident" PREFER_DIPLOMATIC = "prefer_diplomatic" PREFER_ENTHUSIASTIC = "prefer_enthusiastic" PREFER_FRIENDLY = "prefer_friendly" def __str__(self): return self.value class ConfiguredRules: """Configuration rules for a style rule list. :param dates_and_times: Date and time formatting rules. :param formatting: Text formatting rules. :param numbers: Number formatting rules. :param punctuation: Punctuation rules. :param spelling_and_grammar: Spelling and grammar rules. :param style_and_tone: Style and tone rules. :param vocabulary: Vocabulary rules. """ def __init__( self, dates_and_times: Optional[Dict[str, str]] = None, formatting: Optional[Dict[str, str]] = None, numbers: Optional[Dict[str, str]] = None, punctuation: Optional[Dict[str, str]] = None, spelling_and_grammar: Optional[Dict[str, str]] = None, style_and_tone: Optional[Dict[str, str]] = None, vocabulary: Optional[Dict[str, str]] = None, ): self._dates_and_times = dates_and_times or {} self._formatting = formatting or {} self._numbers = numbers or {} self._punctuation = punctuation or {} self._spelling_and_grammar = spelling_and_grammar or {} self._style_and_tone = style_and_tone or {} self._vocabulary = vocabulary or {} @staticmethod def from_json(json) -> "ConfiguredRules": """Create ConfiguredRules from the given API JSON object.""" if not json: return ConfiguredRules() return ConfiguredRules( dates_and_times=json.get("dates_and_times"), formatting=json.get("formatting"), numbers=json.get("numbers"), punctuation=json.get("punctuation"), spelling_and_grammar=json.get("spelling_and_grammar"), style_and_tone=json.get("style_and_tone"), vocabulary=json.get("vocabulary"), ) @property def dates_and_times(self) -> Dict[str, str]: """Returns date and time formatting rules.""" return self._dates_and_times @property def formatting(self) -> Dict[str, str]: """Returns text formatting rules.""" return self._formatting @property def numbers(self) -> Dict[str, str]: """Returns number formatting rules.""" return self._numbers @property def punctuation(self) -> Dict[str, str]: """Returns punctuation rules.""" return self._punctuation @property def spelling_and_grammar(self) -> Dict[str, str]: """Returns spelling and grammar rules.""" return self._spelling_and_grammar @property def style_and_tone(self) -> Dict[str, str]: """Returns style and tone rules.""" return self._style_and_tone @property def vocabulary(self) -> Dict[str, str]: """Returns vocabulary rules.""" return self._vocabulary class CustomInstruction: """Custom instruction for a style rule. :param label: Label for the custom instruction. :param prompt: Prompt text for the custom instruction. :param source_language: Optional source language code for the custom instruction. """ def __init__( self, label: str, prompt: str, source_language: Optional[str] = None, id: Optional[str] = None, ): self._label = label self._prompt = prompt self._source_language = source_language self._id = id @staticmethod def from_json(json) -> "CustomInstruction": """Create CustomInstruction from the given API JSON object.""" return CustomInstruction( label=json["label"], prompt=json["prompt"], source_language=json.get("source_language"), id=json.get("id"), ) @property def id(self) -> Optional[str]: """Returns the unique ID of the custom instruction.""" return self._id @property def label(self) -> str: """Returns the label of the custom instruction.""" return self._label @property def prompt(self) -> str: """Returns the prompt text of the custom instruction.""" return self._prompt @property def source_language(self) -> Optional[str]: """Returns the source language code, if specified.""" return self._source_language class StyleRuleInfo: """Information about a style rule list. :param style_id: Unique ID assigned to the style rule list. :param name: User-defined name assigned to the style rule list. :param creation_time: Timestamp when the style rule list was created. :param updated_time: Timestamp when the style rule list was last updated. :param language: Language code for the style rule list. :param version: Version number of the style rule list. :param configured_rules: The predefined rules that have been enabled. :param custom_instructions: Optional list of custom instructions. """ def __init__( self, style_id: str, name: str, creation_time: datetime.datetime, updated_time: datetime.datetime, language: str, version: int, configured_rules: Optional[ConfiguredRules] = None, custom_instructions: Optional[List[CustomInstruction]] = None, ): self._style_id = style_id self._name = name self._creation_time = creation_time self._updated_time = updated_time self._language = language self._version = version self._configured_rules = configured_rules self._custom_instructions = custom_instructions def __str__(self) -> str: return f'StyleRule "{self.name}" ({self.style_id})' @staticmethod def from_json(json) -> "StyleRuleInfo": """Create StyleRuleInfo from the given API JSON object.""" configured_rules_data = json.get("configured_rules") configured_rules = None if configured_rules_data: configured_rules = ConfiguredRules.from_json(configured_rules_data) custom_instructions_data = json.get("custom_instructions") custom_instructions = None if custom_instructions_data: custom_instructions = [ CustomInstruction.from_json(instruction) for instruction in custom_instructions_data ] return StyleRuleInfo( style_id=json["style_id"], name=json["name"], creation_time=parse_timestamp(json["creation_time"]), updated_time=parse_timestamp(json["updated_time"]), language=json["language"], version=json["version"], configured_rules=configured_rules, custom_instructions=custom_instructions, ) @property def style_id(self) -> str: """Returns the unique ID of the style rule set.""" return self._style_id @property def name(self) -> str: """Returns the name of the style rule set.""" return self._name @property def creation_time(self) -> datetime.datetime: """Returns the creation timestamp.""" return self._creation_time @property def updated_time(self) -> datetime.datetime: """Returns the last update timestamp.""" return self._updated_time @property def language(self) -> str: """Returns the language code.""" return self._language @property def version(self) -> int: """Returns the version number.""" return self._version @property def configured_rules(self) -> Optional[ConfiguredRules]: """Returns the detailed configuration rules.""" return self._configured_rules @property def custom_instructions(self) -> List[CustomInstruction]: """Returns the list of custom instructions.""" return self._custom_instructions or [] def parse_optional_timestamp( value: Optional[str], ) -> Optional[datetime.datetime]: """Parses an optional API timestamp, returning None if it is absent or in an unrecognised format.""" if not value: return None try: return parse_timestamp(value) except ValueError: # Fall back for timestamps without fractional seconds or without a # timezone offset. try: return datetime.datetime.fromisoformat( value.replace("Z", "+00:00") ) except ValueError: return None class TranslationMemoryInfo: """Information about a translation memory. :param translation_memory_id: Unique ID assigned to the translation memory. :param name: User-defined name assigned to the translation memory. :param source_language: Source language code for the translation memory. :param target_languages: List of target language codes available. :param segment_count: Number of segments stored in the translation memory. :param creation_time: Timestamp when the translation memory was created. :param updated_time: Timestamp when the translation memory was last updated. """ def __init__( self, translation_memory_id: str, name: str, source_language: str, target_languages: List[str], segment_count: int, creation_time: Optional[datetime.datetime] = None, updated_time: Optional[datetime.datetime] = None, ): self._translation_memory_id = translation_memory_id self._name = name self._source_language = source_language self._target_languages = target_languages self._segment_count = segment_count self._creation_time = creation_time self._updated_time = updated_time def __str__(self) -> str: return ( f'TranslationMemory "{self.name}" ' f"({self.translation_memory_id})" ) @staticmethod def from_json(json) -> "TranslationMemoryInfo": """Create TranslationMemoryInfo from the given API JSON object.""" return TranslationMemoryInfo( translation_memory_id=json["translation_memory_id"], name=json["name"], source_language=json["source_language"], target_languages=json.get("target_languages", []), segment_count=json.get("segment_count", 0), creation_time=parse_optional_timestamp(json.get("creation_time")), updated_time=parse_optional_timestamp(json.get("updated_time")), ) @property def translation_memory_id(self) -> str: """Returns the unique ID of the translation memory.""" return self._translation_memory_id @property def name(self) -> str: """Returns the name of the translation memory.""" return self._name @property def source_language(self) -> str: """Returns the source language code.""" return self._source_language @property def target_languages(self) -> List[str]: """Returns the list of target language codes.""" return self._target_languages @property def segment_count(self) -> int: """Returns the number of segments stored.""" return self._segment_count @property def creation_time(self) -> Optional[datetime.datetime]: """Returns the creation timestamp, if provided by the API.""" return self._creation_time @property def updated_time(self) -> Optional[datetime.datetime]: """Returns the last update timestamp, if provided by the API.""" return self._updated_time class TranslationMemoryTargetSegment: """A target-language translation attached to a source segment. :param target_segment_id: Unique ID assigned to the target segment. :param target_language: Target language code of the translation. :param target_text: The translated text. :param creation_time: Timestamp when the target segment was created. :param updated_time: Timestamp when the target segment was last updated. :param last_used_time: Timestamp when the target segment was last used. """ def __init__( self, target_segment_id: str, target_language: str, target_text: str, creation_time: Optional[datetime.datetime] = None, updated_time: Optional[datetime.datetime] = None, last_used_time: Optional[datetime.datetime] = None, ): self._target_segment_id = target_segment_id self._target_language = target_language self._target_text = target_text self._creation_time = creation_time self._updated_time = updated_time self._last_used_time = last_used_time def __str__(self) -> str: return f"{self.target_language}: {self.target_text}" @staticmethod def from_json(json) -> "TranslationMemoryTargetSegment": """Create TranslationMemoryTargetSegment from the given API JSON object.""" return TranslationMemoryTargetSegment( target_segment_id=json["target_segment_id"], target_language=json["target_language"], target_text=json["target_text"], creation_time=parse_optional_timestamp(json.get("creation_time")), updated_time=parse_optional_timestamp(json.get("updated_time")), last_used_time=parse_optional_timestamp( json.get("last_used_time") ), ) @property def target_segment_id(self) -> str: """Returns the unique ID of the target segment.""" return self._target_segment_id @property def target_language(self) -> str: """Returns the target language code.""" return self._target_language @property def target_text(self) -> str: """Returns the translated text.""" return self._target_text @property def creation_time(self) -> Optional[datetime.datetime]: """Returns the creation timestamp, if provided by the API.""" return self._creation_time @property def updated_time(self) -> Optional[datetime.datetime]: """Returns the last update timestamp, if provided by the API.""" return self._updated_time @property def last_used_time(self) -> Optional[datetime.datetime]: """Returns the last used timestamp, if provided by the API.""" return self._last_used_time class TranslationMemorySegment: """A source segment of a translation memory and its translations. :param source_segment_id: Unique ID assigned to the source segment. :param source_text: The source text. :param targets: Translations of the source text, one per target language. :param creation_time: Timestamp when the source segment was created. :param updated_time: Timestamp when the source segment was last updated. :param last_used_time: Timestamp when the source segment was last used. """ def __init__( self, source_segment_id: str, source_text: str, targets: List[TranslationMemoryTargetSegment], creation_time: Optional[datetime.datetime] = None, updated_time: Optional[datetime.datetime] = None, last_used_time: Optional[datetime.datetime] = None, ): self._source_segment_id = source_segment_id self._source_text = source_text self._targets = targets self._creation_time = creation_time self._updated_time = updated_time self._last_used_time = last_used_time def __str__(self) -> str: return f"TranslationMemorySegment ({self.source_segment_id})" @staticmethod def from_json(json) -> "TranslationMemorySegment": """Create TranslationMemorySegment from the given API JSON object.""" return TranslationMemorySegment( source_segment_id=json["source_segment_id"], source_text=json["source_text"], targets=[ TranslationMemoryTargetSegment.from_json(target) for target in json.get("targets", []) ], creation_time=parse_optional_timestamp(json.get("creation_time")), updated_time=parse_optional_timestamp(json.get("updated_time")), last_used_time=parse_optional_timestamp( json.get("last_used_time") ), ) @property def source_segment_id(self) -> str: """Returns the unique ID of the source segment.""" return self._source_segment_id @property def source_text(self) -> str: """Returns the source text.""" return self._source_text @property def targets(self) -> List[TranslationMemoryTargetSegment]: """Returns the translations of the source text.""" return self._targets @property def creation_time(self) -> Optional[datetime.datetime]: """Returns the creation timestamp, if provided by the API.""" return self._creation_time @property def updated_time(self) -> Optional[datetime.datetime]: """Returns the last update timestamp, if provided by the API.""" return self._updated_time @property def last_used_time(self) -> Optional[datetime.datetime]: """Returns the last used timestamp, if provided by the API.""" return self._last_used_time class TranslationMemorySegments: """One page of translation memory segments. :param segments: The segments in this page. :param segment_count: Total number of segments stored in the translation memory. This is translation-memory-level metadata and is not reduced by a text filter. :param next_page_cursor: Opaque cursor to pass as page_cursor to fetch the next page, or None if this is the last page. """ def __init__( self, segments: List[TranslationMemorySegment], segment_count: int, next_page_cursor: Optional[str] = None, ): self._segments = segments self._segment_count = segment_count self._next_page_cursor = next_page_cursor def __iter__(self): return iter(self._segments) def __len__(self) -> int: return len(self._segments) @staticmethod def from_json(json) -> "TranslationMemorySegments": """Create TranslationMemorySegments from the given API JSON object.""" return TranslationMemorySegments( segments=[ TranslationMemorySegment.from_json(segment) for segment in json.get("segments", []) ], segment_count=json.get("segment_count", 0), next_page_cursor=json.get("next_page_cursor"), ) @property def segments(self) -> List[TranslationMemorySegment]: """Returns the segments in this page.""" return self._segments @property def segment_count(self) -> int: """Returns the total number of segments in the translation memory.""" return self._segment_count @property def next_page_cursor(self) -> Optional[str]: """Returns the cursor for the next page, or None on the last page.""" return self._next_page_cursor class TranslationMemoryImport: """A newly created translation memory import job. The TMX file must be uploaded to upload_url before expires_at; processing starts automatically once the upload is detected. :param job_id: Unique ID assigned to the import job. :param upload_url: URL to upload the TMX file to. :param expires_at: Timestamp after which the upload URL is no longer valid. """ def __init__( self, job_id: str, upload_url: str, expires_at: Optional[datetime.datetime] = None, ): self._job_id = job_id self._upload_url = upload_url self._expires_at = expires_at def __str__(self) -> str: return f"TranslationMemoryImport ({self.job_id})" @staticmethod def from_json(json) -> "TranslationMemoryImport": """Create TranslationMemoryImport from the given API JSON object.""" return TranslationMemoryImport( job_id=json["job_id"], upload_url=json["upload_url"], expires_at=parse_optional_timestamp(json.get("expires_at")), ) @property def job_id(self) -> str: """Returns the unique ID of the import job.""" return self._job_id @property def upload_url(self) -> str: """Returns the URL to upload the TMX file to.""" return self._upload_url @property def expires_at(self) -> Optional[datetime.datetime]: """Returns the expiry timestamp of the upload URL.""" return self._expires_at class TranslationMemoryExport: """A translation memory export job. :param job_id: Unique ID assigned to the export job. :param translation_memory_id: ID of the translation memory being exported. :param reused_existing: True if the API reused a previously completed export instead of starting a new one. """ def __init__( self, job_id: str, translation_memory_id: Optional[str] = None, reused_existing: bool = False, ): self._job_id = job_id self._translation_memory_id = translation_memory_id self._reused_existing = reused_existing def __str__(self) -> str: return f"TranslationMemoryExport ({self.job_id})" @staticmethod def from_json(json, reused_existing: bool = False): """Create TranslationMemoryExport from the given API JSON object.""" parameters = json.get("parameters") or {} return TranslationMemoryExport( job_id=json["job_id"], translation_memory_id=parameters.get("translation_memory_id"), reused_existing=reused_existing, ) @property def job_id(self) -> str: """Returns the unique ID of the export job.""" return self._job_id @property def translation_memory_id(self) -> Optional[str]: """Returns the ID of the translation memory being exported.""" return self._translation_memory_id @property def reused_existing(self) -> bool: """Returns True if a previously completed export was reused.""" return self._reused_existing class TranslationMemoryJobResult: """The outcome of a translation memory import or export job. :param status: Job status, one of "awaiting_input", "processing", "completed", "downloaded", "failed" or "expired". :param required_action: Human-readable action the caller must take, set while the job is waiting on the caller. :param download_url: Download URL of the exported TMX file, set once an export completes. :param expires_at: Timestamp after which the download URL is no longer valid. :param error_message: Error description, set when the job failed. :param translation_memory_id: ID of the translation memory created by a completed import. :param skipped_segment_count: Number of segments an import skipped. """ STATUS_AWAITING_INPUT = "awaiting_input" STATUS_PROCESSING = "processing" STATUS_COMPLETED = "completed" STATUS_DOWNLOADED = "downloaded" STATUS_FAILED = "failed" STATUS_EXPIRED = "expired" def __init__( self, status: str, required_action: Optional[str] = None, download_url: Optional[str] = None, expires_at: Optional[datetime.datetime] = None, error_message: Optional[str] = None, translation_memory_id: Optional[str] = None, skipped_segment_count: Optional[int] = None, ): self._status = status self._required_action = required_action self._download_url = download_url self._expires_at = expires_at self._error_message = error_message self._translation_memory_id = translation_memory_id self._skipped_segment_count = skipped_segment_count def __str__(self) -> str: return f"TranslationMemoryJobResult ({self.status})" @staticmethod def from_json(json) -> "TranslationMemoryJobResult": """Create TranslationMemoryJobResult from the given API JSON object.""" status_metadata = json.get("status_metadata") or {} error = json.get("error") or {} return TranslationMemoryJobResult( status=json.get("status", ""), required_action=status_metadata.get("required_action"), download_url=json.get("download_url"), expires_at=parse_optional_timestamp(json.get("expires_at")), error_message=error.get("message"), translation_memory_id=json.get("translation_memory_id"), skipped_segment_count=json.get("skipped_segment_count"), ) @property def status(self) -> str: """Returns the job status.""" return self._status @property def done(self) -> bool: """True if the job has finished, successfully or not.""" return self._status in ( self.STATUS_COMPLETED, self.STATUS_DOWNLOADED, self.STATUS_FAILED, self.STATUS_EXPIRED, ) @property def ok(self) -> bool: """False if the job failed or expired.""" return self._status not in (self.STATUS_FAILED, self.STATUS_EXPIRED) @property def required_action(self) -> Optional[str]: """Returns the action the caller must take, if any.""" return self._required_action @property def download_url(self) -> Optional[str]: """Returns the download URL of a completed export.""" return self._download_url @property def expires_at(self) -> Optional[datetime.datetime]: """Returns the expiry timestamp of the download URL.""" return self._expires_at @property def error_message(self) -> Optional[str]: """Returns the error description if the job failed.""" return self._error_message @property def translation_memory_id(self) -> Optional[str]: """Returns the ID of the translation memory a completed import created.""" return self._translation_memory_id @property def skipped_segment_count(self) -> Optional[int]: """Returns the number of segments an import skipped.""" return self._skipped_segment_count class TranslationMemoryJob: """Status of a translation memory import or export job. :param job_id: Unique ID assigned to the job. :param operation: Either "import" or "export". :param results: Results of the job; the API returns exactly one. :param product: Product the job belongs to, always "translation_memory". :param creation_time: Timestamp when the job was created. :param updated_time: Timestamp when the job was last updated. :param translation_memory_id: ID of the translation memory an export job reads from. :param display_name: Display name an import job assigns to the new translation memory. :param source_content_type: MIME type declared for an import job's file. :param source_content_length: Size in bytes declared for an import job's file. """ OPERATION_IMPORT = "import" OPERATION_EXPORT = "export" def __init__( self, job_id: str, operation: str, results: List[TranslationMemoryJobResult], product: str = "translation_memory", creation_time: Optional[datetime.datetime] = None, updated_time: Optional[datetime.datetime] = None, translation_memory_id: Optional[str] = None, display_name: Optional[str] = None, source_content_type: Optional[str] = None, source_content_length: Optional[int] = None, ): self._job_id = job_id self._operation = operation self._results = results self._product = product self._creation_time = creation_time self._updated_time = updated_time self._translation_memory_id = translation_memory_id self._display_name = display_name self._source_content_type = source_content_type self._source_content_length = source_content_length def __str__(self) -> str: return ( f"TranslationMemoryJob {self.operation} ({self.job_id}): " f"{self.status}" ) @staticmethod def from_json(json) -> "TranslationMemoryJob": """Create TranslationMemoryJob from the given API JSON object.""" parameters = json.get("parameters") or {} source_file = json.get("source_file") or {} return TranslationMemoryJob( job_id=json["job_id"], operation=json.get("operation", ""), results=[ TranslationMemoryJobResult.from_json(result) for result in json.get("results", []) ], product=json.get("product", "translation_memory"), creation_time=parse_optional_timestamp(json.get("creation_time")), updated_time=parse_optional_timestamp(json.get("updated_time")), translation_memory_id=parameters.get("translation_memory_id"), display_name=parameters.get("display_name"), source_content_type=source_file.get("content_type"), source_content_length=source_file.get("content_length"), ) @property def job_id(self) -> str: """Returns the unique ID of the job.""" return self._job_id @property def operation(self) -> str: """Returns the job operation, "import" or "export".""" return self._operation @property def product(self) -> str: """Returns the product the job belongs to.""" return self._product @property def results(self) -> List[TranslationMemoryJobResult]: """Returns the results of the job.""" return self._results @property def result(self) -> Optional[TranslationMemoryJobResult]: """Returns the single result of the job, or None if absent.""" return self._results[0] if self._results else None @property def status(self) -> Optional[str]: """Returns the status of the job result.""" return self.result.status if self.result else None @property def done(self) -> bool: """True if the job has finished, successfully or not.""" return self.result.done if self.result else False @property def ok(self) -> bool: """False if the job failed or expired.""" return self.result.ok if self.result else True @property def creation_time(self) -> Optional[datetime.datetime]: """Returns the creation timestamp.""" return self._creation_time @property def updated_time(self) -> Optional[datetime.datetime]: """Returns the last update timestamp.""" return self._updated_time @property def translation_memory_id(self) -> Optional[str]: """Returns the translation memory an export job reads from.""" return self._translation_memory_id @property def display_name(self) -> Optional[str]: """Returns the display name an import job assigns.""" return self._display_name @property def source_content_type(self) -> Optional[str]: """Returns the MIME type declared for an import job's file.""" return self._source_content_type @property def source_content_length(self) -> Optional[int]: """Returns the size in bytes declared for an import job's file.""" return self._source_content_length DeepL-deepl-python-df50df2/deepl/deepl_client.py000066400000000000000000001577741523706276600217420ustar00rootroot00000000000000# Copyright 2025 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from deepl.api_data import ( CustomInstruction, MultilingualGlossaryDictionaryEntries, MultilingualGlossaryDictionaryEntriesResponse, MultilingualGlossaryDictionaryInfo, MultilingualGlossaryInfo, Language, WriteResult, StyleRuleInfo, TranslationMemoryExport, TranslationMemoryImport, TranslationMemoryInfo, TranslationMemoryJob, TranslationMemorySegments, ) from deepl.exceptions import DeepLException from deepl.translator import Translator from deepl import util import os import pathlib import requests # type: ignore import time import urllib.parse from typing import ( Any, BinaryIO, Dict, Iterable, List, Optional, TextIO, Union, ) class DeepLClient(Translator): def __init__( self, auth_key: str, *, server_url: Optional[str] = None, proxy: Union[Dict, str, None] = None, send_platform_info: bool = True, verify_ssl: Union[bool, str, None] = None, skip_language_check: bool = False, ): super().__init__( auth_key, server_url=server_url, proxy=proxy, send_platform_info=send_platform_info, verify_ssl=verify_ssl, skip_language_check=skip_language_check, ) def rephrase_text( self, text: Union[str, Iterable[str]], *, target_lang: Union[None, str, Language] = None, style: Optional[str] = None, tone: Optional[str] = None, ) -> Union[WriteResult, List[WriteResult]]: """Improve the text(s) and optionally convert them to the variant of the `target_lang` (requires source lang to match target_lang, excluding variants). :param text: Text to improve. :type text: UTF-8 :class:`str`; string sequence (list, tuple, iterator, generator) :param target_lang: language code the final text should be in, for example "DE", "EN-US", "FR". :param style: Writing style to be used for the improvement. Either style OR tone can be used. :param tone: Tone to be used for the improvement. Either style OR tone can be used. :return: List of WriteResult objects containing results, unless input text was one string, then a single WriteResult object is returned. """ if isinstance(text, str): if len(text) == 0: raise ValueError("text must not be empty") text = [text] multi_input = False elif hasattr(text, "__iter__"): multi_input = True text = list(text) else: raise TypeError( "text parameter must be a string or an iterable of strings" ) request_data: dict = {"text": text} if target_lang: request_data["target_lang"] = target_lang if style: request_data["writing_style"] = style if tone: request_data["tone"] = tone status, content, json = self._api_call( "v2/write/rephrase", json=request_data ) self._raise_for_status(status, content, json) improvements = ( json.get("improvements", []) if (json and isinstance(json, dict)) else [] ) output = [] for improvement in improvements: text = improvement.get("text", "") if improvement else "" detected_source_language = ( improvement.get("detected_source_language", "") if improvement else "" ) target_language = ( improvement.get("target_language", "") if improvement else "" ) output.append( WriteResult(text, detected_source_language, target_language) ) return output if multi_input else output[0] def correct_text( self, text: Union[str, Iterable[str]], *, target_lang: Union[None, str, Language] = None, ) -> Union[WriteResult, List[WriteResult]]: """Fix spelling and grammar errors in the text(s). Unlike `rephrase_text`, this applies a minimal-change correction pass and does not rewrite the text for style or tone. :param text: Text to correct. :type text: UTF-8 :class:`str`; string sequence (list, tuple, iterator, generator) :param target_lang: language code the final text should be in, for example "DE", "EN-US", "FR". :return: List of WriteResult objects containing results, unless input text was one string, then a single WriteResult object is returned. """ if isinstance(text, str): if len(text) == 0: raise ValueError("text must not be empty") text = [text] multi_input = False elif hasattr(text, "__iter__"): multi_input = True text = list(text) else: raise TypeError( "text parameter must be a string or an iterable of strings" ) request_data: dict = {"text": text} if target_lang: request_data["target_lang"] = target_lang status, content, json = self._api_call( "v2/write/correct", json=request_data ) self._raise_for_status(status, content, json) improvements = ( json.get("improvements", []) if (json and isinstance(json, dict)) else [] ) output = [] for improvement in improvements: text = improvement.get("text", "") if improvement else "" detected_source_language = ( improvement.get("detected_source_language", "") if improvement else "" ) target_language = ( improvement.get("target_language", "") if improvement else "" ) output.append( WriteResult(text, detected_source_language, target_language) ) return output if multi_input else output[0] def create_multilingual_glossary( self, name: str, glossary_dicts: List[MultilingualGlossaryDictionaryEntries], ) -> MultilingualGlossaryInfo: """Creates a glossary with given name with all of the specified dictionaries, each with their own language pair and entries. The glossary may be used in the translate_text functions. The available glossary language pairs can be queried using get_glossary_languages(). Glossaries apply to languages, not specific language variants. A glossary for a language applies to any variant of that language: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function requires the glossary entries for each dictionary to be provided as a dictionary of source-target terms. To create a glossary from a CSV file downloaded from the DeepL website, see create_glossary_from_csv(). :param name: user-defined name to attach to glossary. :param dictionaries: a list of MultilingualGlossaryDictionaryEntries which each contains entries for a particular language pair :return: GlossaryInfo containing information about created glossary. :raises ValueError: If the glossary name is empty, or entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ if any(map(lambda d: not d.entries, glossary_dicts)): raise ValueError("glossary entries must not be empty") return self._create_multilingual_glossary(name, glossary_dicts) def create_multilingual_glossary_from_csv( self, name: str, source_lang: str, target_lang: str, csv_data: Union[TextIO, BinaryIO, str, bytes, Any], ) -> MultilingualGlossaryInfo: """Creates a glossary with given name for the source and target languages, containing the entries in the given CSV data. The glossary may be used in the translate_text functions. The available glossary language pairs can be queried using get_glossary_languages(). Glossaries apply to languages, not specific language variants. A glossary for a language applies to any variant of that language: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function allows you to upload a glossary CSV file that you have downloaded from the DeepL website. Information about the expected CSV format can be found in the API documentation: https://developers.deepl.com/docs/api-reference/glossaries#csv-comma-separated-values # noqa :param name: user-defined name to attach to glossary. :param source_lang: Language of source entries. :param target_lang: Language of target entries. :param csv_data: CSV data containing glossary entries, either as a file-like object or string or bytes containing file content. :return: GlossaryInfo containing information about created glossary. :raises ValueError: If the glossary name is empty, or entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ entries = util.convert_csv_to_dict(csv_data) dictionaries = [ MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ) ] return self._create_multilingual_glossary(name, dictionaries) def _create_multilingual_glossary( self, name: str, glossary_dicts: List[MultilingualGlossaryDictionaryEntries], ) -> MultilingualGlossaryInfo: if not name: raise ValueError("glossary name must not be empty") req_glossary_dicts = [] # glossaries are only supported for base language types for glossary_dict in glossary_dicts: req_glossary_dict = { "source_lang": Language.remove_regional_variant( glossary_dict.source_lang ), "target_lang": Language.remove_regional_variant( glossary_dict.target_lang ), "entries": util.convert_dict_to_tsv(glossary_dict.entries), "entries_format": "tsv", } req_glossary_dicts.append(req_glossary_dict) request_data = { "name": name, "dictionaries": req_glossary_dicts, } status, content, json = self._api_call( "v3/glossaries", json=request_data ) self._raise_for_status(status, content, json, glossary=True) return MultilingualGlossaryInfo.from_json(json) def update_multilingual_glossary_name( self, glossary: Union[str, MultilingualGlossaryInfo], name: str, ) -> MultilingualGlossaryInfo: """Updates the name of a glossary with the provided name. :param glossary: GlossaryInfo or ID of glossary to update. :param name: The new name of the glossary :return: MultilingualGlossaryInfo containing information about updated glossary. :raises ValueError: If the name is empty or invalid. :raises DeepLException: If the glossary cannot be found. """ if not name: raise ValueError("glossary name must not be empty") if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id request_data = {"name": name} status, content, json = self._api_call( f"v3/glossaries/{glossary}", method="PATCH", json=request_data ) self._raise_for_status(status, content, json, glossary=True) return MultilingualGlossaryInfo.from_json(json) def update_multilingual_glossary_dictionary( self, glossary: Union[str, MultilingualGlossaryInfo], glossary_dict: MultilingualGlossaryDictionaryEntries, ) -> MultilingualGlossaryInfo: """Updates or creates a glossary dictionary with given glossary dictionary with its entries for the source and target languages. Either updates the glossary's entries if they exist for the given language pair, or adds any new ones to the dictionary if not. The available glossary language pairs can be queried using get_glossary_languages(). Glossaries apply to languages, not specific language variants. A glossary for a language applies to any variant of that language: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function requires the glossary entries to be provided as a dictionary of source-target terms. To create a glossary from a CSV file downloaded from the DeepL website, see create_glossary_from_csv(). :param glossary: GlossaryInfo or ID of glossary to update. :param glossary_dict: The new or updated glossary dictionary :return: MultilingualGlossaryInfo containing information about updated glossary. :raises ValueError: If the glossary entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ if not glossary_dict or not glossary_dict.entries: raise ValueError("glossary entries must not be empty") if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id return self._update_multilingual_glossary(glossary, [glossary_dict]) def update_multilingual_glossary_dictionary_from_csv( self, glossary: Union[str, MultilingualGlossaryInfo], source_lang: str, target_lang: str, csv_data: Union[TextIO, BinaryIO, str, bytes, Any], ) -> MultilingualGlossaryInfo: """Updates or creates a glossary dictionary with given entries in CSV formatting for the source and target languages. Either updates entries if they exist for the given language pair, or adds new ones to the dictionary if not. The available glossary language pairs can be queried using get_glossary_languages(). Glossaries apply to languages, not specific language variants. A glossary for a language applies to any variant of that language: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function allows you to upload a glossary CSV file that you have downloaded from the DeepL website. Information about the expected CSV format can be found in the API documentation: https://www.deepl.com/docs-api/managing-glossaries/supported-glossary-formats/ # noqa :param glossary: MultilingualGlossaryInfo or ID of glossary to update. :param source_lang: Language of source entries. :param target_lang: Language of target entries. :param csv_data: CSV data containing glossary entries, either as a file-like object or string or bytes containing file content. :return: MultilingualGlossaryInfo containing information about updated glossary. :raises ValueError: If the glossary entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ entries = util.convert_csv_to_dict(csv_data) if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id dictionaries = [ MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ) ] return self._update_multilingual_glossary(glossary, dictionaries) def _update_multilingual_glossary( self, glossary_id: str, dictionaries: List[MultilingualGlossaryDictionaryEntries], ) -> MultilingualGlossaryInfo: if not glossary_id: raise ValueError("glossary id must not be empty") req_glossary_dicts = [] # glossaries are only supported for base language types for glossary_dict in dictionaries: req_glossary_dict = { "source_lang": Language.remove_regional_variant( glossary_dict.source_lang ), "target_lang": Language.remove_regional_variant( glossary_dict.target_lang ), "entries": util.convert_dict_to_tsv(glossary_dict.entries), "entries_format": "tsv", } req_glossary_dicts.append(req_glossary_dict) request_data = {} if dictionaries: request_data["dictionaries"] = req_glossary_dicts status, content, json = self._api_call( f"v3/glossaries/{glossary_id}", method="PATCH", json=request_data ) self._raise_for_status(status, content, json, glossary=True) return MultilingualGlossaryInfo.from_json(json) def replace_multilingual_glossary_dictionary( self, glossary: Union[str, MultilingualGlossaryInfo], glossary_dict: MultilingualGlossaryDictionaryEntries, ) -> MultilingualGlossaryDictionaryInfo: """Replaces a glossary dictionary with given entries for the source and target languages. The available glossary language pairs can be queried using get_glossary_languages(). Glossaries apply to languages, not specific language variants. A glossary for a language applies to any variant of that language: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function requires the glossary entries to be provided as a dictionary of source-target terms. To create a glossary from a CSV file downloaded from the DeepL website, see create_glossary_from_csv(). :param glossary: GlossaryInfo or ID of glossary to update. :param glossary_dict: The new glossary dictionary :return: MultilingualGlossaryDictionaryInfo containing information about the updated dictionary. :raises ValueError: If the glossary entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ if not glossary_dict or not glossary_dict.entries: raise ValueError("glossary entries must not be empty") if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id return self._replace_multilingual_glossary_dictionary( glossary, glossary_dict.source_lang, glossary_dict.target_lang, glossary_dict.entries, ) def replace_multilingual_glossary_dictionary_from_csv( self, glossary: Union[str, MultilingualGlossaryInfo], source_lang: str, target_lang: str, csv_data: Union[TextIO, BinaryIO, str, bytes, Any], ) -> MultilingualGlossaryDictionaryInfo: """Replaces a glossary dictionary with given CSV formatted entries for the source and target languages. The available glossary language pairs can be queried using get_glossary_languages(). Glossaries apply to languages, not specific language variants. A glossary for a language applies to any variant of that language: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function allows you to upload a glossary CSV file that you have downloaded from the DeepL website. Information about the expected CSV format can be found in the API documentation: https://www.deepl.com/docs-api/managing-glossaries/supported-glossary-formats/ # noqa :param glossary: MultilingualGlossaryInfo or ID of glossary to update. :param source_lang: Language of source entries. :param target_lang: Language of target entries. :param csv_data: CSV data containing glossary entries, either as a file-like object or string or bytes containing file content. :return: MultilingualGlossaryDictionaryInfo containing information about updated dictionary. :raises ValueError: If the glossary entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ entries = util.convert_csv_to_dict(csv_data) if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id return self._replace_multilingual_glossary_dictionary( glossary, source_lang, target_lang, entries ) def _replace_multilingual_glossary_dictionary( self, glossary_id: str, source_lang: str, target_lang: str, entries: Dict[str, str], ) -> MultilingualGlossaryDictionaryInfo: if not glossary_id: raise ValueError("glossary id must not be empty") # glossaries are only supported for base language types source_lang = Language.remove_regional_variant(source_lang) target_lang = Language.remove_regional_variant(target_lang) request_data = { "source_lang": source_lang, "target_lang": target_lang, "entries": util.convert_dict_to_tsv(entries), "entries_format": "tsv", } status, content, json = self._api_call( f"v3/glossaries/{glossary_id}/dictionaries", method="PUT", json=request_data, ) self._raise_for_status(status, content, json, glossary=True) return MultilingualGlossaryDictionaryInfo.from_json(json) def get_multilingual_glossary( self, glossary_id: str ) -> MultilingualGlossaryInfo: """Retrieves MultilingualGlossaryInfo for the glossary with specified ID. :param glossary_id: ID of glossary to retrieve. :return: MultilingualGlossaryInfo with information about specified glossary. :raises GlossaryNotFoundException: If no glossary with given ID is found. """ status, content, json = self._api_call( f"v3/glossaries/{glossary_id}", method="GET" ) self._raise_for_status(status, content, json, glossary=True) return MultilingualGlossaryInfo.from_json(json) def list_multilingual_glossaries(self) -> List[MultilingualGlossaryInfo]: """Retrieves a list of MultilingualGlossaryInfo for all available glossaries. :return: list of MultilingualGlossaryInfos for all available glossaries. """ status, content, json = self._api_call("v3/glossaries", method="GET") self._raise_for_status(status, content, json, glossary=True) glossaries = ( json.get("glossaries", []) if (json and isinstance(json, dict)) else [] ) return [ MultilingualGlossaryInfo.from_json(glossary) for glossary in glossaries ] def get_multilingual_glossary_entries( self, glossary: Union[str, MultilingualGlossaryInfo], source_lang: str, target_lang: str, ) -> MultilingualGlossaryDictionaryEntriesResponse: """Retrieves the entries for a given source and target language in the specified glossary. :param glossary: MultilingualGlossaryInfo or ID of glossary to retrieve. :param source_lang: Language of source terms. :param target_lang: Language of target terms. :return: MultilingualGlossaryDictionaryEntriesResponse object containing the entries. :raises GlossaryNotFoundException: If no glossary with given ID is found. :raises DeepLException: If the glossary could not be retrieved in the right format. """ if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id source_lang = Language.remove_regional_variant(source_lang) target_lang = Language.remove_regional_variant(target_lang) status, content, json = self._api_call( f"v3/glossaries/{glossary}/entries?source_lang={source_lang}&target_lang={target_lang}", # noqa: E501 method="GET", ) self._raise_for_status(status, content, json, glossary=True) return MultilingualGlossaryDictionaryEntriesResponse.from_json(json) def delete_multilingual_glossary( self, glossary: Union[str, MultilingualGlossaryInfo] ) -> None: """Deletes specified glossary. :param glossary: MultilingualGlossaryInfo or ID of glossary to delete. :raises GlossaryNotFoundException: If no glossary with given ID is found. """ if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id status, content, json = self._api_call( f"v3/glossaries/{glossary}", method="DELETE", ) self._raise_for_status(status, content, json, glossary=True) def delete_multilingual_glossary_dictionary( self, glossary: Union[str, MultilingualGlossaryInfo], dictionary: Optional[MultilingualGlossaryDictionaryInfo] = None, source_lang: Optional[str] = None, target_lang: Optional[str] = None, ) -> None: """Deletes specified glossary dictionary. :param glossary: GlossaryInfo or ID of glossary containing the dictionary to delete :param dictionary: The dictionary to delete. Either the MultilingualGlossaryDictionaryInfo or both the source_lang and target_lang can be provided to identify the dictionary. However, if both are provided, the dictionary takes precendence over source_lang and target_lang. :param source_lang: Optional parameter representing the source language of the dictionary :param target_lang: Optional parameter representing the target language of the dictionary :raises GlossaryNotFoundException: If no glossary with given ID is found. :raises ValueError: If the dictionary or both the source_lang and target_lang were not provided """ if isinstance(glossary, MultilingualGlossaryInfo): glossary = glossary.glossary_id if not dictionary and not (source_lang and target_lang): raise ValueError( "must provide dictionary or both source_lang and target_lang" ) if dictionary: source_lang = dictionary.source_lang target_lang = dictionary.target_lang req_url = f"v3/glossaries/{glossary}/dictionaries?source_lang={source_lang}&target_lang={target_lang}" # noqa: E501 status, content, json = self._api_call( req_url, method="DELETE", ) self._raise_for_status(status, content, json, glossary=True) def get_all_style_rules( self, page: Optional[int] = None, page_size: Optional[int] = None, detailed: Optional[bool] = None, ) -> List[StyleRuleInfo]: """Retrieves a list of StyleRuleInfo for all available style rules. :param page: Page number for pagination, 0-indexed (optional). :param page_size: Number of items per page (optional). :param detailed: Whether to include detailed configuration rules (optional). :return: List of StyleRuleInfo objects for all available style rules. """ params = {} if page is not None: params["page"] = str(page) if page_size is not None: params["page_size"] = str(page_size) if detailed is not None: params["detailed"] = str(detailed).lower() endpoint = "v3/style_rules" if params: query_string = urllib.parse.urlencode(params) endpoint += f"?{query_string}" status, content, json = self._api_call(endpoint, method="GET") self._raise_for_status(status, content, json) style_rules = ( json.get("style_rules", []) if (json and isinstance(json, dict)) else [] ) return [ StyleRuleInfo.from_json(style_rule) for style_rule in style_rules ] def create_style_rule( self, name: str, language: str, configured_rules: Optional[dict] = None, custom_instructions: Optional[List[dict]] = None, ) -> StyleRuleInfo: """Creates a new style rule. :param name: Name for the style rule. :param language: Language code for the style rule. :param configured_rules: Optional dict of configured rules. :param custom_instructions: Optional list of custom instruction dicts. :return: StyleRuleInfo for the created style rule. """ if not name: raise ValueError("name must not be empty") if not language: raise ValueError("language must not be empty") request_data: Dict[str, Any] = {"name": name, "language": language} if configured_rules is not None: request_data["configured_rules"] = configured_rules if custom_instructions is not None: request_data["custom_instructions"] = custom_instructions status, content, json = self._api_call( "v3/style_rules", json=request_data ) self._raise_for_status(status, content, json) return StyleRuleInfo.from_json(json) def get_style_rule( self, style_rule: Union[str, StyleRuleInfo], ) -> StyleRuleInfo: """Retrieves a single style rule by ID. :param style_rule: Style rule ID string or StyleRuleInfo object. :return: StyleRuleInfo for the requested style rule. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") status, content, json = self._api_call( f"v3/style_rules/{style_rule}", method="GET" ) self._raise_for_status(status, content, json) return StyleRuleInfo.from_json(json) def update_style_rule_name( self, style_rule: Union[str, StyleRuleInfo], name: str, ) -> StyleRuleInfo: """Updates the name of a style rule. :param style_rule: Style rule ID string or StyleRuleInfo object. :param name: New name for the style rule. :return: Updated StyleRuleInfo. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") if not name: raise ValueError("name must not be empty") request_data = {"name": name} status, content, json = self._api_call( f"v3/style_rules/{style_rule}", method="PATCH", json=request_data ) self._raise_for_status(status, content, json) return StyleRuleInfo.from_json(json) def delete_style_rule( self, style_rule: Union[str, StyleRuleInfo], ) -> None: """Deletes a style rule. :param style_rule: Style rule ID string or StyleRuleInfo object. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") status, content, json = self._api_call( f"v3/style_rules/{style_rule}", method="DELETE" ) self._raise_for_status(status, content, json) def update_style_rule_configured_rules( self, style_rule: Union[str, StyleRuleInfo], configured_rules: dict, ) -> StyleRuleInfo: """Updates the configured rules of a style rule. :param style_rule: Style rule ID string or StyleRuleInfo object. :param configured_rules: Dict of configured rules to set. :return: Updated StyleRuleInfo. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") status, content, json = self._api_call( f"v3/style_rules/{style_rule}/configured_rules", method="PUT", json=configured_rules, ) self._raise_for_status(status, content, json) return StyleRuleInfo.from_json(json) def create_style_rule_custom_instruction( self, style_rule: Union[str, StyleRuleInfo], label: str, prompt: str, source_language: Optional[str] = None, ) -> CustomInstruction: """Creates a custom instruction for a style rule. :param style_rule: Style rule ID string or StyleRuleInfo object. :param label: Label for the custom instruction. :param prompt: Prompt text for the custom instruction. :param source_language: Optional source language code. :return: Created CustomInstruction. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") if not label: raise ValueError("label must not be empty") if not prompt: raise ValueError("prompt must not be empty") request_data = {"label": label, "prompt": prompt} if source_language is not None: request_data["source_language"] = source_language status, content, json = self._api_call( f"v3/style_rules/{style_rule}/custom_instructions", json=request_data, ) self._raise_for_status(status, content, json) return CustomInstruction.from_json(json) def get_style_rule_custom_instruction( self, style_rule: Union[str, StyleRuleInfo], instruction_id: str, ) -> CustomInstruction: """Retrieves a custom instruction by ID. :param style_rule: Style rule ID string or StyleRuleInfo object. :param instruction_id: ID of the custom instruction. :return: CustomInstruction. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") if not instruction_id: raise ValueError("instruction_id must not be empty") url = ( f"v3/style_rules/{style_rule}" f"/custom_instructions/{instruction_id}" ) status, content, json = self._api_call( url, method="GET", ) self._raise_for_status(status, content, json) return CustomInstruction.from_json(json) def update_style_rule_custom_instruction( self, style_rule: Union[str, StyleRuleInfo], instruction_id: str, label: str, prompt: str, source_language: Optional[str] = None, ) -> CustomInstruction: """Updates a custom instruction. :param style_rule: Style rule ID string or StyleRuleInfo object. :param instruction_id: ID of the custom instruction. :param label: New label for the custom instruction. :param prompt: New prompt text for the custom instruction. :param source_language: Optional source language code. :return: Updated CustomInstruction. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") if not instruction_id: raise ValueError("instruction_id must not be empty") if not label: raise ValueError("label must not be empty") if not prompt: raise ValueError("prompt must not be empty") request_data = {"label": label, "prompt": prompt} if source_language is not None: request_data["source_language"] = source_language url = ( f"v3/style_rules/{style_rule}" f"/custom_instructions/{instruction_id}" ) status, content, json = self._api_call( url, method="PUT", json=request_data, ) self._raise_for_status(status, content, json) return CustomInstruction.from_json(json) def delete_style_rule_custom_instruction( self, style_rule: Union[str, StyleRuleInfo], instruction_id: str, ) -> None: """Deletes a custom instruction from a style rule. :param style_rule: Style rule ID string or StyleRuleInfo object. :param instruction_id: ID of the custom instruction to delete. """ if isinstance(style_rule, StyleRuleInfo): style_rule = style_rule.style_id if not style_rule: raise ValueError("style_rule must not be empty") if not instruction_id: raise ValueError("instruction_id must not be empty") url = ( f"v3/style_rules/{style_rule}" f"/custom_instructions/{instruction_id}" ) status, content, json = self._api_call( url, method="DELETE", ) self._raise_for_status(status, content, json) def list_translation_memories( self, page: Optional[int] = None, page_size: Optional[int] = None, ) -> List[TranslationMemoryInfo]: """Retrieves a list of TranslationMemoryInfo for available translation memories. The maximum number of translation memories returned is controlled by page_size (max 25). :param page: Page number for pagination, 0-indexed (optional). :param page_size: Number of items per page (optional). :return: List of TranslationMemoryInfo objects. """ params = {} if page is not None: params["page"] = str(page) if page_size is not None: params["page_size"] = str(page_size) endpoint = "v3/translation_memories" if params: query_string = urllib.parse.urlencode(params) endpoint += f"?{query_string}" status, content, json = self._api_call(endpoint, method="GET") self._raise_for_status(status, content, json) translation_memories = ( json.get("translation_memories", []) if (json and isinstance(json, dict)) else [] ) return [ TranslationMemoryInfo.from_json(tm) for tm in translation_memories ] @staticmethod def _translation_memory_id( translation_memory: Union[str, TranslationMemoryInfo], ) -> str: if isinstance(translation_memory, TranslationMemoryInfo): translation_memory = translation_memory.translation_memory_id if not translation_memory: raise ValueError("translation_memory must not be empty") return translation_memory @staticmethod def _job_id(job: Union[str, TranslationMemoryJob]) -> str: if isinstance(job, TranslationMemoryJob): job = job.job_id if not job: raise ValueError("job must not be empty") return job def get_translation_memory( self, translation_memory: Union[str, TranslationMemoryInfo], ) -> TranslationMemoryInfo: """Retrieves a single translation memory by ID. :param translation_memory: Translation memory ID string or TranslationMemoryInfo object. :return: TranslationMemoryInfo for the requested translation memory. """ tm_id = self._translation_memory_id(translation_memory) status, content, json = self._api_call( f"v3/translation_memories/{tm_id}", method="GET" ) self._raise_for_status(status, content, json) return TranslationMemoryInfo.from_json(json) def list_translation_memory_segments( self, translation_memory: Union[str, TranslationMemoryInfo], *, page_size: Optional[int] = None, page_cursor: Optional[str] = None, filter_text: Optional[str] = None, filter_case_sensitive: Optional[bool] = None, ) -> TranslationMemorySegments: """Retrieves one page of the segments of a translation memory. Pagination is cursor-based: omit page_cursor on the first call, then pass the previous response's next_page_cursor to fetch the next page. An absent next_page_cursor means the last page has been returned. :param translation_memory: Translation memory ID string or TranslationMemoryInfo object. :param page_size: (Optional) Maximum segments per page (1-100, defaults to 50). :param page_cursor: (Optional) Cursor from a previous response; omit on the first call. :param filter_text: (Optional) Substring filter across source and target text, at least 2 characters. :param filter_case_sensitive: (Optional) Whether the filter is case-sensitive, defaults to False. :return: TranslationMemorySegments for the requested page. """ tm_id = self._translation_memory_id(translation_memory) params = {} if page_size is not None: params["page_size"] = str(page_size) if page_cursor is not None: params["page_cursor"] = page_cursor if filter_text is not None: params["filter_text"] = filter_text if filter_case_sensitive is not None: params["filter_case_sensitive"] = str( filter_case_sensitive ).lower() endpoint = f"v3/translation_memories/{tm_id}/segments" if params: # quote_via=quote percent-encodes spaces in filter_text as %20. # urlencode defaults to quote_plus, which encodes them as "+" — # correct for a form body, but not for a URI query string. query_string = urllib.parse.urlencode( params, quote_via=urllib.parse.quote ) endpoint += f"?{query_string}" status, content, json = self._api_call(endpoint, method="GET") self._raise_for_status(status, content, json) return TranslationMemorySegments.from_json(json) def delete_translation_memory( self, translation_memory: Union[str, TranslationMemoryInfo], ) -> None: """Deletes a translation memory. :param translation_memory: Translation memory ID string or TranslationMemoryInfo object. """ tm_id = self._translation_memory_id(translation_memory) status, content, json = self._api_call( f"v3/translation_memories/{tm_id}", method="DELETE" ) self._raise_for_status(status, content, json) def create_translation_memory_import( self, file_name: str, content_length: int, *, content_type: Optional[str] = None, display_name: Optional[str] = None, ) -> TranslationMemoryImport: """Creates an import job for a new translation memory. The job only declares the file; upload the TMX file itself to the returned upload URL with upload_translation_memory_file(), then poll get_translation_memory_job() for the outcome. Use import_translation_memory_from_filepath() to do all three steps at once. :param file_name: Name of the TMX file to import, for example "legal.tmx". :param content_length: Size of the TMX file in bytes. :param content_type: (Optional) MIME type of the file, defaults to "application/xml". :param display_name: (Optional) Name for the resulting translation memory, defaults to the file name. :return: TranslationMemoryImport with the job ID and upload URL. """ if not file_name: raise ValueError("file_name must not be empty") if content_length <= 0: raise ValueError("content_length must be greater than 0") source_file: Dict[str, Any] = { "file_name": file_name, "content_length": content_length, } if content_type is not None: source_file["content_type"] = content_type request_data: Dict[str, Any] = {"source_file": source_file} if display_name is not None: request_data["parameters"] = {"display_name": display_name} status, content, json = self._api_call( "v3/translation_memories/import", json=request_data ) self._raise_for_status(status, content, json) return TranslationMemoryImport.from_json(json) def upload_translation_memory_file( self, translation_memory_import: Union[str, TranslationMemoryImport], input_file: Union[BinaryIO, bytes, Any], content_type: str = "application/xml", ) -> None: """Uploads a TMX file to the upload URL of an import job, which starts processing. :param translation_memory_import: TranslationMemoryImport returned by create_translation_memory_import(), or its upload URL. :param input_file: TMX file content as bytes or a file-like object opened in binary mode. :param content_type: (Optional) MIME type of the file. Must match the content_type declared when the import job was created. """ if isinstance(translation_memory_import, TranslationMemoryImport): upload_url = translation_memory_import.upload_url else: upload_url = translation_memory_import if not upload_url: raise ValueError("upload_url must not be empty") # Read a file-like object into bytes up front. The request is prepared # once and reused across retries, so a stream body would already be at # EOF on the second attempt and upload nothing. if hasattr(input_file, "read"): input_file = input_file.read() status_code, content = self._asset_call( "PUT", upload_url, data=input_file, headers={"Content-Type": content_type}, ) self._raise_for_asset_status( status_code, content, "uploading translation memory file" ) def create_translation_memory_export( self, translation_memory: Union[str, TranslationMemoryInfo], ) -> TranslationMemoryExport: """Creates an export job for a translation memory. Poll get_translation_memory_job() for the download URL of the exported TMX file. Use export_translation_memory_to_filepath() to do both steps and write the file at once. :param translation_memory: Translation memory ID string or TranslationMemoryInfo object. :return: TranslationMemoryExport with the job ID. """ tm_id = self._translation_memory_id(translation_memory) status, content, json = self._api_call( f"v3/translation_memories/{tm_id}/export" ) self._raise_for_status(status, content, json) # 200 means the API reused a previously completed export, 202 that it # started a new one. return TranslationMemoryExport.from_json( json, reused_existing=(status == 200) ) def get_translation_memory_job( self, job: Union[str, TranslationMemoryJob], ) -> TranslationMemoryJob: """Retrieves the status of a translation memory import or export job. :param job: Job ID string or TranslationMemoryJob object. :return: TranslationMemoryJob with the current status. """ job_id = self._job_id(job) status, content, json = self._api_call( f"v3/translation_memories/jobs/{job_id}", method="GET" ) self._raise_for_status(status, content, json) return TranslationMemoryJob.from_json(json) def wait_until_translation_memory_job_done( self, job: Union[str, TranslationMemoryJob], timeout_s: Optional[int] = None, ) -> TranslationMemoryJob: """Polls a translation memory job until it finishes, sleeping between requests, and returns the final status. Note that an import job keeps reporting "awaiting_input" for a while after its file has been uploaded, because the API detects the upload asynchronously. That status is therefore polled through like any other non-terminal one. A job whose file is never uploaded does not finish on its own, so pass timeout_s when that is a possibility. :param job: Job ID string or TranslationMemoryJob object. :param timeout_s: (Optional) Maximum time to wait before raising an error. Note that this is not accurate to the second, but only polls every 5 seconds. :return: TranslationMemoryJob containing the status when finished. """ job_id = self._job_id(job) status = self.get_translation_memory_job(job_id) start_time_s = time.time() while not status.done: if ( timeout_s is not None and time.time() - start_time_s > timeout_s ): raise DeepLException( f"Manual timeout of {timeout_s}s exceeded for" + " translation memory job", should_retry=False, ) secs = 5.0 util.log_info( f"Rechecking translation memory job status after sleeping " f"for {secs:.3f} seconds." ) time.sleep(secs) status = self.get_translation_memory_job(job_id) return status def download_translation_memory_export( self, job: TranslationMemoryJob, output_file: Union[BinaryIO, Any, None] = None, chunk_size: int = 1, ) -> Optional[requests.Response]: """Downloads the TMX file of a completed export job. :param job: Completed export TranslationMemoryJob carrying the download URL. :param output_file: (Optional) File-like object to store the downloaded TMX file. If not provided, use iter_content() on the returned response object to read streamed file data. :param chunk_size: (Optional) Size of chunk in bytes for streaming. Only used if output_file is specified. :return: None if output_file is specified, otherwise the requests.Response. """ download_url = job.result.download_url if job.result else None if not download_url: raise ValueError( "translation memory export job has no download URL; it may " "not have completed yet" ) status_code, content = self._asset_call( "GET", download_url, stream=True ) self._raise_for_asset_status( status_code, content, "downloading translation memory export" ) assert isinstance(content, requests.Response) if output_file: for chunk in content.iter_content(chunk_size=chunk_size): output_file.write(chunk) return None return content def import_translation_memory_from_filepath( self, input_path: Union[os.PathLike, str], *, display_name: Optional[str] = None, timeout_s: Optional[int] = None, ) -> TranslationMemoryJob: """Imports a TMX file as a new translation memory: creates the import job, uploads the file, and waits for processing to finish. :param input_path: Path to the TMX file to import. :param display_name: (Optional) Name for the resulting translation memory, defaults to the file name. :param timeout_s: (Optional) Maximum time to wait for the import to finish. Note that this is not accurate to the second, but only polls every 5 seconds. :return: TranslationMemoryJob for the completed import; its result carries the new translation memory ID. :raises DeepLException: If the import fails. """ file_path = pathlib.Path(input_path) if not file_path.exists(): raise ValueError(f"file does not exist: {input_path}") created = self.create_translation_memory_import( file_name=file_path.name, content_length=file_path.stat().st_size, display_name=display_name, ) with open(file_path, "rb") as input_file: self.upload_translation_memory_file(created, input_file) job = self.wait_until_translation_memory_job_done( created.job_id, timeout_s ) if not job.ok: error_message = ( job.result.error_message if job.result else None ) or "unknown error" raise DeepLException( "Error occurred while importing translation memory: " f"{error_message}" ) return job def export_translation_memory_to_filepath( self, translation_memory: Union[str, TranslationMemoryInfo], output_path: Union[os.PathLike, str], *, timeout_s: Optional[int] = None, ) -> TranslationMemoryJob: """Exports a translation memory to a TMX file: creates the export job, waits for it to finish, and writes the result to output_path. :param translation_memory: Translation memory ID string or TranslationMemoryInfo object. :param output_path: Path to write the exported TMX file to. :param timeout_s: (Optional) Maximum time to wait for the export to finish. Note that this is not accurate to the second, but only polls every 5 seconds. :return: TranslationMemoryJob for the completed export. :raises DeepLException: If the export fails. """ created = self.create_translation_memory_export(translation_memory) job = self.wait_until_translation_memory_job_done( created.job_id, timeout_s ) if not job.ok: error_message = ( job.result.error_message if job.result else None ) or "unknown error" raise DeepLException( "Error occurred while exporting translation memory: " f"{error_message}" ) with open(output_path, "wb") as output_file: self.download_translation_memory_export( job, output_file, chunk_size=8192 ) return job def _asset_call( self, method: str, url: str, *, data: Any = None, headers: Optional[Dict[str, str]] = None, stream: bool = False, ): """Makes a request to a storage URL handed out by the API, for example a translation memory upload or download URL. These URLs are pre-signed and point outside the DeepL API, so the DeepL Authorization header is deliberately not sent. """ util.log_info("Request to storage URL", method=method, url=url) return self._client.request_with_backoff( method, url, data=data, json=None, headers=dict(headers or {}), stream=stream, ) @staticmethod def _raise_for_asset_status( status_code: int, content: Union[str, requests.Response], action: str, ) -> None: if 200 <= status_code < 300: return detail = content if isinstance(content, str) else "" message = f"Error {action}, HTTP status: {status_code}" if detail: message += f", detail: {detail}" raise DeepLException( message, should_retry=False, http_status_code=status_code ) DeepL-deepl-python-df50df2/deepl/exceptions.py000066400000000000000000000052501523706276600214510ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from typing import Optional from deepl.api_data import DocumentHandle class DeepLException(Exception): """Base class for deepl module exceptions. :param message: Message describing the error that occurred. :param should_retry: True if the request would normally be retried following this error, otherwise false. :param http_status_code: The HTTP status code in the response, if applicable, otherwise None. """ def __init__( self, message: str, should_retry: bool = False, http_status_code: Optional[int] = None, ): super().__init__(message) self.should_retry = should_retry self.http_status_code = http_status_code class AuthorizationException(DeepLException): """Authorization failed, check your authentication key.""" pass class QuotaExceededException(DeepLException): """Quota for this billing period has been exceeded.""" pass class TooManyRequestsException(DeepLException): """The maximum number of failed attempts were reached.""" pass class ConnectionException(DeepLException): """Connection to the DeepL API failed. :param message: Message describing the error that occurred. :param should_retry: True if the request would normally be retried following this error, otherwise false. """ def __init__( self, message: str, should_retry: bool = False, ): super().__init__( message, should_retry=should_retry, ) class DocumentTranslationException(DeepLException): """Error occurred while translating document. :param message: Message describing the error that occurred. :param document_handle: The document handle of the associated document. """ def __init__(self, message: str, document_handle: DocumentHandle): super().__init__(message) self.document_handle = document_handle def __str__(self): return f"{super().__str__()}, document handle: {self.document_handle}" @property def document_request(self): """Deprecated, use document_handle instead.""" import warnings warnings.warn( "document_request is deprecated", DeprecationWarning, stacklevel=2 ) return self.document_handle class GlossaryNotFoundException(DeepLException): """The specified glossary was not found.""" pass class DocumentNotReadyException(DeepLException): """The translation of the specified document is not yet complete.""" pass DeepL-deepl-python-df50df2/deepl/http_client.py000066400000000000000000000211541523706276600216060ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from . import version from .exceptions import ConnectionException, DeepLException import http import platform import random import requests # type: ignore import traceback import time from functools import lru_cache from typing import Dict, Optional, Tuple, Union from .util import log_info from deepl import util user_agent = None max_network_retries = 5 min_connection_timeout = 10.0 class _BackoffTimer: """Implements exponential-backoff strategy. This strategy is based on the GRPC Connection Backoff Protocol: https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md""" BACKOFF_INITIAL = 1.0 BACKOFF_MAX = 120.0 BACKOFF_JITTER = 0.23 BACKOFF_MULTIPLIER = 1.6 def __init__(self): self._num_retries = 0 self._backoff = self.BACKOFF_INITIAL self._deadline = time.time() + self._backoff def get_num_retries(self): return self._num_retries def get_timeout(self): return max(self.get_time_until_deadline(), min_connection_timeout) def get_time_until_deadline(self): return max(self._deadline - time.time(), 0.0) def sleep_until_deadline(self): time.sleep(self.get_time_until_deadline()) # Apply multiplier to current backoff time self._backoff = min( self._backoff * self.BACKOFF_MULTIPLIER, self.BACKOFF_MAX ) # Get deadline by applying jitter as a proportion of backoff: # if jitter is 0.1, then multiply backoff by random value in [0.9, 1.1] self._deadline = time.time() + self._backoff * ( 1 + self.BACKOFF_JITTER * random.uniform(-1, 1) ) self._num_retries += 1 class HttpClient: def __init__( self, proxy: Union[Dict, str, None] = None, send_platform_info: bool = True, verify_ssl: Union[bool, str, None] = None, ): self._session = requests.Session() if proxy: if isinstance(proxy, str): proxy = {"http": proxy, "https": proxy} if not isinstance(proxy, dict): raise ValueError( "proxy may be specified as a URL string or dictionary " "containing URL strings for the http and https keys." ) self._session.proxies.update(proxy) if verify_ssl is not None: self._session.verify = verify_ssl self._send_platform_info = send_platform_info self._app_info_name: Optional[str] = None self._app_info_version: Optional[str] = None def set_app_info(self, app_info_name: str, app_info_version: str): self._app_info_name = app_info_name self._app_info_version = app_info_version return self def close(self): self._session.close() def request_with_backoff( self, method: str, url: str, data: Optional[dict], json: Optional[dict], headers: dict, stream: bool = False, **kwargs, ) -> Tuple[int, Union[str, requests.Response]]: """Makes API request, retrying if necessary, and returns response. Return and exceptions are the same as function request().""" backoff = _BackoffTimer() request = self._prepare_request( method, url, data, json, headers, **kwargs ) while True: response: Optional[Tuple[int, Union[str, requests.Response]]] try: response = self._internal_request( request, stream=stream, timeout=backoff.get_timeout() ) exception = None except Exception as e: response = None exception = e if not self._should_retry( response, exception, backoff.get_num_retries() ): if response is not None: return response else: raise exception # type: ignore[misc] if exception is not None: log_info( f"Encountered a retryable-exception: {str(exception)}" ) log_info( f"Starting retry {backoff.get_num_retries() + 1} for request " f"{method} {url} after sleeping for " f"{backoff.get_time_until_deadline():.2f} seconds." ) backoff.sleep_until_deadline() def request( self, method: str, url: str, data: Optional[dict], json: Optional[dict], headers: dict, stream: bool = False, **kwargs, ) -> Tuple[int, Union[str, requests.Response]]: """Makes API request and returns response content. Response is returned as HTTP status code and either content string (if stream is False) or response (if stream is True). If no response is received will raise ConnectionException.""" request = self._prepare_request( method, url, data, json, headers, **kwargs ) return self._internal_request(request, stream) def _internal_request( self, request: requests.PreparedRequest, stream: bool, timeout: float = min_connection_timeout, **kwargs, ) -> Tuple[int, Union[str, requests.Response]]: try: response = self._session.send( request, stream=stream, timeout=timeout, **kwargs ) if stream: return response.status_code, response else: try: response.encoding = "UTF-8" return response.status_code, response.text finally: response.close() except requests.exceptions.ConnectionError as e: message = f"Connection failed: {e}" raise ConnectionException(message, should_retry=True) from e except requests.exceptions.Timeout as e: message = f"Request timed out: {e}" raise ConnectionException(message, should_retry=True) from e except requests.exceptions.RequestException as e: message = f"Request failed: {e}" raise ConnectionException(message, should_retry=False) from e except Exception as e: message = f"Unexpected request failure: {e}" raise ConnectionException(message, should_retry=False) from e def _should_retry(self, response, exception, num_retries): if num_retries >= max_network_retries: return False if response is None: return exception.should_retry status_code, _ = response # Retry on Too-Many-Requests error and internal errors return status_code == http.HTTPStatus.TOO_MANY_REQUESTS or ( status_code >= http.HTTPStatus.INTERNAL_SERVER_ERROR ) def _prepare_request( self, method: str, url: str, data: Optional[dict], json: Optional[dict], headers: dict, **kwargs, ) -> requests.PreparedRequest: try: headers.setdefault( "User-Agent", _generate_user_agent( user_agent, self._send_platform_info, self._app_info_name, self._app_info_version, ), ) return requests.Request( method, url, data=data, headers=headers, json=json, **kwargs ).prepare() except Exception as e: raise DeepLException( f"Error occurred while preparing request: {e}" ) from e @lru_cache(maxsize=4) def _generate_user_agent( user_agent_str: Optional[str], send_platform_info: bool, app_info_name: Optional[str], app_info_version: Optional[str], ): if user_agent_str: library_info_str = user_agent_str else: library_info_str = f"deepl-python/{version.VERSION}" if send_platform_info: try: library_info_str += ( f" ({platform.platform()}) " f"python/{platform.python_version()} " f"requests/{requests.__version__}" ) except Exception: util.log_info( "Exception when querying platform information:\n" + traceback.format_exc() ) if app_info_name and app_info_version: library_info_str += f" {app_info_name}/{app_info_version}" return library_info_str DeepL-deepl-python-df50df2/deepl/py.typed000066400000000000000000000000001523706276600204010ustar00rootroot00000000000000DeepL-deepl-python-df50df2/deepl/translator.py000066400000000000000000001523121523706276600214630ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from deepl.api_data import ( DocumentHandle, DocumentStatus, Formality, GlossaryLanguagePair, GlossaryInfo, MultilingualGlossaryInfo, ModelType, Language, SplitSentences, StyleRuleInfo, TextResult, TranslationMemoryInfo, Usage, ) from . import http_client, util from .exceptions import ( DocumentNotReadyException, GlossaryNotFoundException, QuotaExceededException, TooManyRequestsException, DeepLException, AuthorizationException, DocumentTranslationException, ) import http import http.client import json as json_module import os import pathlib import requests # type: ignore import time from typing import ( Any, BinaryIO, Dict, Iterable, List, Optional, TextIO, Tuple, Union, ) import urllib.parse class Translator: """Wrapper for the DeepL API for language translation. You must create an instance of Translator to use the DeepL API. :param auth_key: Authentication key as found in your DeepL API account. :param server_url: (Optional) Base URL of DeepL API, can be overridden e.g. for testing purposes. :param proxy: (Optional) Proxy server URL string or dictionary containing URL strings for the 'http' and 'https' keys. This is passed to the underlying requests session, see the requests proxy documentation for more information. :param send_platform_info: (Optional) boolean that indicates if the client library can send basic platform info (python version, OS, http library version) to the DeepL API. True = send info, False = only send client library version :param verify_ssl: (Optional) Controls how requests verifies SSL certificates. This is passed to the underlying requests session, see the requests verify documentation for more information. :param skip_language_check: Deprecated, and now has no effect as the corresponding internal functionality has been removed. This parameter will be removed in a future version. All functions may raise DeepLException or a subclass if a connection error occurs. """ _DEEPL_SERVER_URL = "https://api.deepl.com" _DEEPL_SERVER_URL_FREE = "https://api-free.deepl.com" # HTTP status code used by DeepL API to indicate the character limit for # this billing period has been reached. _HTTP_STATUS_QUOTA_EXCEEDED = 456 def __init__( self, auth_key: str, *, server_url: Optional[str] = None, proxy: Union[Dict, str, None] = None, send_platform_info: bool = True, verify_ssl: Union[bool, str, None] = None, skip_language_check: bool = False, ): if not auth_key: raise ValueError("auth_key must not be empty") if server_url is None: server_url = ( self._DEEPL_SERVER_URL_FREE if util.auth_key_is_free_account(auth_key) else self._DEEPL_SERVER_URL ) self._server_url = server_url self._client = http_client.HttpClient( proxy, send_platform_info, verify_ssl ) self.headers = {"Authorization": f"DeepL-Auth-Key {auth_key}"} def __del__(self): self.close() def _api_call( self, url: str, *, method: str = "POST", data: Optional[dict] = None, json: Optional[dict] = None, stream: bool = False, headers: Optional[dict] = None, **kwargs, ) -> Tuple[int, Union[str, requests.Response], Any]: """ Makes a request to the API, and returns response as status code, content and JSON object. """ if data is not None and json is not None: raise ValueError("cannot accept both json and data") if data is None: data = {} url = urllib.parse.urljoin(self._server_url, url) util.log_info("Request to DeepL API", method=method, url=url) util.log_debug("Request details", data=data, json=json) if headers is None: headers = dict() headers.update( {k: v for k, v in self.headers.items() if k not in headers} ) status_code, content = self._client.request_with_backoff( method, url, data=data, json=json, stream=stream, headers=headers, **kwargs, ) json = None if isinstance(content, str): content_str = content try: json = json_module.loads(content) except json_module.JSONDecodeError: pass else: content_str = content.text util.log_info("DeepL API response", url=url, status_code=status_code) util.log_debug("Response details", content=content_str) return status_code, content, json def _raise_for_status( self, status_code: int, content: Union[str, requests.Response], json: Any, glossary: bool = False, downloading_document: bool = False, ): message = "" if json is not None and isinstance(json, dict) and "message" in json: message += ", message: " + json["message"] if json is not None and isinstance(json, dict) and "detail" in json: message += ", detail: " + json["detail"] if 200 <= status_code < 400: return elif status_code == http.HTTPStatus.FORBIDDEN: raise AuthorizationException( f"Authorization failure, check auth_key{message}", http_status_code=status_code, ) elif status_code == self._HTTP_STATUS_QUOTA_EXCEEDED: raise QuotaExceededException( f"Quota for this billing period has been exceeded{message}", http_status_code=status_code, ) elif status_code == http.HTTPStatus.NOT_FOUND: if glossary: raise GlossaryNotFoundException( f"Glossary not found{message}", http_status_code=status_code, ) raise DeepLException( f"Not found{message}", http_status_code=status_code, ) elif status_code == http.HTTPStatus.BAD_REQUEST: raise DeepLException( f"Bad request{message}", http_status_code=status_code ) elif status_code == http.HTTPStatus.TOO_MANY_REQUESTS: raise TooManyRequestsException( "Too many requests, DeepL servers are currently experiencing " f"high load{message}", should_retry=True, http_status_code=status_code, ) elif status_code == http.HTTPStatus.SERVICE_UNAVAILABLE: if downloading_document: raise DocumentNotReadyException( f"Document not ready{message}", should_retry=True, http_status_code=status_code, ) else: raise DeepLException( f"Service unavailable{message}", should_retry=True, http_status_code=status_code, ) else: status_name = ( http.client.responses[status_code] if status_code in http.client.responses else "Unknown" ) content_str = content if isinstance(content, str) else content.text raise DeepLException( f"Unexpected status code: {status_code} {status_name}, " f"content: {content_str}.", should_retry=False, http_status_code=status_code, ) def _check_valid_languages( self, source_lang: Optional[str], target_lang: str ): """Internal function to check given languages are valid.""" if target_lang == "EN": raise DeepLException( 'target_lang="EN" is deprecated, please use "EN-GB" or "EN-US"' "instead." ) elif target_lang == "PT": raise DeepLException( 'target_lang="PT" is deprecated, please use "PT-PT" or "PT-BR"' "instead." ) def _check_language_and_formality( self, source_lang: Union[str, Language, None], target_lang: Union[str, Language], formality: Union[str, Formality, None], glossary: Union[ str, GlossaryInfo, MultilingualGlossaryInfo, None ] = None, style_rule: Union[str, StyleRuleInfo, None] = None, translation_memory: Union[str, TranslationMemoryInfo, None] = None, translation_memory_threshold: Optional[int] = None, glossary_ids: Optional[ List[Union[str, GlossaryInfo, MultilingualGlossaryInfo]] ] = None, ) -> dict: # target_lang and source_lang are case insensitive target_lang = str(target_lang).upper() if source_lang is not None: source_lang = str(source_lang).upper() if glossary is not None and source_lang is None: raise ValueError("source_lang is required if using a glossary") if glossary_ids is not None: if glossary is not None: raise ValueError( "glossary_ids cannot be used together with the glossary " "parameter" ) if source_lang is None: raise ValueError( "source_lang is required if using glossary_ids" ) if len(glossary_ids) > 5: raise ValueError( "glossary_ids must not contain more than 5 glossary IDs" ) if isinstance(glossary, GlossaryInfo): if ( Language.remove_regional_variant(target_lang) != glossary.target_lang or source_lang != glossary.source_lang ): raise ValueError( "source_lang and target_lang must match glossary" ) if isinstance(glossary, MultilingualGlossaryInfo): target_lang_code = Language.remove_regional_variant(target_lang) if not any( glossary_dict.target_lang == target_lang_code and glossary_dict.source_lang == source_lang for glossary_dict in glossary.dictionaries ): raise ValueError( "must have a glossary with a dictionary for the given " "source_lang and target_lang" ) if isinstance(style_rule, StyleRuleInfo): if ( Language.remove_regional_variant(target_lang) != style_rule.language.upper() ): raise ValueError("target_lang must match style rule language") self._check_valid_languages(source_lang, target_lang) request_data: dict = {"target_lang": target_lang} if source_lang is not None: request_data["source_lang"] = source_lang if formality is not None: request_data["formality"] = str(formality).lower() if isinstance(glossary, GlossaryInfo) or isinstance( glossary, MultilingualGlossaryInfo ): request_data["glossary_id"] = glossary.glossary_id elif glossary is not None: request_data["glossary_id"] = glossary if glossary_ids is not None: resolved_ids = [ ( entry.glossary_id if isinstance( entry, (GlossaryInfo, MultilingualGlossaryInfo) ) else entry ) for entry in glossary_ids ] # The API expects glossary_ids as an array. For text translation # this is JSON-encoded as an array; for document translation # (form/multipart) translate_document_upload joins it into a # single comma-separated glossary_ids field. request_data["glossary_ids"] = resolved_ids if isinstance(style_rule, StyleRuleInfo): request_data["style_id"] = style_rule.style_id elif style_rule is not None: request_data["style_id"] = style_rule if isinstance(translation_memory, TranslationMemoryInfo): request_data["translation_memory_id"] = ( translation_memory.translation_memory_id ) elif translation_memory is not None: request_data["translation_memory_id"] = translation_memory if translation_memory_threshold is not None: if translation_memory is None: raise ValueError( "translation_memory_threshold requires" " translation_memory" ) if not (0 <= translation_memory_threshold <= 100): raise ValueError( "translation_memory_threshold must be between 0 and 100" ) request_data["translation_memory_threshold"] = ( translation_memory_threshold ) return request_data def _create_glossary( self, name: str, source_lang: Union[str, Language], target_lang: Union[str, Language], entries_format: str, entries: Union[str, bytes], ) -> GlossaryInfo: # glossaries are only supported for base language types source_lang = Language.remove_regional_variant(source_lang) target_lang = Language.remove_regional_variant(target_lang) if not name: raise ValueError("glossary name must not be empty") request_data = { "name": name, "source_lang": source_lang, "target_lang": target_lang, "entries_format": entries_format, "entries": entries, } status, content, json = self._api_call( "v2/glossaries", json=request_data ) self._raise_for_status(status, content, json, glossary=True) return GlossaryInfo.from_json(json) def close(self): if hasattr(self, "_client"): self._client.close() def set_app_info(self, app_info_name: str, app_info_version: str): self._client.set_app_info(app_info_name, app_info_version) return self @property def server_url(self): return self._server_url def translate_text( self, text: Union[str, Iterable[str]], *, source_lang: Union[str, Language, None] = None, target_lang: Union[str, Language], context: Optional[str] = None, split_sentences: Union[str, SplitSentences, None] = None, preserve_formatting: Optional[bool] = None, formality: Union[str, Formality, None] = None, glossary: Union[ str, GlossaryInfo, MultilingualGlossaryInfo, None ] = None, glossary_ids: Optional[ List[Union[str, GlossaryInfo, MultilingualGlossaryInfo]] ] = None, tag_handling: Optional[str] = None, tag_handling_version: Optional[str] = None, outline_detection: Optional[bool] = None, non_splitting_tags: Union[str, List[str], None] = None, splitting_tags: Union[str, List[str], None] = None, ignore_tags: Union[str, List[str], None] = None, model_type: Union[str, ModelType, None] = None, style_rule: Union[str, StyleRuleInfo, None] = None, translation_memory: Union[str, TranslationMemoryInfo, None] = None, translation_memory_threshold: Optional[int] = None, custom_instructions: Optional[List[str]] = None, extra_body_parameters: Optional[dict] = None, ) -> Union[TextResult, List[TextResult]]: """Translate text(s) into the target language. :param text: Text to translate. :type text: UTF-8 :class:`str`; string sequence (list, tuple, iterator, generator) :param source_lang: (Optional) Language code of input text, for example "DE", "EN", "FR". If omitted, DeepL will auto-detect the input language. If a glossary is used, source_lang must be specified. :param target_lang: language code to translate text into, for example "DE", "EN-US", "FR". :param context: (Optional) Additional contextual text to influence translations, that is not translated itself. Characters in the `context` parameter are not counted toward billing. See the API documentation for more information and example usage. :param split_sentences: (Optional) Controls how the translation engine should split input into sentences before translation, see :class:`SplitSentences`. :param preserve_formatting: (Optional) Set to True to prevent the translation engine from correcting some formatting aspects, and instead leave the formatting unchanged. :param formality: (Optional) Desired formality for translation, as Formality enum, "less", "more", "prefer_less", "prefer_more", or "default". :param glossary: (Optional) glossary or glossary ID to use for translation. Must match specified source_lang and target_lang. :param glossary_ids: (Optional) list of up to 5 glossaries or glossary IDs to use for translation, applied in order. Requires source_lang to be set and cannot be combined with the glossary parameter. :param style_rule: (Optional) style rule or style rule ID to use for translation. :param translation_memory: (Optional) translation memory or translation memory ID to use for translation. :param translation_memory_threshold: (Optional) minimum matching percentage for fuzzy matches from the translation memory (0-100). Recommended minimum is 75%. :param tag_handling: (Optional) Type of tags to parse before translation, only "xml" and "html" are currently available. :param tag_handling_version: (Optional) Version of tag handling algorithm to use, "v1" or "v2". :param outline_detection: (Optional) Set to False to disable automatic tag detection. :param non_splitting_tags: (Optional) XML tags that should not split a sentence. :type non_splitting_tags: List of XML tags or comma-separated-list of tags. :param splitting_tags: (Optional) XML tags that should split a sentence. :type splitting_tags: List of XML tags or comma-separated-list of tags. :param ignore_tags: (Optional) XML tags containing text that should not be translated. :type ignore_tags: List of XML tags or comma-separated-list of tags. :param model_type: (Optional) Controls whether the translation engine should use a potentially slower model to achieve higher quality. :param custom_instructions: (Optional) List of custom instructions to guide the translation. Maximum of 10 instructions, each with a maximum length of 300 characters. :param extra_body_parameters: (Optional) Additional key/value pairs to include in the JSON request body sent to the API. If provided, keys in this dict will be added to the request body. Existing keys set by the client will not be overwritten by entries in extra_body_parameters. :return: List of TextResult objects containing results, unless input text was one string, then a single TextResult object is returned. """ if isinstance(text, str): if len(text) == 0: raise ValueError("text must not be empty") text = [text] multi_input = False elif hasattr(text, "__iter__"): multi_input = True text = list(text) else: raise TypeError( "text parameter must be a string or an iterable of strings" ) request_data = self._check_language_and_formality( source_lang, target_lang, formality, glossary, style_rule, translation_memory, translation_memory_threshold, glossary_ids, ) request_data["text"] = text # Always send show_billed_characters=true, remove when the API default # is changed to true request_data["show_billed_characters"] = True if context is not None: request_data["context"] = context if split_sentences is not None: request_data["split_sentences"] = str(split_sentences) if preserve_formatting is not None: request_data["preserve_formatting"] = bool(preserve_formatting) if tag_handling is not None: request_data["tag_handling"] = tag_handling if tag_handling_version is not None: request_data["tag_handling_version"] = tag_handling_version if outline_detection is not None: request_data["outline_detection"] = bool(outline_detection) if model_type is not None: request_data["model_type"] = str(model_type) def join_tags(tag_argument: Union[str, Iterable[str]]) -> List[str]: if isinstance(tag_argument, str): tag_argument = [tag_argument] return [ tag for arg_string in tag_argument for tag in arg_string.split(",") ] if non_splitting_tags is not None: request_data["non_splitting_tags"] = join_tags(non_splitting_tags) if splitting_tags is not None: request_data["splitting_tags"] = join_tags(splitting_tags) if ignore_tags is not None: request_data["ignore_tags"] = join_tags(ignore_tags) if custom_instructions is not None: request_data["custom_instructions"] = custom_instructions # Do not overwrite keys that were explicitly set by this method. if extra_body_parameters: for k, v in extra_body_parameters.items(): request_data[k] = v status, content, json = self._api_call( "v2/translate", json=request_data ) self._raise_for_status(status, content, json) translations = ( json.get("translations", []) if (json and isinstance(json, dict)) else [] ) output = [] for translation in translations: text = translation.get("text", "") if translation else "" detected_source_language = ( translation.get("detected_source_language", "") if translation else "" ) billed_characters = int(translation.get("billed_characters")) model_type_used = translation.get("model_type_used") output.append( TextResult( text, detected_source_language, billed_characters, model_type_used, ) ) return output if multi_input else output[0] def translate_text_with_glossary( self, text: Union[str, Iterable[str]], glossary: GlossaryInfo, target_lang: Union[str, Language, None] = None, **kwargs, ) -> Union[TextResult, List[TextResult]]: """Translate text(s) using given glossary. The source and target languages are assumed to match the glossary languages. Note that if the glossary target language is English (EN), the text will be translated into British English (EN-GB). To instead translate into American English specify target_lang="EN-US". :param text: Text to translate. :type text: UTF-8 :class:`str`; string sequence (list, tuple, iterator, generator) :param glossary: glossary to use for translation. :type glossary: :class:`GlossaryInfo`. :param target_lang: override target language of glossary. :return: List of TextResult objects containing results, unless input text was one string, then a single TextResult object is returned. """ if not isinstance(glossary, GlossaryInfo): msg = ( "This function expects the glossary parameter to be an " "instance of GlossaryInfo. Use get_glossary() to obtain a " "GlossaryInfo using the glossary ID of an existing " "glossary. Alternatively, use translate_text() and " "specify the glossary ID using the glossary parameter. " ) raise ValueError(msg) if target_lang is None: target_lang = glossary.target_lang if target_lang == "EN": target_lang = "EN-GB" return self.translate_text( text, source_lang=glossary.source_lang, target_lang=target_lang, glossary=glossary, **kwargs, ) def translate_document_from_filepath( self, input_path: Union[str, pathlib.PurePath], output_path: Union[str, pathlib.PurePath], *, source_lang: Optional[str] = None, target_lang: str, formality: Union[str, Formality] = Formality.DEFAULT, glossary: Union[ str, GlossaryInfo, MultilingualGlossaryInfo, None ] = None, glossary_ids: Optional[ List[Union[str, GlossaryInfo, MultilingualGlossaryInfo]] ] = None, style_rule: Union[str, StyleRuleInfo, None] = None, translation_memory: Union[str, TranslationMemoryInfo, None] = None, translation_memory_threshold: Optional[int] = None, timeout_s: Optional[int] = None, extra_body_parameters: Optional[dict] = None, ) -> DocumentStatus: """Upload document at given input path, translate it into the target language, and download result to given output path. :param input_path: Path to document to be translated. :param output_path: Path to store translated document. :param source_lang: (Optional) Language code of input document, for example "DE", "EN", "FR". If omitted, DeepL will auto-detect the input language. :param target_lang: Language code to translate document into, for example "DE", "EN-US", "FR". :param formality: (Optional) Desired formality for translation, as Formality enum, "less", "more", "prefer_less", or "prefer_more". :param glossary: (Optional) glossary or glossary ID to use for translation. Must match specified source_lang and target_lang. :param glossary_ids: (Optional) list of up to 5 glossaries or glossary IDs to use for translation, applied in order. Requires source_lang to be set and cannot be combined with the glossary parameter. :param style_rule: (Optional) style rule or style rule ID to use for translation. :param translation_memory: (Optional) translation memory or translation memory ID to use for translation. :param translation_memory_threshold: (Optional) minimum matching percentage for fuzzy matches from the translation memory (0-100). Recommended minimum is 75%. :param timeout_s: (beta) (Optional) Maximum time to wait before the call raises an error. Note that this is not accurate to the second, but only polls every 5 seconds. :param extra_body_parameters: (Optional) Additional key/value pairs to include in the JSON request body sent to the API. If provided, keys in this dict will be added to the request body. Existing keys set by the client will not be overwritten by entries in extra_body_parameters. :return: DocumentStatus when document translation completed, this allows the number of billed characters to be queried. :raises DocumentTranslationException: If an error occurs during translation. The exception includes information about the document request. """ # Determine output_format from output path in_ext = pathlib.PurePath(input_path).suffix.lower() out_ext = pathlib.PurePath(output_path).suffix.lower() output_format = None if in_ext == out_ext else out_ext[1:] with open(input_path, "rb") as in_file: with open(output_path, "wb") as out_file: try: return self.translate_document( in_file, out_file, target_lang=target_lang, source_lang=source_lang, formality=formality, glossary=glossary, glossary_ids=glossary_ids, style_rule=style_rule, translation_memory=translation_memory, translation_memory_threshold=( translation_memory_threshold ), output_format=output_format, timeout_s=timeout_s, extra_body_parameters=extra_body_parameters, ) except Exception as e: out_file.close() os.unlink(output_path) raise e def translate_document( self, input_document: Union[TextIO, BinaryIO, Any], output_document: Union[TextIO, BinaryIO, Any], *, source_lang: Optional[str] = None, target_lang: str, formality: Union[str, Formality] = Formality.DEFAULT, glossary: Union[ str, GlossaryInfo, MultilingualGlossaryInfo, None ] = None, glossary_ids: Optional[ List[Union[str, GlossaryInfo, MultilingualGlossaryInfo]] ] = None, style_rule: Union[str, StyleRuleInfo, None] = None, translation_memory: Union[str, TranslationMemoryInfo, None] = None, translation_memory_threshold: Optional[int] = None, filename: Optional[str] = None, output_format: Optional[str] = None, timeout_s: Optional[int] = None, extra_body_parameters: Optional[dict] = None, ) -> DocumentStatus: """Upload document, translate it into the target language, and download result. :param input_document: Document to translate as a file-like object. It is recommended to open files in binary mode. :param output_document: File-like object to receive translated document. :param source_lang: (Optional) Language code of input document, for example "DE", "EN", "FR". If omitted, DeepL will auto-detect the input language. :param target_lang: Language code to translate document into, for example "DE", "EN-US", "FR". :param formality: (Optional) Desired formality for translation, as Formality enum, "less", "more", "prefer_less", or "prefer_more". :param glossary: (Optional) glossary or glossary ID to use for translation. Must match specified source_lang and target_lang. :param glossary_ids: (Optional) list of up to 5 glossaries or glossary IDs to use for translation, applied in order. Requires source_lang to be set and cannot be combined with the glossary parameter. :param style_rule: (Optional) style rule or style rule ID to use for translation. :param translation_memory: (Optional) translation memory or translation memory ID to use for translation. :param translation_memory_threshold: (Optional) minimum matching percentage for fuzzy matches from the translation memory (0-100). Recommended minimum is 75%. :param filename: (Optional) Filename including extension, only required if uploading string or bytes containing file content. :param output_format: (Optional) Desired output file extension, if it differs from the input file format. :param timeout_s: (beta) (Optional) Maximum time to wait before the call raises an error. Note that this is not accurate to the second, but only polls every 5 seconds. :param extra_body_parameters: (Optional) Additional key/value pairs to include in the JSON request body sent to the API. If provided, keys in this dict will be added to the request body. Existing keys set by the client will not be overwritten by entries in extra_body_parameters. :return: DocumentStatus when document translation completed, this allows the number of billed characters to be queried. :raises DocumentTranslationException: If an error occurs during translation, the exception includes the document handle. """ handle = self.translate_document_upload( input_document, target_lang=target_lang, source_lang=source_lang, formality=formality, glossary=glossary, glossary_ids=glossary_ids, style_rule=style_rule, translation_memory=translation_memory, translation_memory_threshold=translation_memory_threshold, filename=filename, output_format=output_format, extra_body_parameters=extra_body_parameters, ) try: status = self.translate_document_wait_until_done(handle, timeout_s) if status.ok: self.translate_document_download(handle, output_document) except Exception as e: raise DocumentTranslationException(str(e), handle) from e if not status.ok: error_message = status.error_message or "unknown error" raise DocumentTranslationException( f"Error occurred while translating document: {error_message}", handle, ) return status def translate_document_upload( self, input_document: Union[TextIO, BinaryIO, str, bytes, Any], *, source_lang: Optional[str] = None, target_lang: str, formality: Union[str, Formality, None] = None, glossary: Union[ str, GlossaryInfo, MultilingualGlossaryInfo, None ] = None, glossary_ids: Optional[ List[Union[str, GlossaryInfo, MultilingualGlossaryInfo]] ] = None, style_rule: Union[str, StyleRuleInfo, None] = None, translation_memory: Union[str, TranslationMemoryInfo, None] = None, translation_memory_threshold: Optional[int] = None, filename: Optional[str] = None, output_format: Optional[str] = None, extra_body_parameters: Optional[dict] = None, ) -> DocumentHandle: """Upload document to be translated and return handle associated with request. :param input_document: Document to translate as a file-like object, or string or bytes containing file content. :param source_lang: (Optional) Language code of input document, for example "DE", "EN", "FR". If omitted, DeepL will auto-detect the input language. :param target_lang: Language code to translate document into, for example "DE", "EN-US", "FR". :param formality: (Optional) Desired formality for translation, as Formality enum, "less", "more", "prefer_less", or "prefer_more". :param glossary: (Optional) glossary or glossary ID to use for translation. Must match specified source_lang and target_lang. :param glossary_ids: (Optional) list of up to 5 glossaries or glossary IDs to use for translation, applied in order. Requires source_lang to be set and cannot be combined with the glossary parameter. :param style_rule: (Optional) style rule or style rule ID to use for translation. :param translation_memory: (Optional) translation memory or translation memory ID to use for translation. :param translation_memory_threshold: (Optional) minimum matching percentage for fuzzy matches from the translation memory (0-100). Recommended minimum is 75%. :param filename: (Optional) Filename including extension, only required if uploading string or bytes containing file content. :param output_format: (Optional) Desired output file extension, if it differs from the input file format. :param extra_body_parameters: (Optional) Additional key/value pairs to include in the JSON request body sent to the API. If provided, keys in this dict will be added to the request body. Existing keys set by the client will not be overwritten by entries in extra_body_parameters. :return: DocumentHandle with ID and key identifying document. """ request_data = self._check_language_and_formality( source_lang, target_lang, formality, glossary, style_rule, translation_memory, translation_memory_threshold, glossary_ids=glossary_ids, ) if output_format: request_data["output_format"] = output_format # The document endpoint uses multipart/form-data. Send glossary_ids as # a single comma-separated field (the form form of the array), matching # the other SDKs, instead of repeated multipart fields. if isinstance(request_data.get("glossary_ids"), list): request_data["glossary_ids"] = ",".join( request_data["glossary_ids"] ) files: Dict[str, Any] = {} if isinstance(input_document, (str, bytes)): if filename is None: raise ValueError( "filename is required if uploading file content as string " "or bytes" ) files = {"file": (filename, input_document)} else: files = {"file": input_document} if extra_body_parameters: for k, v in extra_body_parameters.items(): request_data[k] = v status, content, json = self._api_call( "v2/document", data=request_data, files=files ) self._raise_for_status(status, content, json) if not json: json = {} return DocumentHandle( json.get("document_id", ""), json.get("document_key", "") ) def translate_document_get_status( self, handle: DocumentHandle ) -> DocumentStatus: """Gets the status of the document translation request associated with given handle. :param handle: DocumentHandle to the request to check. :return: DocumentStatus containing the request status. :raises DocumentTranslationException: If an error occurs during querying the document, the exception includes the document handle. """ data = {"document_key": handle.document_key} url = f"v2/document/{handle.document_id}" status_code, content, json = self._api_call(url, json=data) self._raise_for_status(status_code, content, json) status = ( json.get("status", None) if (json and isinstance(json, dict)) else None ) if not status: raise DocumentTranslationException( "Querying document status gave an empty response", handle ) seconds_remaining = ( json.get("seconds_remaining", None) if (json and isinstance(json, dict)) else None ) billed_characters = ( json.get("billed_characters", None) if (json and isinstance(json, dict)) else None ) error_message = ( json.get("error_message", None) if (json and isinstance(json, dict)) else None ) return DocumentStatus( status, seconds_remaining, billed_characters, error_message ) def translate_document_wait_until_done( self, handle: DocumentHandle, timeout_s: Optional[int] = None, ) -> DocumentStatus: """ Continually polls the status of the document translation associated with the given handle, sleeping in between requests, and returns the final status when the translation completes (whether successful or not). :param handle: DocumentHandle to the document translation to wait on. :param timeout_s: (beta) (Optional) Maximum time to wait before the call raises an error. Note that this is not accurate to the second, but only polls every 5 seconds. :return: DocumentStatus containing the status when completed. """ status = self.translate_document_get_status(handle) start_time_s = time.time() while status.ok and not status.done: if ( timeout_s is not None and time.time() - start_time_s > timeout_s ): raise DeepLException( f"Manual timeout of {timeout_s}s exceeded for" + " document translation", should_retry=False, ) else: secs = ( 5.0 # seconds_remaining is currently unreliable, so just ) # poll equidistantly util.log_info( f"Rechecking document translation status " f"after sleeping for {secs:.3f} seconds." ) time.sleep(secs) status = self.translate_document_get_status(handle) return status def translate_document_download( self, handle: DocumentHandle, output_file: Union[TextIO, BinaryIO, Any, None] = None, chunk_size: int = 1, ) -> Optional[requests.Response]: """Downloads the translated document for the request associated with given handle and returns a response object for streaming the data. Call iter_content() on the response object to read streamed file data. Alternatively, a file-like object may be given as output_file where the complete file will be downloaded and written to. :param handle: DocumentHandle associated with request. :param output_file: (Optional) File-like object to store downloaded document. If not provided, use iter_content() on the returned response object to read streamed file data. :param chunk_size: (Optional) Size of chunk in bytes for streaming. Only used if output_file is specified. :return: None if output_file is specified, otherwise the requests.Response will be returned. """ data = {"document_key": handle.document_key} url = f"v2/document/{handle.document_id}/result" status_code, response, json = self._api_call( url, json=data, stream=True ) # TODO: once we drop py3.6 support, replace this with @overload # annotations in `_api_call` and chained private functions. # See for example https://stackoverflow.com/a/74070166/4926599 # In addition, drop the type: ignore annotation on the # `import requests` / `from requests` assert isinstance(response, requests.Response) self._raise_for_status( status_code, "", json, downloading_document=True ) if output_file: chunks = response.iter_content(chunk_size=chunk_size) for chunk in chunks: output_file.write(chunk) return None else: return response def get_source_languages(self, skip_cache: bool = False) -> List[Language]: """Request the list of available source languages. :param skip_cache: Deprecated, and now has no effect as the corresponding internal functionality has been removed. This parameter will be removed in a future version. :return: List of supported source languages. """ status, content, json = self._api_call("v2/languages", method="GET") self._raise_for_status(status, content, json) languages = json if (json and isinstance(json, list)) else [] return [ Language( language["language"], language["name"], ) for language in languages ] def get_target_languages(self, skip_cache: bool = False) -> List[Language]: """Request the list of available target languages. :param skip_cache: Deprecated, and now has no effect as the corresponding internal functionality has been removed. This parameter will be removed in a future version. :return: List of supported target languages. """ data = {"type": "target"} status, content, json = self._api_call( "v2/languages", method="GET", data=data ) self._raise_for_status(status, content, json) languages = json if (json and isinstance(json, list)) else [] return [ Language( language["language"], language["name"], language.get("supports_formality", None), ) for language in languages ] def get_glossary_languages(self) -> List[GlossaryLanguagePair]: """Request the list of language pairs supported for glossaries.""" status, content, json = self._api_call( "v2/glossary-language-pairs", method="GET" ) self._raise_for_status(status, content, json) supported_languages = ( json.get("supported_languages", []) if (json and isinstance(json, dict)) else [] ) return [ GlossaryLanguagePair( language_pair["source_lang"], language_pair["target_lang"] ) for language_pair in supported_languages ] def get_usage(self) -> Usage: """Requests the current API usage.""" status, content, json = self._api_call("v2/usage", method="GET") self._raise_for_status(status, content, json) if not isinstance(json, dict): json = {} return Usage(json) def create_glossary( self, name: str, source_lang: Union[str, Language], target_lang: Union[str, Language], entries: Dict[str, str], ) -> GlossaryInfo: """Creates a glossary with given name for the source and target languages, containing the entries in dictionary. The glossary may be used in the translate_text functions. Only certain language pairs are supported. The available language pairs can be queried using get_glossary_languages(). Glossaries are not regional specific: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function requires the glossary entries to be provided as a dictionary of source-target terms. To create a glossary from a CSV file downloaded from the DeepL website, see create_glossary_from_csv(). :param name: user-defined name to attach to glossary. :param source_lang: Language of source terms. :param target_lang: Language of target terms. :param entries: dictionary of terms to insert in glossary, with the keys and values representing source and target terms respectively. :return: GlossaryInfo containing information about created glossary. :raises ValueError: If the glossary name is empty, or entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ if not entries: raise ValueError("glossary entries must not be empty") return self._create_glossary( name, source_lang, target_lang, "tsv", util.convert_dict_to_tsv(entries), ) def create_glossary_from_csv( self, name: str, source_lang: Union[str, Language], target_lang: Union[str, Language], csv_data: Union[TextIO, BinaryIO, str, bytes, Any], ) -> GlossaryInfo: """Creates a glossary with given name for the source and target languages, containing the entries in the given CSV data. The glossary may be used in the translate_text functions. Only certain language pairs are supported. The available language pairs can be queried using get_glossary_languages(). Glossaries are not regional specific: a glossary with target language EN may be used to translate texts into both EN-US and EN-GB. This function allows you to upload a glossary CSV file that you have downloaded from the DeepL website. Information about the expected CSV format can be found in the API documentation: https://www.deepl.com/docs-api/managing-glossaries/supported-glossary-formats/ # noqa :param name: user-defined name to attach to glossary. :param source_lang: Language of source terms. :param target_lang: Language of target terms. :param csv_data: CSV data containing glossary entries, either as a file-like object or string or bytes containing file content. :return: GlossaryInfo containing information about created glossary. :raises ValueError: If the glossary name is empty, or entries are empty or invalid. :raises DeepLException: If source and target language pair are not supported for glossaries. """ entries = ( csv_data if isinstance(csv_data, (str, bytes)) else csv_data.read() ) if not isinstance(entries, (bytes, str)): raise ValueError("Entries of the glossary are invalid") return self._create_glossary( name, source_lang, target_lang, "csv", entries ) def get_glossary(self, glossary_id: str) -> GlossaryInfo: """Retrieves GlossaryInfo for the glossary with specified ID. :param glossary_id: ID of glossary to retrieve. :return: GlossaryInfo with information about specified glossary. :raises GlossaryNotFoundException: If no glossary with given ID is found. """ status, content, json = self._api_call( f"v2/glossaries/{glossary_id}", method="GET" ) self._raise_for_status(status, content, json, glossary=True) return GlossaryInfo.from_json(json) def list_glossaries(self) -> List[GlossaryInfo]: """Retrieves GlossaryInfo for all available glossaries. :return: list of GlossaryInfo for all available glossaries. """ status, content, json = self._api_call("v2/glossaries", method="GET") self._raise_for_status(status, content, json, glossary=True) glossaries = ( json.get("glossaries", []) if (json and isinstance(json, dict)) else [] ) return [GlossaryInfo.from_json(glossary) for glossary in glossaries] def get_glossary_entries(self, glossary: Union[str, GlossaryInfo]) -> dict: """Retrieves the entries of the specified glossary and returns them as a dictionary. :param glossary: GlossaryInfo or ID of glossary to retrieve. :return: dictionary of glossary entries. :raises GlossaryNotFoundException: If no glossary with given ID is found. :raises DeepLException: If the glossary could not be retrieved in the right format. """ if isinstance(glossary, GlossaryInfo): glossary_id = glossary.glossary_id else: glossary_id = glossary status, content, json = self._api_call( f"v2/glossaries/{glossary_id}/entries", method="GET", headers={"Accept": "text/tab-separated-values"}, ) self._raise_for_status(status, content, json, glossary=True) if not isinstance(content, str): raise DeepLException( "Could not get the glossary content as a string", http_status_code=status, ) return util.convert_tsv_to_dict(content) def delete_glossary(self, glossary: Union[str, GlossaryInfo]) -> None: """Deletes specified glossary. :param glossary: GlossaryInfo or ID of glossary to delete. :raises GlossaryNotFoundException: If no glossary with given ID is found. """ if isinstance(glossary, GlossaryInfo): glossary_id = glossary.glossary_id else: glossary_id = glossary status, content, json = self._api_call( f"v2/glossaries/{glossary_id}", method="DELETE", ) self._raise_for_status(status, content, json, glossary=True) DeepL-deepl-python-df50df2/deepl/util.py000066400000000000000000000144531523706276600202520ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import csv import datetime import importlib import io import itertools import logging import re from typing import Any, BinaryIO, Dict, Optional, TextIO, Union logger = logging.getLogger("deepl") def _optional_import(module_name: str): """Tries importing the specified module and returns it if successful, else None. Example: keyring = _optional_import('keyring') if keyring: keyring.get_password(...) else: # Code to handle the module not being present pass :param module_name: str containing the exact module name :return: The module, if the import was successful, or None """ try: module = importlib.import_module(module_name) return module except ImportError: return None def _get_log_text(message, **kwargs): return ( message + " " + " ".join(f"{key}={value}" for key, value in sorted(kwargs.items())) ) def log_debug(message, **kwargs): text = _get_log_text(message, **kwargs) logger.debug(text) def log_info(message, **kwargs): text = _get_log_text(message, **kwargs) logger.info(text) def get_int_safe(d: Optional[dict], key: str) -> Optional[int]: """Returns value in dictionary with given key as int, or None.""" try: return int(d.get(key) if d else None) # type: ignore[arg-type] except (TypeError, ValueError): return None def auth_key_is_free_account(auth_key: str) -> bool: """Returns True if the given authentication key belongs to a DeepL API Free account, otherwise False.""" return auth_key.endswith(":fx") def validate_glossary_term(term: str) -> None: """Checks if the given glossary term contains any disallowed characters. :param term: Glossary term to check for validity. :raises ValueError: If the term is not valid or a disallowed character is found.""" if len(term) == 0: raise ValueError(f'Term "{term}" is not a valid string') if any( ( 0 <= ord(char) <= 31 # C0 control characters or 128 <= ord(char) <= 159 # C1 control characters or char in "\u2028\u2029" # Unicode newlines ) for char in term ): raise ValueError(f'Term "{term}" contains invalid character') def convert_tsv_to_dict( tsv: str, term_separator: str = "\t", skip_checks: bool = False ) -> dict: """Converts the given tab-separated values (TSV) string to an entries dictionary for use in a glossary. Each line should contain a source and target term separated by a tab. Empty lines are ignored. :param tsv: string containing TSV to parse. :param term_separator: optional term separator to use. :param skip_checks: set to True to override entry validation. :return: dictionary containing parsed entries. """ entries_dict = {} for line, index in zip(tsv.splitlines(), itertools.count(1)): if not line: continue if term_separator not in line: raise ValueError( f"Entry {index} does not contain separator: {line}" ) source, target = line.split(term_separator, 1) source, target = source.strip(), target.strip() if source in entries_dict: raise ValueError( f'Entry {index} duplicates source term "{source}"' ) if term_separator in target: raise ValueError( f"Entry {index} contains more than one term separator: {line}" ) if not skip_checks: validate_glossary_term(source) validate_glossary_term(target) entries_dict[source] = target return entries_dict def convert_dict_to_tsv( entry_dict: Dict[str, str], skip_checks: bool = False ) -> str: """Converts the given glossary entries dictionary to a tab-separated values (TSV) string. :param entry_dict: dictionary containing glossary entries. :param skip_checks: set to True to override entry validation. :return: string containing entries in TSV format. """ if not skip_checks: for source, target in entry_dict.items(): validate_glossary_term(source.strip()) validate_glossary_term(target.strip()) return "\n".join( f"{s.strip()}\t{t.strip()}" for s, t in entry_dict.items() ) def convert_csv_to_dict( csv_data: Union[TextIO, BinaryIO, str, bytes, Any], skip_checks: bool = False, ) -> Dict[str, str]: """Converts the given comma-separated values (CSV) string to an entries dictionary for use in a glossary. Each line should contain a source and target term separated by a comma or a source term, target term, source language code, and target language code all separated by commas. Empty lines are ignored. :param tsv: string containing CSV to parse. :param skip_checks: set to True to override entry validation. :return: dictionary containing parsed entries. """ entries_dict = {} csv_string = ( csv_data if isinstance(csv_data, (str, bytes)) else csv_data.read() ) if not isinstance(csv_string, (bytes, str)): raise ValueError("Entries of the glossary are invalid") if isinstance(csv_string, bytes): csv_string = csv_string.decode("utf-8") if isinstance(csv_string, str): csv_string = io.StringIO(csv_string) reader = csv.reader(csv_string) for line, index in zip(reader, itertools.count(1)): if not line: continue source, target = line[0].strip(), line[1].strip() if source in entries_dict: raise ValueError( f'Entry {index} duplicates source term "{source}"' ) if not skip_checks: validate_glossary_term(source) validate_glossary_term(target) entries_dict[source] = target return entries_dict def parse_timestamp(creation_time: str) -> datetime.datetime: # Truncate fractional seconds to 6 digits (microseconds) since Python's # %f only handles up to 6 digits, but the API may return 7 (nanoseconds). creation_time = re.sub(r"(\.\d{6})\d+", r"\1", creation_time) return datetime.datetime.strptime(creation_time, "%Y-%m-%dT%H:%M:%S.%f%z") DeepL-deepl-python-df50df2/deepl/version.py000066400000000000000000000002471523706276600207560ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. VERSION = "1.32.0" DeepL-deepl-python-df50df2/examples/000077500000000000000000000000001523706276600174415ustar00rootroot00000000000000DeepL-deepl-python-df50df2/examples/basic_usage/000077500000000000000000000000001523706276600217065ustar00rootroot00000000000000DeepL-deepl-python-df50df2/examples/basic_usage/README.md000066400000000000000000000002671523706276600231720ustar00rootroot00000000000000# Example: Basic usage An example showing how to translate with the `deepl` python package, showcasing various features. It is also used to test the package, preventing regressions. DeepL-deepl-python-df50df2/examples/basic_usage/__main__.py000066400000000000000000000033721523706276600240050ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import io import deepl import os env_auth_key = "DEEPL_AUTH_KEY" env_server_url = "DEEPL_SERVER_URL" def main() -> None: auth_key = os.getenv(env_auth_key) server_url = os.getenv(env_server_url) if auth_key is None: raise Exception( f"Please provide authentication key via the {env_auth_key} " "environment variable or --auth_key argument" ) # Create a Translator object, and call get_usage() to validate connection translator: deepl.Translator = deepl.Translator( auth_key, server_url=server_url ) u: deepl.Usage = translator.get_usage() u.any_limit_exceeded # Use most translation features of the library _ = translator.translate_text( ["I am an example sentence", "I am another sentence"], source_lang="EN", target_lang="FR", formality=deepl.Formality.DEFAULT, tag_handling=None, ) ginfo: deepl.GlossaryInfo = translator.create_glossary( "Test Glossary", "DE", "FR", {"Hallo": "Bonjour"} ) with io.BytesIO() as output_file: doc_status: deepl.DocumentStatus = translator.translate_document( "My example document", output_file, source_lang="DE", target_lang="FR", filename="example.txt", glossary=ginfo, ) doc_status.done _ = translator.translate_text_with_glossary( ["Ich bin ein Beispielsatz.", "Ich bin noch ein Satz."], glossary=ginfo ) print("Success") # cleanup translator.delete_glossary(ginfo.glossary_id) if __name__ == "__main__": main() DeepL-deepl-python-df50df2/examples/basic_usage/mypy.ini000066400000000000000000000000251523706276600234020ustar00rootroot00000000000000[mypy] strict = true DeepL-deepl-python-df50df2/examples/json/000077500000000000000000000000001523706276600204125ustar00rootroot00000000000000DeepL-deepl-python-df50df2/examples/json/README.md000066400000000000000000000022201523706276600216650ustar00rootroot00000000000000# Example: Translation of JSON values An example showing how to translate strings within JSON objects. The script translates strings, but leaves numbers, booleans, null values and object keys untranslated. It also recurses into arrays and objects. For example: ``` { "greeting": "Good morning!", "messages": [ "This is a message.", { "text": "Here is an embedded text.", "sent": false }, null ], "active": true, "balance": 100.0 } ``` could be translated into German as: ``` { "greeting": "Guten Morgen!", "messages": [ "Dies ist eine Nachricht.", { "text": "Hier ist ein Text eingebettet.", "sent": false }, null ], "active": true, "balance": 100.0 } ``` ## Usage Install the [`deepl` Python library](../../README.md). Define your DeepL auth key as an environment variable `DEEPL_AUTH_KEY`. ``` export DEEPL_AUTH_KEY=f63c02c5-f056-... ``` Run the JSON translator by running Python on this directory: ``` python examples/json --to de '{"greeting": "Hello!"}' ``` For an explanation of the command line arguments, provide the `--help` option: ``` python examples/json --help ``` DeepL-deepl-python-df50df2/examples/json/__main__.py000066400000000000000000000051611523706276600225070ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import argparse import deepl import os from translate_json import translate_json env_auth_key = "DEEPL_AUTH_KEY" env_server_url = "DEEPL_SERVER_URL" def get_parser(prog_name): """Constructs and returns the argument parser.""" parser = argparse.ArgumentParser( prog=prog_name, description="Translate strings in JSON using the DeepL API " "(https://www.deepl.com/docs-api).", epilog="If you encounter issues while using this example, please " "report them at https://github.com/DeepLcom/deepl-python/issues", ) parser.add_argument( "--auth-key", default=None, help="authentication key as given in your DeepL account; the " f"{env_auth_key} environment variable is used as secondary fallback", ) parser.add_argument( "--server-url", default=None, metavar="URL", help=f"alternative server URL for testing; the {env_server_url} " f"environment variable may be used as secondary fallback", ) parser.add_argument( "--to", "--target-lang", dest="target_lang", required=True, help="language into which the JSON strings should be translated", ) parser.add_argument( "--from", "--source-lang", dest="source_lang", help="language of the JSON strings to be translated", ) parser.add_argument( "json", nargs="+", type=str, help="JSON to be translated. Wrap JSON in quotes to prevent " "the shell from splitting on whitespace.", ) return parser def main(): # Create a parser, reusing most of the arguments from the main CLI parser = get_parser(prog_name=None) args = parser.parse_args() auth_key = args.auth_key or os.getenv(env_auth_key) server_url = args.server_url or os.getenv(env_server_url) if auth_key is None: raise Exception( f"Please provide authentication key via the {env_auth_key} " "environment variable or --auth_key argument" ) # Create a Translator object, and call get_usage() to validate connection translator = deepl.Translator(auth_key, server_url=server_url) translator.get_usage() for json in args.json: output = translate_json( json, translator=translator, source_lang=args.source_lang, target_lang=args.target_lang, ) print(output) if __name__ == "__main__": main() DeepL-deepl-python-df50df2/examples/json/translate_json.py000066400000000000000000000057251523706276600240230ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import deepl import json import logging from typing import Any, Callable, List, Tuple from collections.abc import Iterable from deepl.translator import TextResult def batch_translate( input_handler_pairs: List[Tuple[str, Callable[[deepl.TextResult], None]]], target_lang: str, translator: deepl.Translator, **kwargs, ) -> None: """ Takes a list of translation inputs and result handlers, translates all inputs and calls each handler with the translation results. """ inputs = [input_str for input_str, _ in input_handler_pairs] handlers = [handler for _, handler in input_handler_pairs] results = translator.translate_text( inputs, target_lang=target_lang, **kwargs ) assert isinstance(results, list) for result, handler in zip(results, handlers): handler(result) def parse_json_for_translation( obj: Any, translation_candidates: List[ Tuple[str, Callable[[deepl.TextResult], None]] ], ): """ Steps into the given JSON object and adds all strings to translation candidates list """ keys: Iterable = [] if type(obj) is dict: keys = obj.keys() elif type(obj) is list: keys = range(len(obj)) else: return for key in keys: if type(obj[key]) is str: current_obj = obj current_key = key assign = lambda val: current_obj.__setitem__(current_key, val.text) translation_candidates.append((obj[key], assign)) else: parse_json_for_translation(obj[key], translation_candidates) def translate_json( json_input: str, target_lang: str, translator: deepl.Translator, **kwargs, ) -> str: """ Translates given JSON input using DeepL Translator. Most of the arguments of the translate_text function are supported, source_lang, target_lang, glossary_id, formality, etc. :param json_input: JSON input to be translated. :param target_lang: language code to translate template into, for example "DE", "EN-US", "FR". :param translator: deepl.Translator to use for translation. :return: Translated JSON. """ logger = logging.getLogger("deepl") obj = json.loads(json_input) # Wrap the JSON object in an array, in case the input is a string obj = [obj] # Find all text in the JSON that is to be translated translation_candidates: List[ Tuple[str, Callable[[deepl.TextResult], None]] ] = [] parse_json_for_translation(obj, translation_candidates) logger.info( f"Found {len(translation_candidates)} strings to be translated" ) # Translate all texts batch_translate(translation_candidates, target_lang, translator, **kwargs) logger.info("Translation complete") # Unwrap the dummy array and convert to JSON return json.dumps(obj[0]) DeepL-deepl-python-df50df2/examples/mustache/000077500000000000000000000000001523706276600212525ustar00rootroot00000000000000DeepL-deepl-python-df50df2/examples/mustache/README.md000066400000000000000000000040441523706276600225330ustar00rootroot00000000000000# Example: Translation of Mustache templates An example showing how to translate [Mustache templates][mustache-docs] using XML tag-handling. Mustache templates embed tags in literal text and HTML tags; the tags refer to keys in hashes, so they should not be translated. For example: ``` Hello {{name}}. You have just won {{value}} dollars! ``` could be translated into German as: ``` Hallo {{name}}. Sie haben gerade {{value}} Dollar gewonnen! ``` ## Usage Install the [`deepl` Python library](../../README.md). Define your DeepL auth key as an environment variable `DEEPL_AUTH_KEY`. ``` export DEEPL_AUTH_KEY=f63c02c5-f056-... ``` Run the Mustache translator by running Python on this directory: ``` python examples/mustache --to de "Hello {{name}}" ``` For an explanation of the command line arguments, provide the `--help` option: ``` python examples/mustache --help ``` ## How it works This Mustache translator uses XML tag-handling to preserve the Mustache tags while using the DeepL API to translate. It also handles HTML tags embedded in the Mustache template. 1. The input Mustache template is parsed to separate the literal text from the Mustache tags. ``` Hello {{name}}. You have just won {{value}} dollars! ^^^^^^^^ ^^^^^^^^^ ``` 2. The template is modified to replace all Mustache tags with placeholder XML tags. Unique IDs are attached to each placeholder tag to identify them in the translated XML. ``` Hello . You have just won dollars! ^^^^^^^^^^ ^^^^^^^^^^ ``` 3. The XML template is translated using DeepL API. ``` Hallo . Sie haben gerade Dollar gewonnen! ``` 4. The translated XML is parsed to identify placeholder tags and replace them with the original Mustache tags. ``` Hallo {{name}}. Sie haben gerade {{value}} Dollar gewonnen! ^^^^^^^^ ^^^^^^^^^ ``` [mustache-docs]: https://mustache.github.io/mustache.5.htmlDeepL-deepl-python-df50df2/examples/mustache/__main__.py000066400000000000000000000053151523706276600233500ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import argparse import deepl import os from mustache import translate_mustache env_auth_key = "DEEPL_AUTH_KEY" env_server_url = "DEEPL_SERVER_URL" def get_parser(prog_name): """Constructs and returns the argument parser.""" parser = argparse.ArgumentParser( prog=prog_name, description="Translate Mustache templates using the DeepL API " "(https://www.deepl.com/docs-api).", epilog="If you encounter issues while using this example, please " "report them at https://github.com/DeepLcom/deepl-python/issues", ) parser.add_argument( "--auth-key", default=None, help="authentication key as given in your DeepL account; the " f"{env_auth_key} environment variable is used as secondary fallback", ) parser.add_argument( "--server-url", default=None, metavar="URL", help=f"alternative server URL for testing; the {env_server_url} " f"environment variable may be used as secondary fallback", ) parser.add_argument( "--to", "--target-lang", dest="target_lang", required=True, help="language into which the template should be translated", ) parser.add_argument( "--from", "--source-lang", dest="source_lang", help="language of the template to be translated", ) parser.add_argument( "template", nargs="+", type=str, help="template to be translated. Wrap template in quotes to prevent " "the shell from splitting on whitespace.", ) return parser def main(): # Create a parser, reusing most of the arguments from the main CLI parser = get_parser(prog_name=None) args = parser.parse_args() auth_key = args.auth_key or os.getenv(env_auth_key) server_url = args.server_url or os.getenv(env_server_url) if auth_key is None: raise Exception( f"Please provide authentication key via the {env_auth_key} " "environment variable or --auth_key argument" ) # Create a Translator object, and call get_usage() to validate connection translator = deepl.Translator(auth_key, server_url=server_url) translator.get_usage() for template in args.template: # Call translate_mustache() to translate the Mustache template output = translate_mustache( template, translator=translator, source_lang=args.source_lang, target_lang=args.target_lang, ) print(output) if __name__ == "__main__": main() DeepL-deepl-python-df50df2/examples/mustache/html_parsing.py000066400000000000000000000055141523706276600243200ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from html.parser import HTMLParser import html from typing import List, Tuple, Dict class PassthroughHTMLParser(HTMLParser): """ An HTML parser that accumulates parsed HTML in the original HTML. The original HTML can be accessed using parsed(). Note: some HTML features are not correctly handled and reproduced: entity references, character references, declarations, processing instructions. """ def __init__(self): super().__init__(convert_charrefs=False) self._result = "" def parsed(self): return self._result def _append(self, text): self._result += text def handle_startendtag(self, tag, attrs): self._append(f"<{tag}{self._encode_attrs(attrs)} />") def handle_starttag(self, tag, attrs): self._append(f"<{tag}{self._encode_attrs(attrs)}>") def handle_endtag(self, tag): self._append(f"") def handle_charref(self, name): self._append(f"&#{name};") def handle_entityref(self, name): self._append(f"&{name};") def handle_data(self, data): self._append(data) def handle_comment(self, data): self._append(f"") def handle_decl(self, decl): self._append(f"") def handle_pi(self, data): self._append(f"") @staticmethod def _encode_attrs(attrs: List[Tuple[str, str]]) -> str: if attrs: return " " + " ".join( f'{key}="{html.escape(value)}"' for key, value in attrs ) else: return "" class TagReplacerHTMLParser(PassthroughHTMLParser): """ An HTML parser that accumulates parsed HTML unmodified, except that matching start-end tags are replaced by prepared content. :param replace_tags: A dictionary of tags to replace, with keys matching tag names ("tag") or tag names with IDs ("tag#id"), and values the text to replace matching tags with. """ def __init__(self, replace_tags: Dict[str, str]): super().__init__() self._replace_tags = replace_tags def handle_startendtag(self, tag, attrs): if self._replace_tag_if_matching(tag): return id = next((value for key, value in attrs if key == "id"), None) if id is not None and self._replace_tag_if_matching(f"{tag}#{id}"): return # If no replacement is found, fall back to passthrough parser super().handle_starttag(tag, attrs) def _replace_tag_if_matching(self, tag_and_id): if tag_and_id in self._replace_tags: stored_tag = self._replace_tags[tag_and_id] self._append(stored_tag) return True return False DeepL-deepl-python-df50df2/examples/mustache/mustache.py000066400000000000000000000105241523706276600234370ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from deepl.translator import TextResult from html_parsing import TagReplacerHTMLParser import deepl import logging from typing import Dict, Iterator, List, Optional, Tuple def tokenize_mustache( template: str, delimiters: List[Tuple[str, str]] ) -> Iterator[Tuple[str, str]]: """ Tokenizes Mustache template with given delimiters, yielding tuples of ("literal", text) or ("tag", tag-content). :param template: Mustache template text. :param delimiters: List of left and right delimiters identifying Mustache tags. """ literal = "" while template: # Find the next Mustache tag for ldel, rdel in delimiters: if not template.startswith(ldel): continue rpos = template.find(rdel, len(ldel)) if rpos == -1: continue rpos += len(rdel) yield "literal", literal literal = "" yield "tag", template[0:rpos] template = template[rpos:] break else: # Template does not begin with tag, append to literal literal += template[0] template = template[1:] if literal: yield "literal", literal def convert_mustache_to_xml( template: str, placeholder_tag: str, delimiters: List[Tuple[str, str]] ) -> Tuple[str, Dict[str, str]]: """ Converts Mustache template to XML by replacing Mustache tags with placeholder XML tags. :param template: Mustache template text. :param placeholder_tag: XML tag name for placeholder tags. :param delimiters: List of left and right delimiters identifying Mustache tags. :return XML and dictionary of replaced tags. """ xml = "" id_counter = 0 extracted_tokens = {} tokens = tokenize_mustache(template, delimiters) for tag_type, tag_content in tokens: if tag_type == "tag": tag_id = str(id_counter) id_counter += 1 xml += f'<{placeholder_tag} id="{tag_id}" />' extracted_tokens[f"{placeholder_tag}#{tag_id}"] = tag_content else: xml += tag_content return xml, extracted_tokens def translate_mustache( template: str, target_lang: str, translator: deepl.Translator, delimiters: Optional[List[Tuple[str, str]]] = None, placeholder_tag: str = "m", **kwargs, ) -> str: """ Translates given Mustache template text using DeepL Translator, by converting the template to XML. Most of the arguments of the translate_text function are supported, source_lang, target_lang, glossary_id, formality, etc. The tag_handling argument is *not* supported, because the Mustache tags are replaced by XML before translation. :param template: Mustache template to be translated. :param target_lang: language code to translate template into, for example "DE", "EN-US", "FR". :param translator: deepl.Translator to use for translation. :param delimiters: Optional. Tuples of left- and right-delimiters identifying Mustache tags. Delimiter tuples must be specified in order-of-precedence, for example {{{ must be before {{. :param placeholder_tag: Optional. Dummy XML-tag to replace Mustache tags, defaults to "m". :return: Translated Mustache template. """ if not delimiters: delimiters = [("{{{", "}}}"), ("{{", "}}")] logger = logging.getLogger("deepl") # Replace Mustache tags with placeholder XML tags placeholder_xml, replaced_tags = convert_mustache_to_xml( template, placeholder_tag, delimiters ) logger.info("XML with placeholder tags: %s", placeholder_xml) logger.debug("Placeholder tokens: %s", replaced_tags) # Translate XML with placeholder tags result = translator.translate_text( placeholder_xml, target_lang=target_lang, tag_handling="xml", **kwargs ) assert isinstance(result, TextResult) logger.info("Translated XML: %s", result.text) # Reinsert the extracted Mustache tags in the translated XML xml_parser = TagReplacerHTMLParser(replaced_tags) xml_parser.feed(result.text) xml_parser.close() translated_template = xml_parser.parsed() return translated_template DeepL-deepl-python-df50df2/examples/voice/000077500000000000000000000000001523706276600205465ustar00rootroot00000000000000DeepL-deepl-python-df50df2/examples/voice/cli/000077500000000000000000000000001523706276600213155ustar00rootroot00000000000000DeepL-deepl-python-df50df2/examples/voice/cli/README.md000066400000000000000000000053561523706276600226050ustar00rootroot00000000000000# Example: Real-time audio translation using the DeepL Voice API An example command-line interface showing how to use the [DeepL Voice API][voice-api-docs] for real-time audio transcription and translation. The CLI supports both audio file input and live microphone input, streaming audio data to the API via WebSocket and receiving transcriptions and translations as they become available. ## Usage Install dependencies for this example: ```shell cd examples/voice/cli uv sync # or poetry install ``` For microphone support, install the `mic` dependency group. You may need to install a system package for `pyaudio` first (e.g. `portaudio19-dev` on Debian/Ubuntu, `portaudio` on MacOS): ```shell uv sync --group mic # or poetry install --with mic ``` Define your DeepL Auth key as an environment variable `DEEPL_AUTH_KEY`. ```shell export DEEPL_AUTH_KEY=your-api-key-here ``` Translate an audio file to one or more target languages: ```shell uv run python deepl-voice-api-cli.py audio.mp3 de fr # or poetry run python deepl-voice-api-cli.py audio.mp3 de fr ``` For live microphone translation, use `-` as the audio file path: ```shell uv run python deepl-voice-api-cli.py - es # or poetry run python deepl-voice-api-cli.py - es ``` For an explanation of the command line arguments, provide the `--help` option: ```shell uv run python deepl-voice-api-cli.py --help # or poetry run python deepl-voice-api-cli.py --help ``` ## How it works This CLI demonstrates the DeepL Voice API streaming workflow: 1. **Request a streaming session**: The CLI sends a POST request to the DeepL Voice API to obtain a WebSocket URI with authentication token, specifying the source media type, source language (or auto-detection), and target languages. 2. **Connect via WebSocket**: The CLI establishes a WebSocket connection using the obtained URI for bidirectional streaming communication. 3. **Stream audio data**: Audio is read in chunks (from file or microphone) and sent to the WebSocket as base64-encoded JSON message. For files, chunks are sent with simulated delays; for microphone, audio is captured in real-time. 4. **Receive transcriptions and translations**: The API streams back real-time updates containing both tentative (in-progress) and concluded (finalized) transcriptions in the source language and translations in all requested target languages. 5. **Signal completion**: After all audio is sent, an `end_of_source_media` message flushes the pipeline, allowing the server to finalize processing and send complete transcripts. The CLI supports various audio formats including MP3, FLAC, OGG, WebM, and raw PCM. Microphone input automatically uses PCM format at 16kHz sample rate. [voice-api-docs]: https://developers.deepl.com/api-reference/voice DeepL-deepl-python-df50df2/examples/voice/cli/deepl-voice-api-cli.py000066400000000000000000000325611523706276600254060ustar00rootroot00000000000000# Copyright 2025 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import asyncio import base64 import json import logging import sys from urllib.parse import urlencode import aiohttp import click import websockets # Configure logging with timestamps logging.basicConfig( level=logging.INFO, format="[%(levelname)s][%(asctime)s.%(msecs)03d] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger(__name__) # Supported media content types for the DeepL Voice API supported_media_content_types = [ "audio/auto", "audio/flac", "audio/mpeg", "audio/ogg", "audio/webm", "audio/x-matroska", "audio/ogg;codecs=flac", "audio/ogg;codecs=opus", "audio/pcm;encoding=s16le;rate=8000", "audio/pcm;encoding=s16le;rate=16000", "audio/pcm;encoding=s16le;rate=44100", "audio/pcm;encoding=s16le;rate=48000", "audio/webm;codecs=opus", "audio/x-matroska;codecs=aac", "audio/x-matroska;codecs=flac", "audio/x-matroska;codecs=mp3", "audio/x-matroska;codecs=opus", ] async def get_streaming_uri( session: aiohttp.ClientSession, auth_key: str, source_media_content_type: str, source_language: str | None, target_languages: tuple[str, ...], formality: str | None, glossary_id: str | None, ): """ Get a DeepL Voice API streaming URI for real-time transcription and translation. Please refer to https://developers.deepl.com/api-reference/voice/request-stream. Args: session: aiohttp.ClientSession instance for making HTTP requests auth_key: DeepL API key for authentication source_media_content_type: content type of the audio file source_language: source language of the audio file, or None for auto-detection target_languages: tuple of target languages for translation formality: formality setting for translation glossary_id: glossary ID for translation Returns: str: WebSocket URI for real-time audio translation """ get_stream_url = "https://api.deepl.com/v3/voice/realtime" request_payload = { "source_media_content_type": source_media_content_type, "source_language": source_language, "source_language_mode": "fixed" if source_language else "auto", "target_languages": target_languages, "formality": formality, "glossary_id": glossary_id, } logger.info(f"Config: {json.dumps(request_payload, indent=2)}") logger.info(f"Obtaining stream URI from {get_stream_url}...") async with session.post( get_stream_url, json=request_payload, headers={ "Content-Type": "application/json", "Authorization": f"DeepL-Auth-Key {auth_key}", }, ) as response: response.raise_for_status() response_data = await response.json() stream_token = response_data.get("token") stream_url = response_data.get("streaming_url") stream_session_id = response_data.get("session_id") if stream_token is None or stream_url is None: raise ValueError( f"Did not receive a valid token or streaming URL from the API: {response_data.get('message')}" ) logger.info(f"Streaming Session Id: {stream_session_id}") # Return WebSocket URI with token as query parameter return f"{stream_url}?{urlencode({'token': stream_token})}" async def audio_input_provider(filename: str): """ Read an audio file and yield its content in chunks. Args: filename: path to the audio file or '-' for default audio input (microphone) Yields: tuple[int, bytes]: sequence number and audio chunk data """ if filename == "-": from pyaudio_utils import DefaultAudioInput async with DefaultAudioInput() as mic_input: logger.info("Terminate with [Ctrl-C]") sequence = 0 async for audio_chunk in mic_input: yield sequence, audio_chunk sequence += 1 else: logger.info(f"Reading audio data from file: '{filename}'") with open(filename, "rb") as file: sequence = 0 while audio_chunk := file.read( 6400 # that's 200 ms of pcm 16k s16le ): yield sequence, audio_chunk # wait 200 ms between chunks # to simulate real-time audio input. we don't know the audio length of the chunks here. # it's important to not flood the server with data too quickly, otherwise you hit # the rate limits and get disconnected. await asyncio.sleep(0.2) sequence += 1 async def websocket_listener(websocket, target_languages: tuple[str, ...]): """ Listen for and process WebSocket responses from the DeepL Voice API. Receives and logs transcript updates (both source and target), collecting concluded transcript chunks to display the full transcript as it's built. Exits when the end_of_stream message is received. Args: websocket: WebSocket connection to listen on target_languages: tuple of target languages for translation Raises: RuntimeError: if an error occurs during WebSocket communication """ try: # we collect and join transcript updates to form the full transcript full_concluded_source_transcript = "" full_concluded_target_transcript = dict.fromkeys(target_languages, "") async for message in websocket: message_data = json.loads(message) # there should only be one field set in the message assert ( len(message_data) == 1 ), f"Unexpected message format: {message_data}" if "source_transcript_update" in message_data: transcript_update = message_data["source_transcript_update"] # join all chunks of the update concluded_update = "".join( chunk["text"] for chunk in transcript_update["concluded"] ) tentative_update = "".join( chunk["text"] for chunk in transcript_update["tentative"] ) # append the concluded part to the full transcript full_concluded_source_transcript += concluded_update logger.info( f"[WS] Source Transcript Update: {full_concluded_source_transcript} [{tentative_update}]\n" ) elif "target_transcript_update" in message_data: transcript_update = message_data["target_transcript_update"] language = transcript_update["language"] # join all chunks of the update concluded_update = "".join( chunk["text"] for chunk in transcript_update["concluded"] ) tentative_update = "".join( chunk["text"] for chunk in transcript_update["tentative"] ) # append the concluded part to the full transcript full_concluded_target_transcript[language] += concluded_update logger.info( f"[WS] Target Transcript Update ({language}): {full_concluded_target_transcript[language]} [{tentative_update}]\n" ) elif "end_of_source_transcript" in message_data: logger.info( f"[WS] Final Source Transcript: {full_concluded_source_transcript}\n" ) elif "end_of_target_transcript" in message_data: language = message_data["end_of_target_transcript"]["language"] logger.info( f"[WS] Final Target Transcript ({language}): {full_concluded_target_transcript[language]}\n" ) elif "end_of_stream" in message_data: logger.info("[WS] End of Stream") # no more messages expected, we can exit the listener here return elif "error" in message_data: logger.error(f"[WS] Error received: {message_data['error']}") else: logger.warning( f"[WS] Unknown WebSocket Message: {message_data}" ) except websockets.exceptions.ConnectionClosed: logger.warning("[WebSocket Response]: Connection closed by server") except Exception as ex: raise RuntimeError("WebSocket Listener Error") from ex async def main( source_media_content_type: str, audio_file_path: str, source_language: str | None, target_languages: tuple[str, ...], formality: str | None, glossary_id: str | None, auth_key: str, ): """ Main entry function for DeepL Voice API streaming demo. Args: source_media_content_type: content type of the audio file audio_file_path: path to the audio file or '-' for default audio input (microphone) source_language: source language of the audio file, or None for auto-detection target_languages: tuple of target languages for translation formality: formality setting for translation glossary_id: glossary ID for translation auth_key: DeepL API key for authentication """ logger.info("Starting DeepL Voice API streaming demo...") # mic input implies pcm 16k source_media_content_type = ( "audio/pcm;encoding=s16le;rate=16000" if audio_file_path == "-" else source_media_content_type ) async with aiohttp.ClientSession() as session: # STEP 1: Obtain the WebSocket URI for the streaming service stream_uri = await get_streaming_uri( session, auth_key, source_media_content_type, source_language, target_languages, formality, glossary_id, ) # STEP 2: Connect to the WebSocket using the obtained URI logger.info("Connecting to WebSocket...") async with websockets.connect(stream_uri) as websocket: logger.info("Connected to WebSocket!") # STEP 3: Start listening for responses in a background task listen_task = asyncio.create_task( websocket_listener(websocket, target_languages) ) # STEP 4: load and send audio data in chunks to the WebSocket logger.info("Start sending audio data") async for sequence, audio_chunk in audio_input_provider( audio_file_path ): audio_message = json.dumps( { "source_media_chunk": { "data": base64.b64encode(audio_chunk).decode( "utf-8" ) } } ) await websocket.send(audio_message) logger.debug( f"Sent audio chunk {sequence} ({len(audio_chunk)} bytes)" ) # STEP 5: Indicate end of audio data input to the stream # This flushes the transcription/translation pipeline and # allows the server to finalize the processing of the audio. # The server will then send the final transcript updates and the indication # that the target transcripts are complete. await websocket.send(json.dumps({"end_of_source_media": {}})) logger.info("Finished sending audio data") # wait for the listening task to complete await listen_task logger.info("WebSocket connection closed") @click.command() @click.argument( "audio-file-path", type=click.Path(exists=True, allow_dash=True, dir_okay=False), ) @click.argument("target-language", type=str, nargs=-1) @click.option( "-c", "--media-content-type", type=click.Choice(supported_media_content_types), default="audio/auto", help="Content type of the audio file or auto for auto-detection (default: audio/auto)", ) @click.option( "-s", "--source-language", type=str, help="Set source language of the audio file", default=None, ) @click.option( "-f", "--formality", type=click.Choice(("formal", "informal")), help="Formality setting for translation", default=None, ) @click.option( "-g", "--glossary", "glossary_id", type=str, help="ID of glossary to use for translation", default=None, ) @click.option( "-k", "--auth-key", envvar="DEEPL_AUTH_KEY", help="DeepL Auth Key", required=True, ) def sync_main( media_content_type: str, audio_file_path: str, target_language: tuple[str, ...], source_language: str | None, formality: str | None, glossary_id: str | None, auth_key: str, ): """ DeepL Voice API streaming example CLI. Please refer to https://developers.deepl.com/api-reference/voice for the DeepL Voice API documentation. \b AUDIO_FILE_PATH: Path to the audio file to be transcribed and translated. Use '-' for default audio input (microphone). """ asyncio.run( main( media_content_type, audio_file_path, source_language, target_language, formality, glossary_id, auth_key, ) ) if __name__ == "__main__": sys.exit(sync_main()) DeepL-deepl-python-df50df2/examples/voice/cli/poetry.lock000066400000000000000000003773131523706276600235270ustar00rootroot00000000000000# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, ] [[package]] name = "aiohttp" version = "3.13.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155"}, {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c"}, {file = "aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802"}, {file = "aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f"}, {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6"}, {file = "aiohttp-3.13.2-cp310-cp310-win32.whl", hash = "sha256:e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251"}, {file = "aiohttp-3.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514"}, {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0"}, {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb"}, {file = "aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592"}, {file = "aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782"}, {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8"}, {file = "aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec"}, {file = "aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c"}, {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b"}, {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc"}, {file = "aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e"}, {file = "aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169"}, {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248"}, {file = "aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e"}, {file = "aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45"}, {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be"}, {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742"}, {file = "aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e"}, {file = "aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476"}, {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23"}, {file = "aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254"}, {file = "aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a"}, {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b"}, {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61"}, {file = "aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011"}, {file = "aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4"}, {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a"}, {file = "aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940"}, {file = "aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4"}, {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673"}, {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd"}, {file = "aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e"}, {file = "aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be"}, {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c"}, {file = "aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734"}, {file = "aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f"}, {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7fbdf5ad6084f1940ce88933de34b62358d0f4a0b6ec097362dcd3e5a65a4989"}, {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c3a50345635a02db61792c85bb86daffac05330f6473d524f1a4e3ef9d0046d"}, {file = "aiohttp-3.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e87dff73f46e969af38ab3f7cb75316a7c944e2e574ff7c933bc01b10def7f5"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2adebd4577724dcae085665f294cc57c8701ddd4d26140504db622b8d566d7aa"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e036a3a645fe92309ec34b918394bb377950cbb43039a97edae6c08db64b23e2"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:23ad365e30108c422d0b4428cf271156dd56790f6dd50d770b8e360e6c5ab2e6"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f9b2c2d4b9d958b1f9ae0c984ec1dd6b6689e15c75045be8ccb4011426268ca"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a92cf4b9bea33e15ecbaa5c59921be0f23222608143d025c989924f7e3e0c07"}, {file = "aiohttp-3.13.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:070599407f4954021509193404c4ac53153525a19531051661440644728ba9a7"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:29562998ec66f988d49fb83c9b01694fa927186b781463f376c5845c121e4e0b"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4dd3db9d0f4ebca1d887d76f7cdbcd1116ac0d05a9221b9dad82c64a62578c4d"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d7bc4b7f9c4921eba72677cd9fedd2308f4a4ca3e12fab58935295ad9ea98700"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dacd50501cd017f8cccb328da0c90823511d70d24a323196826d923aad865901"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8b2f1414f6a1e0683f212ec80e813f4abef94c739fd090b66c9adf9d2a05feac"}, {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04c3971421576ed24c191f610052bcb2f059e395bc2489dd99e397f9bc466329"}, {file = "aiohttp-3.13.2-cp39-cp39-win32.whl", hash = "sha256:9f377d0a924e5cc94dc620bc6366fc3e889586a7f18b748901cf016c916e2084"}, {file = "aiohttp-3.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c705601e16c03466cb72011bd1af55d68fa65b045356d8f96c216e5f6db0fa5"}, {file = "aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca"}, ] [package.dependencies] aiohappyeyeballs = ">=2.5.0" aiosignal = ">=1.4.0" attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, ] [package.dependencies] frozenlist = ">=1.1.0" typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] name = "attrs" version = "25.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"}, {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"}, ] [[package]] name = "black" version = "25.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, {file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"}, {file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"}, {file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"}, {file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"}, {file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"}, {file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"}, {file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"}, {file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"}, {file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"}, {file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"}, {file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"}, {file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"}, {file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"}, {file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"}, {file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"}, {file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"}, {file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"}, {file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"}, {file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"}, {file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"}, {file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"}, {file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"}, {file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"}, {file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" pytokens = ">=0.3.0" [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" version = "2025.11.12" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] name = "click" version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["main", "dev"] markers = "platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "frozenlist" version = "1.8.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565"}, {file = "frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad"}, {file = "frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2"}, {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186"}, {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e"}, {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450"}, {file = "frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef"}, {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4"}, {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff"}, {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c"}, {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f"}, {file = "frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7"}, {file = "frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a"}, {file = "frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6"}, {file = "frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e"}, {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84"}, {file = "frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9"}, {file = "frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93"}, {file = "frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f"}, {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695"}, {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52"}, {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581"}, {file = "frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567"}, {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b"}, {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92"}, {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d"}, {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd"}, {file = "frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967"}, {file = "frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25"}, {file = "frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b"}, {file = "frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a"}, {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1"}, {file = "frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b"}, {file = "frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4"}, {file = "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383"}, {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4"}, {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8"}, {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b"}, {file = "frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52"}, {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29"}, {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3"}, {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143"}, {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608"}, {file = "frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa"}, {file = "frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf"}, {file = "frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746"}, {file = "frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd"}, {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a"}, {file = "frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7"}, {file = "frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40"}, {file = "frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027"}, {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822"}, {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121"}, {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5"}, {file = "frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e"}, {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11"}, {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1"}, {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1"}, {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8"}, {file = "frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed"}, {file = "frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496"}, {file = "frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231"}, {file = "frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62"}, {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94"}, {file = "frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c"}, {file = "frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52"}, {file = "frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51"}, {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65"}, {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82"}, {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714"}, {file = "frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d"}, {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506"}, {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51"}, {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e"}, {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0"}, {file = "frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41"}, {file = "frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b"}, {file = "frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888"}, {file = "frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042"}, {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47"}, {file = "frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca"}, {file = "frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068"}, {file = "frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95"}, {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459"}, {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675"}, {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61"}, {file = "frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6"}, {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5"}, {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3"}, {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1"}, {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178"}, {file = "frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda"}, {file = "frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087"}, {file = "frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a"}, {file = "frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103"}, {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, ] [[package]] name = "idna" version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "multidict" version = "6.7.0" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349"}, {file = "multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e"}, {file = "multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3"}, {file = "multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62"}, {file = "multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111"}, {file = "multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36"}, {file = "multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85"}, {file = "multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7"}, {file = "multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0"}, {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc"}, {file = "multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721"}, {file = "multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6"}, {file = "multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8"}, {file = "multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b"}, {file = "multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34"}, {file = "multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff"}, {file = "multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81"}, {file = "multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912"}, {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184"}, {file = "multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45"}, {file = "multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa"}, {file = "multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1"}, {file = "multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a"}, {file = "multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8"}, {file = "multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4"}, {file = "multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b"}, {file = "multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec"}, {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6"}, {file = "multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159"}, {file = "multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca"}, {file = "multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf"}, {file = "multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd"}, {file = "multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288"}, {file = "multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17"}, {file = "multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390"}, {file = "multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e"}, {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00"}, {file = "multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb"}, {file = "multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad"}, {file = "multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762"}, {file = "multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6"}, {file = "multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d"}, {file = "multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6"}, {file = "multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792"}, {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842"}, {file = "multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b"}, {file = "multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38"}, {file = "multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1"}, {file = "multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f"}, {file = "multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f"}, {file = "multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885"}, {file = "multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c"}, {file = "multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000"}, {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63"}, {file = "multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718"}, {file = "multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a"}, {file = "multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9"}, {file = "multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0"}, {file = "multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13"}, {file = "multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd"}, {file = "multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827"}, {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c"}, {file = "multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40"}, {file = "multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851"}, {file = "multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e"}, {file = "multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e"}, {file = "multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4"}, {file = "multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91"}, {file = "multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f"}, {file = "multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546"}, {file = "multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3"}, {file = "multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5"}, ] [[package]] name = "mypy-extensions" version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "packaging" version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" version = "4.5.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"}, {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"}, ] [package.extras] docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] type = ["mypy (>=1.18.2)"] [[package]] name = "propcache" version = "0.4.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db"}, {file = "propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8"}, {file = "propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925"}, {file = "propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21"}, {file = "propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5"}, {file = "propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db"}, {file = "propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7"}, {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4"}, {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60"}, {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f"}, {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900"}, {file = "propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c"}, {file = "propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb"}, {file = "propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37"}, {file = "propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581"}, {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf"}, {file = "propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5"}, {file = "propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e"}, {file = "propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566"}, {file = "propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165"}, {file = "propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc"}, {file = "propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48"}, {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570"}, {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85"}, {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e"}, {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757"}, {file = "propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f"}, {file = "propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1"}, {file = "propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6"}, {file = "propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239"}, {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2"}, {file = "propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403"}, {file = "propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207"}, {file = "propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72"}, {file = "propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367"}, {file = "propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4"}, {file = "propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf"}, {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3"}, {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778"}, {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6"}, {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9"}, {file = "propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75"}, {file = "propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8"}, {file = "propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db"}, {file = "propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1"}, {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf"}, {file = "propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311"}, {file = "propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74"}, {file = "propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe"}, {file = "propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af"}, {file = "propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c"}, {file = "propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f"}, {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1"}, {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24"}, {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa"}, {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61"}, {file = "propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66"}, {file = "propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81"}, {file = "propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e"}, {file = "propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1"}, {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b"}, {file = "propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566"}, {file = "propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835"}, {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e"}, {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859"}, {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b"}, {file = "propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0"}, {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af"}, {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393"}, {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874"}, {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7"}, {file = "propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1"}, {file = "propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717"}, {file = "propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37"}, {file = "propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a"}, {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d233076ccf9e450c8b3bc6720af226b898ef5d051a2d145f7d765e6e9f9bcff"}, {file = "propcache-0.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:357f5bb5c377a82e105e44bd3d52ba22b616f7b9773714bff93573988ef0a5fb"}, {file = "propcache-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbc3b6dfc728105b2a57c06791eb07a94229202ea75c59db644d7d496b698cac"}, {file = "propcache-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:182b51b421f0501952d938dc0b0eb45246a5b5153c50d42b495ad5fb7517c888"}, {file = "propcache-0.4.1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b536b39c5199b96fc6245eb5fb796c497381d3942f169e44e8e392b29c9ebcc"}, {file = "propcache-0.4.1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:db65d2af507bbfbdcedb254a11149f894169d90488dd3e7190f7cdcb2d6cd57a"}, {file = "propcache-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd2dbc472da1f772a4dae4fa24be938a6c544671a912e30529984dd80400cd88"}, {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:daede9cd44e0f8bdd9e6cc9a607fc81feb80fae7a5fc6cecaff0e0bb32e42d00"}, {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:71b749281b816793678ae7f3d0d84bd36e694953822eaad408d682efc5ca18e0"}, {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:0002004213ee1f36cfb3f9a42b5066100c44276b9b72b4e1504cddd3d692e86e"}, {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fe49d0a85038f36ba9e3ffafa1103e61170b28e95b16622e11be0a0ea07c6781"}, {file = "propcache-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:99d43339c83aaf4d32bda60928231848eee470c6bda8d02599cc4cebe872d183"}, {file = "propcache-0.4.1-cp39-cp39-win32.whl", hash = "sha256:a129e76735bc792794d5177069691c3217898b9f5cee2b2661471e52ffe13f19"}, {file = "propcache-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:948dab269721ae9a87fd16c514a0a2c2a1bdb23a9a61b969b0f9d9ee2968546f"}, {file = "propcache-0.4.1-cp39-cp39-win_arm64.whl", hash = "sha256:5fd37c406dd6dc85aa743e214cef35dc54bbdd1419baac4f6ae5e5b1a2976938"}, {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, ] [[package]] name = "pyaudio" version = "0.2.14" description = "Cross-platform audio I/O with PortAudio" optional = false python-versions = "*" groups = ["mic"] files = [ {file = "PyAudio-0.2.14-cp310-cp310-win32.whl", hash = "sha256:126065b5e82a1c03ba16e7c0404d8f54e17368836e7d2d92427358ad44fefe61"}, {file = "PyAudio-0.2.14-cp310-cp310-win_amd64.whl", hash = "sha256:2a166fc88d435a2779810dd2678354adc33499e9d4d7f937f28b20cc55893e83"}, {file = "PyAudio-0.2.14-cp311-cp311-win32.whl", hash = "sha256:506b32a595f8693811682ab4b127602d404df7dfc453b499c91a80d0f7bad289"}, {file = "PyAudio-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:bbeb01d36a2f472ae5ee5e1451cacc42112986abe622f735bb870a5db77cf903"}, {file = "PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b"}, {file = "PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3"}, {file = "PyAudio-0.2.14-cp313-cp313-win32.whl", hash = "sha256:95328285b4dab57ea8c52a4a996cb52be6d629353315be5bfda403d15932a497"}, {file = "PyAudio-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:692d8c1446f52ed2662120bcd9ddcb5aa2b71f38bda31e58b19fb4672fffba69"}, {file = "PyAudio-0.2.14-cp38-cp38-win32.whl", hash = "sha256:858caf35b05c26d8fc62f1efa2e8f53d5fa1a01164842bd622f70ddc41f55000"}, {file = "PyAudio-0.2.14-cp38-cp38-win_amd64.whl", hash = "sha256:2dac0d6d675fe7e181ba88f2de88d321059b69abd52e3f4934a8878e03a7a074"}, {file = "PyAudio-0.2.14-cp39-cp39-win32.whl", hash = "sha256:f745109634a7c19fa4d6b8b7d6967c3123d988c9ade0cd35d4295ee1acdb53e9"}, {file = "PyAudio-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:009f357ee5aa6bc8eb19d69921cd30e98c42cddd34210615d592a71d09c4bd57"}, {file = "PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87"}, ] [package.extras] test = ["numpy"] [[package]] name = "pytokens" version = "0.3.0" description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"}, {file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"}, ] [package.extras] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] [[package]] name = "typing-extensions" version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main"] markers = "python_version < \"3.13\"" files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] name = "websockets" version = "15.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, ] [[package]] name = "yarl" version = "1.22.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e"}, {file = "yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f"}, {file = "yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf"}, {file = "yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a"}, {file = "yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c"}, {file = "yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147"}, {file = "yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb"}, {file = "yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6"}, {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0"}, {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda"}, {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc"}, {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737"}, {file = "yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467"}, {file = "yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea"}, {file = "yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca"}, {file = "yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b"}, {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511"}, {file = "yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6"}, {file = "yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028"}, {file = "yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d"}, {file = "yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503"}, {file = "yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65"}, {file = "yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e"}, {file = "yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d"}, {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7"}, {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967"}, {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed"}, {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6"}, {file = "yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e"}, {file = "yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca"}, {file = "yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b"}, {file = "yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376"}, {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f"}, {file = "yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2"}, {file = "yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74"}, {file = "yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df"}, {file = "yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb"}, {file = "yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2"}, {file = "yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82"}, {file = "yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a"}, {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124"}, {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa"}, {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7"}, {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d"}, {file = "yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520"}, {file = "yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8"}, {file = "yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c"}, {file = "yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74"}, {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53"}, {file = "yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a"}, {file = "yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c"}, {file = "yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601"}, {file = "yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a"}, {file = "yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df"}, {file = "yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2"}, {file = "yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b"}, {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273"}, {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a"}, {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d"}, {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02"}, {file = "yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67"}, {file = "yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95"}, {file = "yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d"}, {file = "yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b"}, {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10"}, {file = "yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3"}, {file = "yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9"}, {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f"}, {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0"}, {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e"}, {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708"}, {file = "yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f"}, {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d"}, {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8"}, {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5"}, {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f"}, {file = "yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62"}, {file = "yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03"}, {file = "yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249"}, {file = "yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b"}, {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4"}, {file = "yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683"}, {file = "yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b"}, {file = "yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e"}, {file = "yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590"}, {file = "yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2"}, {file = "yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da"}, {file = "yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784"}, {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b"}, {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694"}, {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d"}, {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd"}, {file = "yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da"}, {file = "yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2"}, {file = "yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79"}, {file = "yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33"}, {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1"}, {file = "yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca"}, {file = "yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53"}, {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c"}, {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf"}, {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face"}, {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b"}, {file = "yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486"}, {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138"}, {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a"}, {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529"}, {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093"}, {file = "yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c"}, {file = "yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e"}, {file = "yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27"}, {file = "yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1"}, {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748"}, {file = "yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859"}, {file = "yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9"}, {file = "yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054"}, {file = "yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b"}, {file = "yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60"}, {file = "yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890"}, {file = "yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba"}, {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca"}, {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba"}, {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b"}, {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e"}, {file = "yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8"}, {file = "yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b"}, {file = "yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed"}, {file = "yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2"}, {file = "yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff"}, {file = "yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.11" content-hash = "fb951d4130baf2ebf819ecb8de2ca54e8b960d146fe6506f37e4ecff053555c5" DeepL-deepl-python-df50df2/examples/voice/cli/pyaudio_utils.py000066400000000000000000000106741523706276600245710ustar00rootroot00000000000000# Copyright 2025 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import asyncio import contextlib import logging import threading try: import pyaudio except Exception as ex: raise ImportError( "PyAudio is required for audio input functionality.\nPlease install the environment with '--group mic'" ) from ex logger = logging.getLogger(__name__) class DefaultAudioInput(contextlib.AbstractAsyncContextManager): """ Async context manager for capturing audio from the default system input device. This class provides an async iterator interface for streaming audio data from the default microphone in real-time. Audio is captured in a background thread and made available through an async queue. Audio format: - Sample rate: 16kHz - Format: 16-bit signed PCM (paInt16) - Channels: 1 (mono) - Chunk size: 1600 frames (100ms of audio) """ def __init__(self): """Initialize the audio input handler with empty state.""" self.stream = None self.queue = asyncio.Queue( maxsize=300 # equivalent to 30 seconds of audio ) self.pa = None self.close_event = threading.Event() self.reader_thread = None async def __aenter__(self): """ Start the audio capture background thread when entering the context. """ self.reader_thread = threading.Thread( target=self._reader_worker, args=(asyncio.get_running_loop(),), daemon=True, ) self.close_event.clear() self.reader_thread.start() # Wait for the stream to initialize await asyncio.sleep(1) return self async def __aexit__(self, exc_type, exc_val, exc_tb): """ Signals the background thread to stop and waits for it to complete. """ self.close_event.set() await asyncio.to_thread(self.reader_thread.join) logger.info("Audio input stream closed") async def __aiter__(self): """ Async iterator that yields audio chunks as they become available. Yields: bytes: raw audio data chunks in 16-bit PCM format """ while audio_chunk := await self.queue.get(): yield audio_chunk self.queue.task_done() def _reader_worker(self, eventloop: asyncio.AbstractEventLoop): """ Background thread worker that reads audio data from the microphone. This method runs in a separate thread to avoid blocking the async event loop. It continuously reads audio chunks from the default input device and places them in the queue for consumption by the async iterator. Args: eventloop: asyncio event loop to use for thread-safe queue operations """ pa = pyaudio.PyAudio() frames_per_chunk = 1600 # 100ms of audio at 16kHz default_device = pa.get_default_input_device_info() # Verify the device supports the required audio format if not pa.is_format_supported( 16000, # Sample rate input_device=default_device["index"], input_channels=default_device["maxInputChannels"], input_format=pyaudio.paInt16, ): raise RuntimeError( f"Requested audio format not supported by device '{default_device['name']}'" ) # Open the audio stream with the required configuration self.stream = pa.open( format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=frames_per_chunk, start=True, input_device_index=default_device["index"], ) logger.info( f"Reading audio data from default input device: '{default_device['name']}'" ) # Read audio chunks until close is signaled while self.stream.is_active() and not self.close_event.is_set(): chunk = self.stream.read( frames_per_chunk, exception_on_overflow=True ) if not eventloop.is_running(): break # Thread-safe queue operation using the event loop eventloop.call_soon_threadsafe(self.queue.put_nowait, chunk) # Clean up PyAudio resources self.stream.stop_stream() self.stream.close() pa.terminate() DeepL-deepl-python-df50df2/examples/voice/cli/pyproject.toml000066400000000000000000000013111523706276600242250ustar00rootroot00000000000000# Copyright 2025 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. [project] name = "deepl-voice-api-cli" version = "1.0.0" requires-python = ">=3.11" dependencies = [ "aiohttp>=3.13.2", "certifi>=2025.11.12", "click>=8.3.1", "websockets>=15.0.1", ] # (supported by uv) [dependency-groups] dev = [ "black>=25.11.0", ] mic = [ "pyaudio>=0.2.14", ] # poetry configuration needs non-package mode [tool.poetry] package-mode = false # (for poetry compatibility) [tool.poetry.group.dev.dependencies] black = ">=25.11.0" [tool.poetry.group.mic.dependencies] pyaudio = ">=0.2.14" [tool.black] line-length = 79 DeepL-deepl-python-df50df2/examples/voice/cli/uv.lock000066400000000000000000005601241523706276600226310ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.11" [[package]] name = "aiohappyeyeballs" version = "2.6.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, ] [[package]] name = "aiohttp" version = "3.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, { name = "propcache" }, { name = "yarl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, { url = "https://files.pythonhosted.org/packages/bf/78/7e90ca79e5aa39f9694dcfd74f4720782d3c6828113bb1f3197f7e7c4a56/aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be", size = 732139, upload-time = "2025-10-28T20:57:02.455Z" }, { url = "https://files.pythonhosted.org/packages/db/ed/1f59215ab6853fbaa5c8495fa6cbc39edfc93553426152b75d82a5f32b76/aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742", size = 490082, upload-time = "2025-10-28T20:57:04.784Z" }, { url = "https://files.pythonhosted.org/packages/68/7b/fe0fe0f5e05e13629d893c760465173a15ad0039c0a5b0d0040995c8075e/aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293", size = 489035, upload-time = "2025-10-28T20:57:06.894Z" }, { url = "https://files.pythonhosted.org/packages/d2/04/db5279e38471b7ac801d7d36a57d1230feeee130bbe2a74f72731b23c2b1/aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811", size = 1720387, upload-time = "2025-10-28T20:57:08.685Z" }, { url = "https://files.pythonhosted.org/packages/31/07/8ea4326bd7dae2bd59828f69d7fdc6e04523caa55e4a70f4a8725a7e4ed2/aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a", size = 1688314, upload-time = "2025-10-28T20:57:10.693Z" }, { url = "https://files.pythonhosted.org/packages/48/ab/3d98007b5b87ffd519d065225438cc3b668b2f245572a8cb53da5dd2b1bc/aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4", size = 1756317, upload-time = "2025-10-28T20:57:12.563Z" }, { url = "https://files.pythonhosted.org/packages/97/3d/801ca172b3d857fafb7b50c7c03f91b72b867a13abca982ed6b3081774ef/aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a", size = 1858539, upload-time = "2025-10-28T20:57:14.623Z" }, { url = "https://files.pythonhosted.org/packages/f7/0d/4764669bdf47bd472899b3d3db91fffbe925c8e3038ec591a2fd2ad6a14d/aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e", size = 1739597, upload-time = "2025-10-28T20:57:16.399Z" }, { url = "https://files.pythonhosted.org/packages/c4/52/7bd3c6693da58ba16e657eb904a5b6decfc48ecd06e9ac098591653b1566/aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb", size = 1555006, upload-time = "2025-10-28T20:57:18.288Z" }, { url = "https://files.pythonhosted.org/packages/48/30/9586667acec5993b6f41d2ebcf96e97a1255a85f62f3c653110a5de4d346/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded", size = 1683220, upload-time = "2025-10-28T20:57:20.241Z" }, { url = "https://files.pythonhosted.org/packages/71/01/3afe4c96854cfd7b30d78333852e8e851dceaec1c40fd00fec90c6402dd2/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b", size = 1712570, upload-time = "2025-10-28T20:57:22.253Z" }, { url = "https://files.pythonhosted.org/packages/11/2c/22799d8e720f4697a9e66fd9c02479e40a49de3de2f0bbe7f9f78a987808/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8", size = 1733407, upload-time = "2025-10-28T20:57:24.37Z" }, { url = "https://files.pythonhosted.org/packages/34/cb/90f15dd029f07cebbd91f8238a8b363978b530cd128488085b5703683594/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04", size = 1550093, upload-time = "2025-10-28T20:57:26.257Z" }, { url = "https://files.pythonhosted.org/packages/69/46/12dce9be9d3303ecbf4d30ad45a7683dc63d90733c2d9fe512be6716cd40/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476", size = 1758084, upload-time = "2025-10-28T20:57:28.349Z" }, { url = "https://files.pythonhosted.org/packages/f9/c8/0932b558da0c302ffd639fc6362a313b98fdf235dc417bc2493da8394df7/aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23", size = 1716987, upload-time = "2025-10-28T20:57:30.233Z" }, { url = "https://files.pythonhosted.org/packages/5d/8b/f5bd1a75003daed099baec373aed678f2e9b34f2ad40d85baa1368556396/aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254", size = 425859, upload-time = "2025-10-28T20:57:32.105Z" }, { url = "https://files.pythonhosted.org/packages/5d/28/a8a9fc6957b2cee8902414e41816b5ab5536ecf43c3b1843c10e82c559b2/aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a", size = 452192, upload-time = "2025-10-28T20:57:34.166Z" }, { url = "https://files.pythonhosted.org/packages/9b/36/e2abae1bd815f01c957cbf7be817b3043304e1c87bad526292a0410fdcf9/aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b", size = 735234, upload-time = "2025-10-28T20:57:36.415Z" }, { url = "https://files.pythonhosted.org/packages/ca/e3/1ee62dde9b335e4ed41db6bba02613295a0d5b41f74a783c142745a12763/aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61", size = 490733, upload-time = "2025-10-28T20:57:38.205Z" }, { url = "https://files.pythonhosted.org/packages/1a/aa/7a451b1d6a04e8d15a362af3e9b897de71d86feac3babf8894545d08d537/aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4", size = 491303, upload-time = "2025-10-28T20:57:40.122Z" }, { url = "https://files.pythonhosted.org/packages/57/1e/209958dbb9b01174870f6a7538cd1f3f28274fdbc88a750c238e2c456295/aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b", size = 1717965, upload-time = "2025-10-28T20:57:42.28Z" }, { url = "https://files.pythonhosted.org/packages/08/aa/6a01848d6432f241416bc4866cae8dc03f05a5a884d2311280f6a09c73d6/aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694", size = 1667221, upload-time = "2025-10-28T20:57:44.869Z" }, { url = "https://files.pythonhosted.org/packages/87/4f/36c1992432d31bbc789fa0b93c768d2e9047ec8c7177e5cd84ea85155f36/aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906", size = 1757178, upload-time = "2025-10-28T20:57:47.216Z" }, { url = "https://files.pythonhosted.org/packages/ac/b4/8e940dfb03b7e0f68a82b88fd182b9be0a65cb3f35612fe38c038c3112cf/aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9", size = 1838001, upload-time = "2025-10-28T20:57:49.337Z" }, { url = "https://files.pythonhosted.org/packages/d7/ef/39f3448795499c440ab66084a9db7d20ca7662e94305f175a80f5b7e0072/aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011", size = 1716325, upload-time = "2025-10-28T20:57:51.327Z" }, { url = "https://files.pythonhosted.org/packages/d7/51/b311500ffc860b181c05d91c59a1313bdd05c82960fdd4035a15740d431e/aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6", size = 1547978, upload-time = "2025-10-28T20:57:53.554Z" }, { url = "https://files.pythonhosted.org/packages/31/64/b9d733296ef79815226dab8c586ff9e3df41c6aff2e16c06697b2d2e6775/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213", size = 1682042, upload-time = "2025-10-28T20:57:55.617Z" }, { url = "https://files.pythonhosted.org/packages/3f/30/43d3e0f9d6473a6db7d472104c4eff4417b1e9df01774cb930338806d36b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49", size = 1680085, upload-time = "2025-10-28T20:57:57.59Z" }, { url = "https://files.pythonhosted.org/packages/16/51/c709f352c911b1864cfd1087577760ced64b3e5bee2aa88b8c0c8e2e4972/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae", size = 1728238, upload-time = "2025-10-28T20:57:59.525Z" }, { url = "https://files.pythonhosted.org/packages/19/e2/19bd4c547092b773caeb48ff5ae4b1ae86756a0ee76c16727fcfd281404b/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa", size = 1544395, upload-time = "2025-10-28T20:58:01.914Z" }, { url = "https://files.pythonhosted.org/packages/cf/87/860f2803b27dfc5ed7be532832a3498e4919da61299b4a1f8eb89b8ff44d/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4", size = 1742965, upload-time = "2025-10-28T20:58:03.972Z" }, { url = "https://files.pythonhosted.org/packages/67/7f/db2fc7618925e8c7a601094d5cbe539f732df4fb570740be88ed9e40e99a/aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a", size = 1697585, upload-time = "2025-10-28T20:58:06.189Z" }, { url = "https://files.pythonhosted.org/packages/0c/07/9127916cb09bb38284db5036036042b7b2c514c8ebaeee79da550c43a6d6/aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940", size = 431621, upload-time = "2025-10-28T20:58:08.636Z" }, { url = "https://files.pythonhosted.org/packages/fb/41/554a8a380df6d3a2bba8a7726429a23f4ac62aaf38de43bb6d6cde7b4d4d/aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4", size = 457627, upload-time = "2025-10-28T20:58:11Z" }, { url = "https://files.pythonhosted.org/packages/c7/8e/3824ef98c039d3951cb65b9205a96dd2b20f22241ee17d89c5701557c826/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673", size = 767360, upload-time = "2025-10-28T20:58:13.358Z" }, { url = "https://files.pythonhosted.org/packages/a4/0f/6a03e3fc7595421274fa34122c973bde2d89344f8a881b728fa8c774e4f1/aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd", size = 504616, upload-time = "2025-10-28T20:58:15.339Z" }, { url = "https://files.pythonhosted.org/packages/c6/aa/ed341b670f1bc8a6f2c6a718353d13b9546e2cef3544f573c6a1ff0da711/aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3", size = 509131, upload-time = "2025-10-28T20:58:17.693Z" }, { url = "https://files.pythonhosted.org/packages/7f/f0/c68dac234189dae5c4bbccc0f96ce0cc16b76632cfc3a08fff180045cfa4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf", size = 1864168, upload-time = "2025-10-28T20:58:20.113Z" }, { url = "https://files.pythonhosted.org/packages/8f/65/75a9a76db8364b5d0e52a0c20eabc5d52297385d9af9c35335b924fafdee/aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e", size = 1719200, upload-time = "2025-10-28T20:58:22.583Z" }, { url = "https://files.pythonhosted.org/packages/f5/55/8df2ed78d7f41d232f6bd3ff866b6f617026551aa1d07e2f03458f964575/aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5", size = 1843497, upload-time = "2025-10-28T20:58:24.672Z" }, { url = "https://files.pythonhosted.org/packages/e9/e0/94d7215e405c5a02ccb6a35c7a3a6cfff242f457a00196496935f700cde5/aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad", size = 1935703, upload-time = "2025-10-28T20:58:26.758Z" }, { url = "https://files.pythonhosted.org/packages/0b/78/1eeb63c3f9b2d1015a4c02788fb543141aad0a03ae3f7a7b669b2483f8d4/aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e", size = 1792738, upload-time = "2025-10-28T20:58:29.787Z" }, { url = "https://files.pythonhosted.org/packages/41/75/aaf1eea4c188e51538c04cc568040e3082db263a57086ea74a7d38c39e42/aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61", size = 1624061, upload-time = "2025-10-28T20:58:32.529Z" }, { url = "https://files.pythonhosted.org/packages/9b/c2/3b6034de81fbcc43de8aeb209073a2286dfb50b86e927b4efd81cf848197/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661", size = 1789201, upload-time = "2025-10-28T20:58:34.618Z" }, { url = "https://files.pythonhosted.org/packages/c9/38/c15dcf6d4d890217dae79d7213988f4e5fe6183d43893a9cf2fe9e84ca8d/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98", size = 1776868, upload-time = "2025-10-28T20:58:38.835Z" }, { url = "https://files.pythonhosted.org/packages/04/75/f74fd178ac81adf4f283a74847807ade5150e48feda6aef024403716c30c/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693", size = 1790660, upload-time = "2025-10-28T20:58:41.507Z" }, { url = "https://files.pythonhosted.org/packages/e7/80/7368bd0d06b16b3aba358c16b919e9c46cf11587dc572091031b0e9e3ef0/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a", size = 1617548, upload-time = "2025-10-28T20:58:43.674Z" }, { url = "https://files.pythonhosted.org/packages/7d/4b/a6212790c50483cb3212e507378fbe26b5086d73941e1ec4b56a30439688/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be", size = 1817240, upload-time = "2025-10-28T20:58:45.787Z" }, { url = "https://files.pythonhosted.org/packages/ff/f7/ba5f0ba4ea8d8f3c32850912944532b933acbf0f3a75546b89269b9b7dde/aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c", size = 1762334, upload-time = "2025-10-28T20:58:47.936Z" }, { url = "https://files.pythonhosted.org/packages/7e/83/1a5a1856574588b1cad63609ea9ad75b32a8353ac995d830bf5da9357364/aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734", size = 464685, upload-time = "2025-10-28T20:58:50.642Z" }, { url = "https://files.pythonhosted.org/packages/9f/4d/d22668674122c08f4d56972297c51a624e64b3ed1efaa40187607a7cb66e/aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f", size = 498093, upload-time = "2025-10-28T20:58:52.782Z" }, ] [[package]] name = "aiosignal" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, ] [[package]] name = "attrs" version = "25.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] name = "black" version = "25.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "mypy-extensions" }, { name = "packaging" }, { name = "pathspec" }, { name = "platformdirs" }, { name = "pytokens" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" }, { url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" }, { url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" }, { url = "https://files.pythonhosted.org/packages/34/b9/f17dea34eecb7cc2609a89627d480fb6caea7b86190708eaa7eb15ed25e7/black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", size = 1352904, upload-time = "2025-11-10T01:59:26.252Z" }, { url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" }, { url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" }, { url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" }, { url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" }, { url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" }, { url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" }, { url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" }, { url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" }, { url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" }, { url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" }, { url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" }, { url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" }, { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, ] [[package]] name = "certifi" version = "2025.11.12" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] name = "click" version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "deepl-voice-api-cli" version = "1.0.0" source = { virtual = "." } dependencies = [ { name = "aiohttp" }, { name = "certifi" }, { name = "click" }, { name = "websockets" }, ] [package.dev-dependencies] dev = [ { name = "black" }, ] mic = [ { name = "pyaudio" }, ] [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.13.2" }, { name = "certifi", specifier = ">=2025.11.12" }, { name = "click", specifier = ">=8.3.1" }, { name = "websockets", specifier = ">=15.0.1" }, ] [package.metadata.requires-dev] dev = [{ name = "black", specifier = ">=25.11.0" }] mic = [{ name = "pyaudio", specifier = ">=0.2.14" }] [[package]] name = "frozenlist" version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] name = "multidict" version = "6.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] name = "pathspec" version = "0.12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "platformdirs" version = "4.5.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, ] [[package]] name = "propcache" version = "0.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] [[package]] name = "pyaudio" version = "0.2.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87", size = 47066, upload-time = "2023-11-07T07:11:48.806Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/f0/b0eab89eafa70a86b7b566a4df2f94c7880a2d483aa8de1c77d335335b5b/PyAudio-0.2.14-cp311-cp311-win32.whl", hash = "sha256:506b32a595f8693811682ab4b127602d404df7dfc453b499c91a80d0f7bad289", size = 144624, upload-time = "2023-11-07T07:11:36.94Z" }, { url = "https://files.pythonhosted.org/packages/82/d8/f043c854aad450a76e476b0cf9cda1956419e1dacf1062eb9df3c0055abe/PyAudio-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:bbeb01d36a2f472ae5ee5e1451cacc42112986abe622f735bb870a5db77cf903", size = 164070, upload-time = "2023-11-07T07:11:38.579Z" }, { url = "https://files.pythonhosted.org/packages/8d/45/8d2b76e8f6db783f9326c1305f3f816d4a12c8eda5edc6a2e1d03c097c3b/PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b", size = 144750, upload-time = "2023-11-07T07:11:40.142Z" }, { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126, upload-time = "2023-11-07T07:11:41.539Z" }, { url = "https://files.pythonhosted.org/packages/3a/77/66cd37111a87c1589b63524f3d3c848011d21ca97828422c7fde7665ff0d/PyAudio-0.2.14-cp313-cp313-win32.whl", hash = "sha256:95328285b4dab57ea8c52a4a996cb52be6d629353315be5bfda403d15932a497", size = 150982, upload-time = "2024-11-20T19:12:12.404Z" }, { url = "https://files.pythonhosted.org/packages/a5/8b/7f9a061c1cc2b230f9ac02a6003fcd14c85ce1828013aecbaf45aa988d20/PyAudio-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:692d8c1446f52ed2662120bcd9ddcb5aa2b71f38bda31e58b19fb4672fffba69", size = 173655, upload-time = "2024-11-20T19:12:13.616Z" }, ] [[package]] name = "pytokens" version = "0.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4e/8d/a762be14dae1c3bf280202ba3172020b2b0b4c537f94427435f19c413b72/pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a", size = 17644, upload-time = "2025-11-05T13:36:35.34Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/84/25/d9db8be44e205a124f6c98bc0324b2bb149b7431c53877fc6d1038dddaf5/pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3", size = 12195, upload-time = "2025-11-05T13:36:33.183Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] [[package]] name = "websockets" version = "15.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] [[package]] name = "yarl" version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] DeepL-deepl-python-df50df2/license_checker.sh000077500000000000000000000006111523706276600212660ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: ./license_checker.sh source_code_pattern # Example: ./license_checker.sh '*.py' # This will search all .py files, ignoring anything not tracked in your git tree git ls-files -z $1 | xargs -0 -I{} sh -c 'RES=$(head -n 3 "{}" | grep "Copyright 20[0-9][0-9] DeepL SE (https://www.deepl.com)"); if [ ! "${RES}" ] ; then echo "Lacking copyright header in" "{}" ; fi' DeepL-deepl-python-df50df2/poetry.lock000066400000000000000000004320351523706276600200260ustar00rootroot00000000000000# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = true python-versions = ">=3.8" groups = ["main"] markers = "python_version < \"3.12\" and extra == \"keyring\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] [[package]] name = "black" version = "25.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" groups = ["dev"] markers = "python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\"" files = [ {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, {file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"}, {file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"}, {file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"}, {file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"}, {file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"}, {file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"}, {file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"}, {file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"}, {file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"}, {file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"}, {file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"}, {file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"}, {file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"}, {file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"}, {file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"}, {file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"}, {file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"}, {file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"}, {file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"}, {file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"}, {file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"}, {file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"}, {file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"}, {file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" pytokens = ">=0.3.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "black" version = "25.12.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\"" files = [ {file = "black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8"}, {file = "black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a"}, {file = "black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea"}, {file = "black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f"}, {file = "black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da"}, {file = "black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a"}, {file = "black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be"}, {file = "black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b"}, {file = "black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5"}, {file = "black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655"}, {file = "black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a"}, {file = "black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783"}, {file = "black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59"}, {file = "black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892"}, {file = "black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43"}, {file = "black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5"}, {file = "black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f"}, {file = "black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf"}, {file = "black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d"}, {file = "black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce"}, {file = "black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5"}, {file = "black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f"}, {file = "black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f"}, {file = "black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83"}, {file = "black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b"}, {file = "black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828"}, {file = "black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" pytokens = ">=0.3.0" [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" version = "2025.11.12" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] name = "cffi" version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = true python-versions = ">=3.9" groups = ["main"] markers = "platform_python_implementation != \"PyPy\" and extra == \"keyring\" and sys_platform == \"linux\"" files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" version = "3.4.4" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main"] files = [ {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, ] [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\"" files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "click" version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\"" files = [ {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.10.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\"" files = [ {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, ] [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "coverage" version = "7.13.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\"" files = [ {file = "coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147"}, {file = "coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d"}, {file = "coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0"}, {file = "coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90"}, {file = "coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d"}, {file = "coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b"}, {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6"}, {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e"}, {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae"}, {file = "coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29"}, {file = "coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f"}, {file = "coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1"}, {file = "coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88"}, {file = "coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3"}, {file = "coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9"}, {file = "coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee"}, {file = "coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf"}, {file = "coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3"}, {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef"}, {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851"}, {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb"}, {file = "coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba"}, {file = "coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19"}, {file = "coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a"}, {file = "coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c"}, {file = "coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3"}, {file = "coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e"}, {file = "coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c"}, {file = "coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62"}, {file = "coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968"}, {file = "coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e"}, {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f"}, {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee"}, {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf"}, {file = "coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c"}, {file = "coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7"}, {file = "coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6"}, {file = "coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c"}, {file = "coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78"}, {file = "coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b"}, {file = "coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd"}, {file = "coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992"}, {file = "coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4"}, {file = "coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a"}, {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766"}, {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4"}, {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398"}, {file = "coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784"}, {file = "coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461"}, {file = "coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500"}, {file = "coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9"}, {file = "coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc"}, {file = "coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a"}, {file = "coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4"}, {file = "coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6"}, {file = "coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1"}, {file = "coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd"}, {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c"}, {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0"}, {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e"}, {file = "coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53"}, {file = "coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842"}, {file = "coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2"}, {file = "coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09"}, {file = "coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894"}, {file = "coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a"}, {file = "coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f"}, {file = "coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909"}, {file = "coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4"}, {file = "coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75"}, {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9"}, {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465"}, {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864"}, {file = "coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9"}, {file = "coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5"}, {file = "coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a"}, {file = "coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0"}, {file = "coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a"}, {file = "coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6"}, {file = "coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673"}, {file = "coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5"}, {file = "coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d"}, {file = "coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8"}, {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486"}, {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564"}, {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7"}, {file = "coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416"}, {file = "coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f"}, {file = "coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79"}, {file = "coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4"}, {file = "coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573"}, {file = "coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd"}, ] [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = true python-versions = ">=3.7" groups = ["main"] markers = "(python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\") and extra == \"keyring\" and sys_platform == \"linux\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "cryptography" version = "46.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = true python-versions = "!=3.9.0,!=3.9.1,>=3.8" groups = ["main"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\" and extra == \"keyring\" and sys_platform == \"linux\"" files = [ {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e"}, {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926"}, {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71"}, {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac"}, {file = "cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018"}, {file = "cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb"}, {file = "cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c"}, {file = "cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665"}, {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3"}, {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20"}, {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de"}, {file = "cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914"}, {file = "cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db"}, {file = "cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21"}, {file = "cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04"}, {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506"}, {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963"}, {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4"}, {file = "cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df"}, {file = "cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f"}, {file = "cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372"}, {file = "cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32"}, {file = "cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c"}, {file = "cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea"}, {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b"}, {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb"}, {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717"}, {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9"}, {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}, {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}, ] [package.dependencies] cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] nox = ["nox[uv] (>=2024.4.15)"] pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] name = "exceptiongroup" version = "1.3.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"}, {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"}, ] [package.dependencies] typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] [[package]] name = "flake8" version = "7.3.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"}, {file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.14.0,<2.15.0" pyflakes = ">=3.4.0,<3.5.0" [[package]] name = "idna" version = "3.11" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.8" groups = ["main"] files = [ {file = "idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea"}, {file = "idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "importlib-metadata" version = "8.7.1" description = "Read metadata from Python packages" optional = true python-versions = ">=3.9" groups = ["main"] markers = "python_version < \"3.12\" and extra == \"keyring\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] perf = ["ipython"] test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" groups = ["dev"] markers = "python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\"" files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "iniconfig" version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\"" files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] name = "jaraco-classes" version = "3.4.0" description = "Utility functions for Python class constructs" optional = true python-versions = ">=3.8" groups = ["main"] markers = "extra == \"keyring\"" files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, ] [package.dependencies] more-itertools = "*" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-context" version = "6.0.2" description = "Useful decorators and context managers" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"keyring\"" files = [ {file = "jaraco_context-6.0.2-py3-none-any.whl", hash = "sha256:55fc21af4b4f9ca94aa643b6ee7fe13b1e4c01abf3aeb98ca4ad9c80b741c786"}, {file = "jaraco_context-6.0.2.tar.gz", hash = "sha256:953ae8dddb57b1d791bf72ea1009b32088840a7dd19b9ba16443f62be919ee57"}, ] [package.dependencies] "backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] test = ["jaraco.test (>=5.6.0)", "portend", "pytest (>=6,!=8.1.*)"] type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] [[package]] name = "jaraco-functools" version = "4.4.0" description = "Functools like those found in stdlib" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"keyring\"" files = [ {file = "jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176"}, {file = "jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb"}, ] [package.dependencies] more_itertools = "*" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"] type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] [[package]] name = "jeepney" version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = true python-versions = ">=3.7" groups = ["main"] markers = "extra == \"keyring\" and sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, ] [package.extras] test = ["async-timeout ; python_version < \"3.11\"", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] trio = ["trio"] [[package]] name = "keyring" version = "25.7.0" description = "Store and access your passwords safely." optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"keyring\"" files = [ {file = "keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f"}, {file = "keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b"}, ] [package.dependencies] importlib_metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} "jaraco.classes" = "*" "jaraco.context" = "*" "jaraco.functools" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] completion = ["shtab (>=1.1.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=3.4)"] test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] type = ["pygobject-stubs", "pytest-mypy (>=1.0.1)", "shtab", "types-pywin32"] [[package]] name = "librt" version = "0.7.5" description = "Mypyc runtime library" optional = false python-versions = ">=3.9" groups = ["dev"] markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "librt-0.7.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:81056e01bba1394f1d92904ec61a4078f66df785316275edbaf51d90da8c6e26"}, {file = "librt-0.7.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d7c72c8756eeb3aefb1b9e3dac7c37a4a25db63640cac0ab6fc18e91a0edf05a"}, {file = "librt-0.7.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddc4a16207f88f9597b397fc1f60781266d13b13de922ff61c206547a29e4bbd"}, {file = "librt-0.7.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63055d3dda433ebb314c9f1819942f16a19203c454508fdb2d167613f7017169"}, {file = "librt-0.7.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f85f9b5db87b0f52e53c68ad2a0c5a53e00afa439bd54a1723742a2b1021276"}, {file = "librt-0.7.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c566a4672564c5d54d8ab65cdaae5a87ee14c1564c1a2ddc7a9f5811c750f023"}, {file = "librt-0.7.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fee15c2a190ef389f14928135c6fb2d25cd3fdb7887bfd9a7b444bbdc8c06b96"}, {file = "librt-0.7.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:584cb3e605ec45ba350962cec853e17be0a25a772f21f09f1e422f7044ae2a7d"}, {file = "librt-0.7.5-cp310-cp310-win32.whl", hash = "sha256:9c08527055fbb03c641c15bbc5b79dd2942fb6a3bd8dabf141dd7e97eeea4904"}, {file = "librt-0.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:dd810f2d39c526c42ea205e0addad5dc08ef853c625387806a29d07f9d150d9b"}, {file = "librt-0.7.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f952e1a78c480edee8fb43aa2bf2e84dcd46c917d44f8065b883079d3893e8fc"}, {file = "librt-0.7.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75965c1f4efb7234ff52a58b729d245a21e87e4b6a26a0ec08052f02b16274e4"}, {file = "librt-0.7.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:732e0aa0385b59a1b2545159e781c792cc58ce9c134249233a7c7250a44684c4"}, {file = "librt-0.7.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdde31759bd8888f3ef0eebda80394a48961328a17c264dce8cc35f4b9cde35d"}, {file = "librt-0.7.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3146d52465b3b6397d25d513f428cb421c18df65b7378667bb5f1e3cc45805"}, {file = "librt-0.7.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29c8d2fae11d4379ea207ba7fc69d43237e42cf8a9f90ec6e05993687e6d648b"}, {file = "librt-0.7.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb41f04046b4f22b1e7ba5ef513402cd2e3477ec610e5f92d38fe2bba383d419"}, {file = "librt-0.7.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8bb7883c1e94ceb87c2bf81385266f032da09cd040e804cc002f2c9d6b842e2f"}, {file = "librt-0.7.5-cp311-cp311-win32.whl", hash = "sha256:84d4a6b9efd6124f728558a18e79e7cc5c5d4efc09b2b846c910de7e564f5bad"}, {file = "librt-0.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:ab4b0d3bee6f6ff7017e18e576ac7e41a06697d8dea4b8f3ab9e0c8e1300c409"}, {file = "librt-0.7.5-cp311-cp311-win_arm64.whl", hash = "sha256:730be847daad773a3c898943cf67fb9845a3961d06fb79672ceb0a8cd8624cfa"}, {file = "librt-0.7.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba1077c562a046208a2dc6366227b3eeae8f2c2ab4b41eaf4fd2fa28cece4203"}, {file = "librt-0.7.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:654fdc971c76348a73af5240d8e2529265b9a7ba6321e38dd5bae7b0d4ab3abe"}, {file = "librt-0.7.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6b7b58913d475911f6f33e8082f19dd9b120c4f4a5c911d07e395d67b81c6982"}, {file = "librt-0.7.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e0fd344bad57026a8f4ccfaf406486c2fc991838050c2fef156170edc3b775"}, {file = "librt-0.7.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46aa91813c267c3f60db75d56419b42c0c0b9748ec2c568a0e3588e543fb4233"}, {file = "librt-0.7.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ddc0ab9dbc5f9ceaf2bf7a367bf01f2697660e908f6534800e88f43590b271db"}, {file = "librt-0.7.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7a488908a470451338607650f1c064175094aedebf4a4fa37890682e30ce0b57"}, {file = "librt-0.7.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e47fc52602ffc374e69bf1b76536dc99f7f6dd876bd786c8213eaa3598be030a"}, {file = "librt-0.7.5-cp312-cp312-win32.whl", hash = "sha256:cda8b025875946ffff5a9a7590bf9acde3eb02cb6200f06a2d3e691ef3d9955b"}, {file = "librt-0.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:b591c094afd0ffda820e931148c9e48dc31a556dc5b2b9b3cc552fa710d858e4"}, {file = "librt-0.7.5-cp312-cp312-win_arm64.whl", hash = "sha256:532ddc6a8a6ca341b1cd7f4d999043e4c71a212b26fe9fd2e7f1e8bb4e873544"}, {file = "librt-0.7.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b1795c4b2789b458fa290059062c2f5a297ddb28c31e704d27e161386469691a"}, {file = "librt-0.7.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2fcbf2e135c11f721193aa5f42ba112bb1046afafbffd407cbc81d8d735c74d0"}, {file = "librt-0.7.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c039bbf79a9a2498404d1ae7e29a6c175e63678d7a54013a97397c40aee026c5"}, {file = "librt-0.7.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3919c9407faeeee35430ae135e3a78acd4ecaaaa73767529e2c15ca1d73ba325"}, {file = "librt-0.7.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26b46620e1e0e45af510d9848ea0915e7040605dd2ae94ebefb6c962cbb6f7ec"}, {file = "librt-0.7.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9bbb8facc5375476d392990dd6a71f97e4cb42e2ac66f32e860f6e47299d5e89"}, {file = "librt-0.7.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e9e9c988b5ffde7be02180f864cbd17c0b0c1231c235748912ab2afa05789c25"}, {file = "librt-0.7.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:edf6b465306215b19dbe6c3fb63cf374a8f3e1ad77f3b4c16544b83033bbb67b"}, {file = "librt-0.7.5-cp313-cp313-win32.whl", hash = "sha256:060bde69c3604f694bd8ae21a780fe8be46bb3dbb863642e8dfc75c931ca8eee"}, {file = "librt-0.7.5-cp313-cp313-win_amd64.whl", hash = "sha256:a82d5a0ee43aeae2116d7292c77cc8038f4841830ade8aa922e098933b468b9e"}, {file = "librt-0.7.5-cp313-cp313-win_arm64.whl", hash = "sha256:3c98a8d0ac9e2a7cb8ff8c53e5d6e8d82bfb2839abf144fdeaaa832f2a12aa45"}, {file = "librt-0.7.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9937574e6d842f359b8585903d04f5b4ab62277a091a93e02058158074dc52f2"}, {file = "librt-0.7.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5cd3afd71e9bc146203b6c8141921e738364158d4aa7cdb9a874e2505163770f"}, {file = "librt-0.7.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cffa3ef0af29687455161cb446eff059bf27607f95163d6a37e27bcb37180f6"}, {file = "librt-0.7.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82f3f088482e2229387eadf8215c03f7726d56f69cce8c0c40f0795aebc9b361"}, {file = "librt-0.7.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7aa33153a5bb0bac783d2c57885889b1162823384e8313d47800a0e10d0070e"}, {file = "librt-0.7.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:265729b551a2dd329cc47b323a182fb7961af42abf21e913c9dd7d3331b2f3c2"}, {file = "librt-0.7.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:168e04663e126416ba712114050f413ac306759a1791d87b7c11d4428ba75760"}, {file = "librt-0.7.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:553dc58987d1d853adda8aeadf4db8e29749f0b11877afcc429a9ad892818ae2"}, {file = "librt-0.7.5-cp314-cp314-win32.whl", hash = "sha256:263f4fae9eba277513357c871275b18d14de93fd49bf5e43dc60a97b81ad5eb8"}, {file = "librt-0.7.5-cp314-cp314-win_amd64.whl", hash = "sha256:85f485b7471571e99fab4f44eeb327dc0e1f814ada575f3fa85e698417d8a54e"}, {file = "librt-0.7.5-cp314-cp314-win_arm64.whl", hash = "sha256:49c596cd18e90e58b7caa4d7ca7606049c1802125fcff96b8af73fa5c3870e4d"}, {file = "librt-0.7.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:54d2aef0b0f5056f130981ad45081b278602ff3657fe16c88529f5058038e802"}, {file = "librt-0.7.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0b4791202296ad51ac09a3ff58eb49d9da8e3a4009167a6d76ac418a974e5fd4"}, {file = "librt-0.7.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e860909fea75baef941ee6436e0453612505883b9d0d87924d4fda27865b9a2"}, {file = "librt-0.7.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f02c4337bf271c4f06637f5ff254fad2238c0b8e32a3a480ebb2fc5e26f754a5"}, {file = "librt-0.7.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f51ffe59f4556243d3cc82d827bde74765f594fa3ceb80ec4de0c13ccd3416"}, {file = "librt-0.7.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b7f080ba30601dfa3e3deed3160352273e1b9bc92e652f51103c3e9298f7899"}, {file = "librt-0.7.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fb565b4219abc8ea2402e61c7ba648a62903831059ed3564fa1245cc245d58d7"}, {file = "librt-0.7.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a3cfb15961e7333ea6ef033dc574af75153b5c230d5ad25fbcd55198f21e0cf"}, {file = "librt-0.7.5-cp314-cp314t-win32.whl", hash = "sha256:118716de5ad6726332db1801bc90fa6d94194cd2e07c1a7822cebf12c496714d"}, {file = "librt-0.7.5-cp314-cp314t-win_amd64.whl", hash = "sha256:3dd58f7ce20360c6ce0c04f7bd9081c7f9c19fc6129a3c705d0c5a35439f201d"}, {file = "librt-0.7.5-cp314-cp314t-win_arm64.whl", hash = "sha256:08153ea537609d11f774d2bfe84af39d50d5c9ca3a4d061d946e0c9d8bce04a1"}, {file = "librt-0.7.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:df2e210400b28e50994477ebf82f055698c79797b6ee47a1669d383ca33263e1"}, {file = "librt-0.7.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d2cc7d187e8c6e9b7bdbefa9697ce897a704ea7a7ce844f2b4e0e2aa07ae51d3"}, {file = "librt-0.7.5-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39183abee670bc37b85f11e86c44a9cad1ed6efa48b580083e89ecee13dd9717"}, {file = "librt-0.7.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191cbd42660446d67cf7a95ac7bfa60f49b8b3b0417c64f216284a1d86fc9335"}, {file = "librt-0.7.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea1b60b86595a5dc1f57b44a801a1c4d8209c0a69518391d349973a4491408e6"}, {file = "librt-0.7.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:af69d9e159575e877c7546d1ee817b4ae089aa221dd1117e20c24ad8dc8659c7"}, {file = "librt-0.7.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0e2bf8f91093fac43e3eaebacf777f12fd539dce9ec5af3efc6d8424e96ccd49"}, {file = "librt-0.7.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8dcae24de1bc9da93aa689cb6313c70e776d7cea2fcf26b9b6160fedfe6bd9af"}, {file = "librt-0.7.5-cp39-cp39-win32.whl", hash = "sha256:cdb001a1a0e4f41e613bca2c0fc147fc8a7396f53fc94201cbfd8ec7cd69ca4b"}, {file = "librt-0.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:a9eacbf983319b26b5f340a2e0cd47ac1ee4725a7f3a72fd0f15063c934b69d6"}, {file = "librt-0.7.5.tar.gz", hash = "sha256:de4221a1181fa9c8c4b5f35506ed6f298948f44003d84d2a8b9885d7e01e6cfa"}, ] [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] name = "more-itertools" version = "10.8.0" description = "More routines for operating on iterables, beyond itertools" optional = true python-versions = ">=3.9" groups = ["main"] markers = "extra == \"keyring\"" files = [ {file = "more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b"}, {file = "more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd"}, ] [[package]] name = "mypy" version = "1.19.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"}, {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"}, {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"}, {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"}, {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"}, {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"}, {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"}, {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"}, {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"}, {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"}, {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"}, {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"}, {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"}, {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"}, {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"}, {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"}, {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"}, {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"}, {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"}, {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"}, {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"}, {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"}, {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"}, {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"}, {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"}, {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"}, {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"}, {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"}, {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"}, {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"}, {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"}, {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"}, {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"}, {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"}, {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"}, {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"}, {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"}, {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"}, ] [package.dependencies] librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=0.9.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] name = "mypy-extensions" version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] name = "packaging" version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] name = "platformdirs" version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" groups = ["dev"] markers = "python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\"" files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.14.1)"] [[package]] name = "platformdirs" version = "4.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.10" groups = ["dev"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\"" files = [ {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"}, {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"}, ] [package.extras] docs = ["furo (>=2025.9.25)", "proselint (>=0.14)", "sphinx (>=8.2.3)", "sphinx-autodoc-typehints (>=3.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.4.2)", "pytest-cov (>=7)", "pytest-mock (>=3.15.1)"] type = ["mypy (>=1.18.2)"] [[package]] name = "pluggy" version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pycodestyle" version = "2.14.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"}, {file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"}, ] [[package]] name = "pycparser" version = "2.23" description = "C parser in Python" optional = true python-versions = ">=3.8" groups = ["main"] markers = "platform_python_implementation != \"PyPy\" and extra == \"keyring\" and sys_platform == \"linux\" and implementation_name != \"PyPy\"" files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] [[package]] name = "pydantic" version = "1.10.26" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, ] [package.dependencies] typing-extensions = ">=4.2.0" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] [[package]] name = "pyflakes" version = "3.4.0" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"}, {file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"}, ] [[package]] name = "pygments" version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} iniconfig = ">=1" packaging = ">=20" pluggy = ">=1.5,<2" pygments = ">=2.7.2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytokens" version = "0.3.0" description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"}, {file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"}, ] [package.extras] dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] [[package]] name = "pywin32-ctypes" version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = true python-versions = ">=3.6" groups = ["main"] markers = "extra == \"keyring\" and sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, ] [[package]] name = "requests" version = "2.32.5" description = "Python HTTP for Humans." optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] certifi = ">=2017.4.17" charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" optional = true python-versions = ">=3.6" groups = ["main"] markers = "(python_full_version < \"3.14.0\" or platform_python_implementation == \"PyPy\") and extra == \"keyring\" and sys_platform == \"linux\"" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, ] [package.dependencies] cryptography = ">=2.0" jeepney = ">=0.6" [[package]] name = "secretstorage" version = "3.5.0" description = "Python bindings to FreeDesktop.org Secret Service API" optional = true python-versions = ">=3.10" groups = ["main"] markers = "python_full_version >= \"3.14.0\" and platform_python_implementation != \"PyPy\" and extra == \"keyring\" and sys_platform == \"linux\"" files = [ {file = "secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137"}, {file = "secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be"}, ] [package.dependencies] cryptography = ">=2.0" jeepney = ">=0.6" [[package]] name = "tomli" version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] [[package]] name = "typing-extensions" version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] name = "urllib3" version = "2.6.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main"] files = [ {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"}, {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"}, ] [package.extras] brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "zipp" version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = true python-versions = ">=3.9" groups = ["main"] markers = "python_version < \"3.12\" and extra == \"keyring\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] keyring = ["keyring"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" content-hash = "1030c5492c7434658508cdc6bec2e382d725b613dba860d09ff7789785532ad5" DeepL-deepl-python-df50df2/pyproject.toml000066400000000000000000000027011523706276600205370ustar00rootroot00000000000000[tool.black] line-length = 79 [tool.poetry] name = "deepl" version = "1.32.0" description = "Python library for the DeepL API." authors = ["DeepL SE "] license = "MIT" readme = "README.md" repository = "https://github.com/DeepLcom/deepl-python" documentation = "https://www.deepl.com/docs-api/" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", # Note: although poetry automatically creates the Python version trove # classifiers, the 3.6 one will be missing because we specify >=3.6.2 "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] [tool.poetry.urls] "Bug Tracker" = "https://github.com/DeepLcom/deepl-python/issues" [tool.coverage.run] omit = [".*", "*/site-packages/*", "tests/*"] [tool.coverage.report] fail_under = 70 [tool.poetry.dependencies] python = ">=3.9,<4" requests = ">=2.32.5" keyring = {version = "^25.7.0", optional = true} [tool.poetry.group.dev.dependencies] black = "^25.1.0" pydantic = "^1.8.1" flake8 = "^7.3.0" mypy = "^1.17.1" pytest = "^8.4.2" coverage = "^7.10.6" [tool.poetry.extras] keyring = ["keyring"] [tool.poetry.scripts] deepl = "deepl.__main__:main" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" DeepL-deepl-python-df50df2/tests/000077500000000000000000000000001523706276600167655ustar00rootroot00000000000000DeepL-deepl-python-df50df2/tests/__init__.py000066400000000000000000000002231523706276600210730ustar00rootroot00000000000000# Copyright 2023 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. DeepL-deepl-python-df50df2/tests/conftest.py000066400000000000000000000402641523706276600211720ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import deepl import os import pathlib from deepl.api_data import ( MultilingualGlossaryDictionaryEntries, MultilingualGlossaryInfo, ) from pydantic import BaseSettings import pytest from typing import Callable, List, Optional from typing_extensions import Protocol import uuid # Set environment variables to change this configuration. # Example: export DEEPL_SERVER_URL=http://localhost:3000/ # export DEEPL_MOCK_SERVER_PORT=3000 # export DEEPL_PROXY_URL=http://localhost:3001/ # export DEEPL_MOCK_PROXY_SERVER_PORT=3001 # # supported use cases: # - using real API # - user needs to configure their auth_key # - using a local mock server # - user needs to configure: server_url and set mock_server_port # - auth_key can be set empty # - using a real server with different IP (e.g. for testing) # - user needs to configure their auth_key and server_url class Config(BaseSettings): auth_key: Optional[str] = None server_url: Optional[str] = None mock_server_port: Optional[int] = None proxy_url: Optional[str] = None mock_proxy_server_port: Optional[int] = None class Config: env_prefix = "DEEPL_" @pytest.fixture def config(): return Config() @pytest.fixture def server(config): class Server: def __init__(self): self.headers = {} self.is_mock_server = config.mock_server_port is not None if self.is_mock_server: self.server_url = config.server_url self.auth_key = "mock_server" uu = str(uuid.uuid1()) session_uuid = f"{os.getenv('PYTEST_CURRENT_TEST')}/{uu}" self.headers["mock-server-session"] = session_uuid self.proxy = config.proxy_url else: self.auth_key = config.auth_key self.server_url = config.server_url self.proxy = config.proxy_url def no_response(self, count): """Instructs the mock server to ignore N requests from this session, giving no response.""" if self.is_mock_server: self.headers["mock-server-session-no-response-count"] = str( count ) def respond_with_429(self, count): """Instructs the mock server to reject N /translate requests from this session with 429 status codes.""" if self.is_mock_server: self.headers["mock-server-session-429-count"] = str(count) def init_character_limit(self, count): """Instructs the mock server to initialize user accounts created by this session with given character limit.""" if self.is_mock_server: self.headers["mock-server-session-init-character-limit"] = str( count ) def init_document_limit(self, count): """Instructs the mock server to initialize user accounts created by this session with given document limit.""" if self.is_mock_server: self.headers["mock-server-session-init-document-limit"] = str( count ) def init_team_document_limit(self, count): """Instructs the mock server to initialize user accounts created by this session with given team document limit.""" if self.is_mock_server: self.headers[ "mock-server-session-init-team-document-limit" ] = str(count) def set_doc_failure(self, count): """Instructs the mock server to fail during translation of N documents during this session.""" if self.is_mock_server: self.headers["mock-server-session-doc-failure"] = str(count) def set_doc_queue_time(self, milliseconds): """Instructs the mock server to queue documents for specified time before translation.""" if self.is_mock_server: self.headers["mock-server-session-doc-queue-time"] = str( milliseconds ) def set_doc_translate_time(self, milliseconds): """Instructs the mock server to translate documents within specified time.""" if self.is_mock_server: self.headers["mock-server-session-doc-translate-time"] = str( milliseconds ) def set_tm_job_processing_polls(self, count): """Instructs the mock server to report a non-terminal status for translation memory jobs for the given number of polls before completing them.""" if self.is_mock_server: self.headers["mock-server-session-tm-job-processing-polls"] = ( str(count) ) def expect_proxy(self, value: bool = True): """Instructs the mock server to only accept requests via the proxy.""" if self.is_mock_server: self.headers["mock-server-session-expect-proxy"] = ( "1" if value else "0" ) return Server() def _make_translator(server, auth_key=None, proxy=None): """Returns a deepl.Translator for the specified server test fixture. The server auth_key is used unless specifically overridden.""" if auth_key is None: auth_key = server.auth_key translator = deepl.Translator( auth_key, server_url=server.server_url, proxy=proxy ) # If the server test fixture has custom headers defined, update the # translator headers and replace with the server headers dictionary. # Note: changing the underlying object is necessary because some tests # make changes to the headers during tests. if server.headers: server.headers.update(translator.headers) translator.headers = server.headers return translator def _make_deepl_client(server, auth_key=None, proxy=None): """Returns a deepl.DeepLClient for the specified server test fixture. The server auth_key is used unless specifically overridden.""" if auth_key is None: auth_key = server.auth_key deepl_client = deepl.DeepLClient( auth_key, server_url=server.server_url, proxy=proxy ) # If the server test fixture has custom headers defined, update the # translator headers and replace with the server headers dictionary. # Note: changing the underlying object is necessary because some tests # make changes to the headers during tests. if server.headers: server.headers.update(deepl_client.headers) deepl_client.headers = server.headers return deepl_client @pytest.fixture def translator(server): """Returns a deepl.Translator to use in all tests taking a parameter 'translator'.""" return _make_translator(server) # TODO Replace all uses of `translator` above with this and delete the # duplicated code. @pytest.fixture def deepl_client(server): """Returns a deepl.DeepLClient to use in all tests taking a parameter 'deepl_client'.""" return _make_deepl_client(server) @pytest.fixture def translator_with_random_auth_key(server): """Returns a deepl.Translator with randomized authentication key, for use in mock-server tests.""" return _make_translator(server, auth_key=str(uuid.uuid1())) @pytest.fixture def translator_with_random_auth_key_and_proxy(server): """Returns a deepl.Translator with randomized authentication key, for use in mock-server tests.""" return _make_translator( server, auth_key=str(uuid.uuid1()), proxy=server.proxy ) @pytest.fixture def cleanup_matching_glossaries(deepl_client): """ Fixture function to remove all glossaries from the server matching the given predicate. Can be used, for example, to remove all glossaries with a matching name. Usage example: def test_example(cleanup_matching_glossaries): ... cleanup_matching_glossaries( lambda glossary: glossary.name.startswith("test ") ) """ def do_cleanup(predicate: Callable[[deepl.GlossaryInfo], bool]): glossaries = deepl_client.list_multilingual_glossaries() for glossary in glossaries: if predicate(glossary): try: deepl_client.delete_multilingual_glossary(glossary) except deepl.DeepLException: pass return do_cleanup class ManagedGlossary: """ Utility content-manager class to create a test glossary and ensure its deletion at the end of a test. """ def __init__( self, translator: deepl.Translator, glossary_name: str, source_lang, target_lang, entries: dict, ): self._translator = translator self._created_glossary = translator.create_glossary( glossary_name, source_lang, target_lang, entries ) def __enter__(self) -> deepl.GlossaryInfo: return self._created_glossary def __exit__(self, exc_type, exc_val, exc_tb): try: self._translator.delete_glossary( self._created_glossary.glossary_id ) except deepl.DeepLException: pass class ManagedMultilingualGlossary: """ Utility content-manager class to create a test glossary and ensure its deletion at the end of a test. """ def __init__( self, deepl_client, glossary_name: str, dictionaries: List[MultilingualGlossaryDictionaryEntries], ): self.deepl_client = deepl_client self._created_glossary = deepl_client.create_multilingual_glossary( glossary_name, dictionaries ) def __enter__(self) -> MultilingualGlossaryInfo: return self._created_glossary def __exit__(self, exc_type, exc_val, exc_tb): try: self.deepl_client.delete_multilingual_glossary( self._created_glossary.glossary_id ) except deepl.DeepLException: pass class CreateManagedGlossaryFunc(Protocol): """Helper class for type hints.""" def __call__( self, source_lang: str = "EN", target_lang: str = "DE", entries: Optional[dict] = None, glossary_name_suffix: str = "", ) -> ManagedGlossary: pass class CreateManagedMultilingualGlossaryFunc(Protocol): """Helper class for type hints.""" def __call__( self, dictionaries: List[MultilingualGlossaryDictionaryEntries], glossary_name_suffix: str = "", ) -> ManagedMultilingualGlossary: pass @pytest.fixture def glossary_manager(translator, glossary_name) -> CreateManagedGlossaryFunc: """ Fixture function that may be used to create context-managed test v2 glossaries, named using the current test. May be called multiple times in a test to create multiple glossaries, ideally with a different suffix for each glossary. Usage example: def test_example(glossary_manager): with glossary_manager( entries={"a": "b"}, glossary_name_suffix="1" ) as glossary1: ... """ def create_managed_glossary( source_lang: str = "EN", target_lang: str = "DE", entries: Optional[dict] = None, glossary_name_suffix: str = "", ): if not entries: entries = {"Hello": "Hallo"} return ManagedGlossary( translator, f"{glossary_name}{glossary_name_suffix}", source_lang, target_lang, entries, ) return create_managed_glossary @pytest.fixture def multilingual_glossary_manager( deepl_client, glossary_name ) -> CreateManagedMultilingualGlossaryFunc: """ Fixture function that may be used to create context-managed test glossaries, named using the current test. May be called multiple times in a test to create multiple glossaries, ideally with a different suffix for each glossary. Usage example: def test_example(glossary_manager): with glossary_manager( entries={"a": "b"}, glossary_name_suffix="1" ) as glossary1: ... """ def create_managed_glossary( dictionaries: List[MultilingualGlossaryDictionaryEntries], glossary_name_suffix: str = "", ): return ManagedMultilingualGlossary( deepl_client, f"{glossary_name}{glossary_name_suffix}", dictionaries, ) return create_managed_glossary @pytest.fixture def glossary_name(request) -> str: """Returns a suitable glossary name to be used in the test""" test_name = request.node.name new_uuid = str(uuid.uuid1()) return f"deepl-python-test-glossary: {test_name} {new_uuid}" @pytest.fixture def example_document_path(tmpdir): tmpdir = pathlib.Path(tmpdir) path = tmpdir / "input" / "example_document.txt" path.parent.mkdir() path.write_text(example_text["EN"]) return path @pytest.fixture def example_glossary_csv(tmpdir): tmpdir = pathlib.Path(tmpdir) content = ( "sourceEntry1,targetEntry1,en,de\n" '"source""Entry","target,Entry",en,de' ) path = tmpdir / "glossary" / "example_glossary.csv" path.parent.mkdir() path.write_text(content) return path @pytest.fixture def example_glossary_csv_entries(): return { "sourceEntry1": "targetEntry1", 'source"Entry': "target,Entry", } @pytest.fixture def example_document_translation(): return example_text["DE"] @pytest.fixture def example_large_document_translation(): return (example_text["DE"] + "\n") * 1000 @pytest.fixture def input_dir_path(tmpdir): tmpdir = pathlib.Path(tmpdir) path = tmpdir / "input" path.mkdir(exist_ok=True) return path @pytest.fixture def output_dir_path(tmpdir): tmpdir = pathlib.Path(tmpdir) path = tmpdir / "output" path.mkdir(exist_ok=True) return path @pytest.fixture def example_large_document_path(input_dir_path): path = input_dir_path / "example_document.txt" path.write_text((example_text["EN"] + "\n") * 1000) return path @pytest.fixture def output_document_path(output_dir_path): return output_dir_path / "example_document.txt" # Decorate test functions with "@needs_mock_server" to skip them if a real # server is used needs_mock_server = pytest.mark.skipif( Config().mock_server_port is None, reason="this test requires a mock server", ) # Decorate test functions with "@needs_mock_proxy_server" to skip them if a # real server is used or mock proxy server is not configured needs_mock_proxy_server = pytest.mark.skipif( Config().mock_proxy_server_port is None or Config().mock_server_port is None, reason="this test requires a mock proxy server", ) # Decorate test functions with "@needs_real_server" to skip them if a mock # server is used needs_real_server = pytest.mark.skipif( not (Config().mock_server_port is None), reason="this test requires a real server", ) example_text = { "AR": "شعاع البروتون", "BG": "протонен лъч", "CS": "protonový paprsek", "DA": "protonstråle", "DE": "Protonenstrahl", "EL": "δέσμη πρωτονίων", "EN": "proton beam", "EN-US": "proton beam", "EN-GB": "proton beam", "ES": "haz de protones", "ET": "prootonikiirgus", "FI": "protonisäde", "FR": "faisceau de protons", "HU": "protonnyaláb", "ID": "berkas proton", "IT": "fascio di protoni", "JA": "陽子ビーム", "KO": "양성자 빔", "LT": "protonų spindulys", "LV": "protonu staru kūlis", "NB": "protonstråle", "NL": "protonenbundel", "PL": "wiązka protonów", "PT": "feixe de prótons", "PT-BR": "feixe de prótons", "PT-PT": "feixe de prótons", "RO": "fascicul de protoni", "RU": "протонный луч", "SK": "protónový lúč", "SL": "protonski žarek", "SV": "protonstråle", "TR": "proton ışını", "UK": "протонний пучок", "ZH": "质子束", } DeepL-deepl-python-df50df2/tests/test_cli.py000066400000000000000000000530321523706276600211500ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from click.testing import CliRunner from .conftest import example_text, needs_mock_server, needs_real_server # flake8: noqa: F401 from deepl import __main__ import deepl import pathlib import pytest import re from unittest.mock import patch main_function = deepl.__main__ @pytest.fixture def runner(server): env = { "DEEPL_SERVER_URL": server.server_url, "DEEPL_AUTH_KEY": server.auth_key, } return CliRunner(env=env) def test_help(runner): result = runner.invoke(main_function, "--help") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "usage" in result.output def test_version(runner): result = runner.invoke(main_function, "--version") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "deepl-python v" in result.output version_regex = re.compile(r"deepl-python v\d+\.\d+\.\d+") assert version_regex.match(result.output) is not None def test_verbose(runner): # verbose = info result = runner.invoke(main_function, "--verbose usage") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Request to DeepL API" in result.output # verbose = debug result = runner.invoke(main_function, "-vv usage") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Request to DeepL API" in result.output assert "Request details" in result.output def test_no_auth(runner): result = runner.invoke( main_function, "usage", env={"DEEPL_AUTH_KEY": None} ) assert result.exit_code == 1, f"exit: {result.exit_code}\n {result.output}" assert "DEEPL_AUTH_KEY" in result.output # Unfortunately there is no secure way to use the keyring module in our CI, # so we have to mock the module's behavior here # For the reason, see https://github.com/jaraco/keyring/issues/477 @patch("deepl.__main__._optional_import") def test_keyring_auth(import_mock, runner): mocked_keyring = { "deepl": {"DEEPL_AUTH_KEY": runner.env["DEEPL_AUTH_KEY"]} } def get_pw_mock(service_name, username): return mocked_keyring[service_name][username] import_mock.return_value.get_password.side_effect = get_pw_mock result = runner.invoke( main_function, "usage", env={"DEEPL_AUTH_KEY": None} ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Usage this billing period" in result.output @patch("importlib.import_module") def test_no_auth_no_keyring(mock, runner): mock.side_effect = ImportError("Keyring module not available in this test") result = runner.invoke( main_function, "usage", env={"DEEPL_AUTH_KEY": None} ) assert result.exit_code == 1, f"exit: {result.exit_code}\n {result.output}" assert "DEEPL_AUTH_KEY" in result.output @patch("importlib.import_module") def test_env_auth_no_keyring(mock, runner): mock.side_effect = ImportError("Keyring module not available in this test") result = runner.invoke(main_function, "usage") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Usage this billing period" in result.output def test_no_command(runner): result = runner.invoke(main_function, "") assert result.exit_code == 2, f"exit: {result.exit_code}\n {result.output}" assert "required: command" in result.output def test_usage(runner): result = runner.invoke(main_function, "usage") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Usage this billing period" in result.output def test_languages(runner): result = runner.invoke(main_function, "languages") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Source languages" in result.output assert "Target languages" in result.output assert "DE: German" in result.output assert "EN: English" in result.output result = runner.invoke(deepl.__main__, "languages --glossary") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "supported for glossaries" in result.output assert "de, en" in result.output def test_text(runner): result = runner.invoke( main_function, 'text --to DE "proton beam" --show-detected-source ' "--show-model-type-used --model-type quality_optimized", ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert example_text["DE"] in result.output assert "Detected source" in result.output assert "Model type used: quality_optimized" in result.output # Test text options extra_options = [ ("--formality more", "'formality': 'more'"), ("--formality prefer_less", "'formality': 'prefer_less'"), ("--split-sentences 0", "'split_sentences': '0'"), ("--preserve-formatting", "'preserve_formatting': True"), ("--tag-handling xml", "'tag_handling': 'xml'"), ("--outline-detection-off", "'outline_detection': False"), ( "--ignore-tags a,b --ignore-tags c", "'ignore_tags': ['a', 'b', 'c']", ), ( "--splitting-tags a,b --splitting-tags c", "'splitting_tags': ['a', 'b', 'c']", ), ( "--non-splitting-tags a,b --non-splitting-tags c", "'non_splitting_tags': ['a', 'b', 'c']", ), ( "--model-type quality_optimized", "'model_type': 'quality_optimized'", ), ] for args, search_str in extra_options: result = runner.invoke( main_function, f'-vv text --to DE "proton beam" {args}' ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" request_details = next( line for line in result.output.splitlines() if line.startswith("Request details data") ) assert search_str in request_details def test_text_stdin(runner): result = runner.invoke( main_function, "text --to DE --show-detected-source -", input=example_text["EN"], ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert example_text["DE"] in result.output assert "Detected source" in result.output @needs_real_server def test_text_preserve_formatting(runner): result = runner.invoke( main_function, 'text --to DE --preserve-formatting "proton beam"' ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert example_text["DE"].lower() in result.output.lower() def test_text_split_sentences(runner): result = runner.invoke( main_function, '-vv text --to DE --split-sentences nonewlines "proton beam"', ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" # Check split_sentences parameter is sent in HTTP request regex = re.compile("Request details.*split_sentences.*nonewlines.*") assert any( regex.match(line) is not None for line in result.output.split("\n") ), f"output:\n{result.output}" def test_text_tags(runner): result = runner.invoke( main_function, "-vv text --to DE --tag-handling xml --splitting-tags split " '--ignore-tags a,b --ignore-tags c --ignore-tags d "proton beam"', ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" # Check ignore_tags parameter is sent in HTTP request regex = re.compile( "Request details.*'ignore_tags': \\['a', 'b', 'c', 'd']" ) assert any( regex.match(line) is not None for line in result.output.split("\n") ), f"output:\n{result.output}" # Check splitting_tags parameter is sent in HTTP request regex = re.compile("Request details.*'splitting_tags': \\['split']") assert any( regex.match(line) is not None for line in result.output.split("\n") ), f"output:\n{result.output}" def test_text_html_tag_handling(runner): result = runner.invoke( main_function, '-vv text --to DE --tag-handling html "

Test

"', ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" def test_text_glossary_ids(runner): # Fake glossary IDs are rejected by the server, but we only need to verify # the CLI maps --glossary-ids to a glossary_ids array request field. result = runner.invoke( main_function, "-vv text --to DE --from EN " "--glossary-ids gid1 --glossary-ids gid2 " '"proton beam"', ) # Check glossary_ids parameter is sent in HTTP request as an array regex = re.compile(r"Request details.*'glossary_ids': \['gid1', 'gid2'\]") assert any( regex.match(line) is not None for line in result.output.split("\n") ), f"output:\n{result.output}" def test_text_glossary_ids_conflicts_with_glossary_id(runner): result = runner.invoke( main_function, "text --to DE --from EN " "--glossary-id gid1 --glossary-ids gid2 " '"proton beam"', ) assert result.exit_code == 1, f"exit: {result.exit_code}\n {result.output}" assert "glossary_ids cannot be used together" in result.output def test_document(runner, tmpdir): tmpdir = pathlib.Path(tmpdir) output_dir = tmpdir / "output" input_document = tmpdir / "document.txt" input_document.write_text(example_text["EN"]) output_document = output_dir / "document.txt" result = runner.invoke( main_function, f"-vv document --to DE {input_document} {output_dir}" ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert example_text["DE"] == output_document.read_text() def test_document_glossary_ids(runner, tmpdir): tmpdir = pathlib.Path(tmpdir) output_dir = tmpdir / "output" input_document = tmpdir / "document.txt" input_document.write_text(example_text["EN"]) # Fake glossary IDs are rejected by the server, but we only need to verify # the CLI maps --glossary-ids to the glossary_ids field. The document # endpoint is multipart/form-data, so it is sent comma-separated. result = runner.invoke( main_function, f"-vv document --to DE --from EN " f"--glossary-ids gid1 --glossary-ids gid2 " f"{input_document} {output_dir}", ) # Check glossary_ids parameter is sent in the document upload request regex = re.compile("Request details.*'glossary_ids': 'gid1,gid2'") assert any( regex.match(line) is not None for line in result.output.split("\n") ), f"output:\n{result.output}" def test_document_style_and_translation_memory(runner, tmpdir): tmpdir = pathlib.Path(tmpdir) output_dir = tmpdir / "output" input_document = tmpdir / "document.txt" input_document.write_text(example_text["EN"]) # Fake IDs are rejected by the server, but we only need to verify the CLI # maps the new document flags to the corresponding request fields. result = runner.invoke( main_function, f"-vv document --to DE " f"--style-id style1 " f"--translation-memory-id tm1 " f"--translation-memory-threshold 80 " f"{input_document} {output_dir}", ) request_details = next( line for line in result.output.splitlines() if line.startswith("Request details data") ) assert "'style_id': 'style1'" in request_details assert "'translation_memory_id': 'tm1'" in request_details assert "'translation_memory_threshold': 80" in request_details def test_document_occupied_output(runner, tmpdir): tmpdir = pathlib.Path(tmpdir) output_dir = tmpdir / "output" input_document = tmpdir / "document.txt" input_document.write_text(example_text["EN"]) # Create a file in place of the output directory output_dir.touch() result = runner.invoke( main_function, f"-vv document --to DE {input_document} {output_dir}" ) assert result.exit_code == 1, f"exit: {result.exit_code}\n {result.output}" assert "already exists" in result.output def test_invalid_document(runner, tmpdir): tmpdir = pathlib.Path(tmpdir) output_dir = tmpdir / "output" input_document = tmpdir / "document.invalid" input_document.write_text(example_text["EN"]) result = runner.invoke( main_function, f"-vv document --to DE {input_document} {output_dir}" ) assert result.exit_code == 1, f"exit: {result.exit_code}\n {result.output}" assert "Invalid file" in result.output or "file extension" in result.output def test_glossary_no_subcommand(runner): result = runner.invoke(main_function, "glossary") assert result.exit_code == 2, f"exit: {result.exit_code}\n {result.output}" assert "required: subcommand" in result.output def test_glossary_create( runner, glossary_name, tmpdir, cleanup_matching_glossaries, example_glossary_csv, ): name_cli = f"{glossary_name}-cli" name_stdin = f"{glossary_name}-stdin" name_file = f"{glossary_name}-file" name_csv = f"{glossary_name}-csv" entries = {"Hallo": "Hello", "Maler": "Artist"} entries_tsv = deepl.convert_dict_to_tsv(entries) entries_cli = "\n".join(f"{s}={t}" for s, t in entries.items()) file = tmpdir / "glossary_entries" file.write(entries_tsv) try: result = runner.invoke( main_function, f'-vv glossary create --name "{name_cli}" --from DE --to EN ' f"{entries_cli}", ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" result = runner.invoke( main_function, f'-vv glossary create --name "{name_stdin}" --from DE --to EN -', input=entries_tsv, ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" result = runner.invoke( main_function, f'-vv glossary create --name "{name_file}" --from DE --to EN ' f"--file {file}", ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" result = runner.invoke( main_function, f'-vv glossary create --name "{name_csv}" --from EN --to DE ' f"--file {example_glossary_csv} --csv", ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" result = runner.invoke(main_function, "-vv glossary list") assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" assert name_cli in result.output assert name_stdin in result.output assert name_file in result.output assert name_csv in result.output # Cannot use --file option together with entries result = runner.invoke( main_function, f'-vv glossary create --name "{name_file}" --from DE --to EN ' f"--file {file} {entries_cli}", ) assert ( result.exit_code == 1 ), f"exit: {result.exit_code}\n {result.output}" assert "--file argument" in result.output finally: cleanup_matching_glossaries( lambda glossary: glossary.name in [name_file, name_cli, name_stdin, name_csv] ) def test_glossary_get(translator, runner, glossary_manager): with glossary_manager() as created_glossary: created_id = created_glossary.glossary_id result = runner.invoke(main_function, f"-vv glossary get {created_id}") print(result.output) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" assert created_id in result.output assert created_glossary.name in result.output def test_glossary_list(translator, runner, glossary_manager): with glossary_manager(glossary_name_suffix="1") as g1, glossary_manager( glossary_name_suffix="2" ) as g2, glossary_manager(glossary_name_suffix="3") as g3: glossary_list = [g1, g2, g3] result = runner.invoke(main_function, "-vv glossary list") assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" for glossary in glossary_list: assert glossary.name in result.output def test_glossary_entries(translator, runner, glossary_manager): entries = {"Hallo": "Hello", "Maler": "Artist"} with glossary_manager(entries=entries) as created_glossary: created_id = created_glossary.glossary_id result = runner.invoke( main_function, f"-vv glossary entries {created_id}" ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" for source, target in entries.items(): assert f"{source}\t{target}" in result.output def test_glossary_delete(translator, runner, glossary_manager): with glossary_manager() as created_glossary: created_id = created_glossary.glossary_id result = runner.invoke(main_function, "glossary list") assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" assert created_id in result.output # Remove the created glossary result = runner.invoke( main_function, f'glossary delete "{created_id}"' ) assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" result = runner.invoke(main_function, "glossary list") assert ( result.exit_code == 0 ), f"exit: {result.exit_code}\n {result.output}" assert created_id not in result.output TM_DEFAULT_ID = "a74d88fb-ed2a-4943-a664-a4512398b994" TM_EXAMPLE_TMX = ( '\n' '' 'Hallo' 'Hello' "\n" ) @pytest.fixture def cli_tmx_path(tmpdir): path = pathlib.Path(tmpdir) / "example.tmx" path.write_text(TM_EXAMPLE_TMX) return path def test_translation_memory_no_subcommand(runner): result = runner.invoke(main_function, "translation-memory") assert result.exit_code == 2, f"exit: {result.exit_code}\n {result.output}" assert "required: subcommand" in result.output @needs_mock_server def test_translation_memory_list_and_get(runner): result = runner.invoke(main_function, "translation-memory list") assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert TM_DEFAULT_ID in result.output result = runner.invoke( main_function, f"translation-memory get {TM_DEFAULT_ID}" ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert TM_DEFAULT_ID in result.output @needs_mock_server def test_translation_memory_segments(runner): result = runner.invoke( main_function, f"translation-memory segments {TM_DEFAULT_ID} --page-size 5", ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Total segments in translation memory:" in result.output assert "Next page cursor:" in result.output result = runner.invoke( main_function, f"translation-memory segments {TM_DEFAULT_ID} --all" ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "Next page cursor:" not in result.output @needs_mock_server def test_translation_memory_import_export_delete(runner, cli_tmx_path, tmpdir): result = runner.invoke( main_function, f'translation-memory import "{cli_tmx_path}" --name "CLI TM"', ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert "completed" in result.output match = re.search(r"Translation memory ID: (\S+)", result.output) assert match is not None, result.output translation_memory_id = match.group(1) output_path = pathlib.Path(tmpdir) / "exported.tmx" result = runner.invoke( main_function, f'translation-memory export {translation_memory_id} "{output_path}"', ) assert result.exit_code == 0, f"exit: {result.exit_code}\n {result.output}" assert output_path.exists() assert "This is a short sentence.

" f"{ar_ignore_part} This is another sentence." ) en_result = translator.translate_text( en_sentence_with_ar_ignore_part, target_lang="en-US", tag_handling="xml", ignore_tags="ignore", ) assert ar_ignore_part in en_result.text en_ignore_part = "This part should be ignored." ar_sentence_with_en_ignore_part = ( f"

هذه جملة قصيرة. {en_ignore_part} هذه جملة أخرى.

" ) ar_result = translator.translate_text( ar_sentence_with_en_ignore_part, target_lang="ar", tag_handling="xml", ignore_tags="ignore", ) assert en_ignore_part in ar_result.text def test_translate_with_enums(translator): result = translator.translate_text( example_text["EN"], source_lang=deepl.Language.ENGLISH, target_lang=deepl.Language.GERMAN, ) assert example_text["DE"] == result.text def test_invalid_authkey(server): translator = deepl.Translator("invalid", server_url=server.server_url) with pytest.raises(deepl.exceptions.AuthorizationException): translator.get_usage() def test_invalid_server_url(server): translator = deepl.Translator("invalid", server_url="http:/api.deepl.com") with pytest.raises(deepl.exceptions.DeepLException): translator.get_usage() def test_usage(translator): usage = translator.get_usage() assert "Usage this billing period" in str(usage) def test_language(translator): source_languages = translator.get_source_languages() for source_language in source_languages: if source_language.code == "EN": assert source_language.name == "English" assert str(source_language) == source_language.code assert source_language.supports_formality is None target_languages = translator.get_target_languages() for target_language in target_languages: if target_language.code == "DE": assert target_language.supports_formality assert target_language.supports_formality is not None def test_glossary_languages(translator): glossary_languages = translator.get_glossary_languages() assert len(glossary_languages) > 0 for language_pair in glossary_languages: assert len(language_pair.source_lang) > 0 assert len(language_pair.target_lang) > 0 def test_server_url_selected_based_on_auth_key(server): translator_normal = deepl.Translator("ABCD") translator_free = deepl.Translator("ABCD:fx") assert translator_normal.server_url == "https://api.deepl.com" assert translator_free.server_url == "https://api-free.deepl.com" @patch("requests.adapters.HTTPAdapter.send") def test_user_agent(mock_send): mock_send.return_value = _build_test_response() translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"]) translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert "requests/" in ua_header assert " python/" in ua_header assert "(" in ua_header @patch("requests.adapters.HTTPAdapter.send") def test_user_agent_opt_out(mock_send): mock_send.return_value = _build_test_response() translator = deepl.Translator( os.environ["DEEPL_AUTH_KEY"], send_platform_info=False ) translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert "requests/" not in ua_header assert " python/" not in ua_header assert "(" not in ua_header @patch("requests.adapters.HTTPAdapter.send") def test_custom_user_agent(mock_send): mock_send.return_value = _build_test_response() old_user_agent = deepl.http_client.user_agent deepl.http_client.user_agent = "my custom user agent" translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"]) translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert ua_header == "my custom user agent" deepl.http_client.user_agent = old_user_agent @patch("requests.adapters.HTTPAdapter.send") def test_user_agent_with_app_info(mock_send): mock_send.return_value = _build_test_response() translator = deepl.Translator( os.environ["DEEPL_AUTH_KEY"], ).set_app_info("sample_python_plugin", "1.0.2") translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert "requests/" in ua_header assert " python/" in ua_header assert "(" in ua_header assert " sample_python_plugin/1.0.2" in ua_header @patch("requests.adapters.HTTPAdapter.send") def test_user_agent_opt_out_with_app_info(mock_send): mock_send.return_value = _build_test_response() translator = deepl.Translator( os.environ["DEEPL_AUTH_KEY"], send_platform_info=False, ).set_app_info("sample_python_plugin", "1.0.2") translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert "requests/" not in ua_header assert " python/" not in ua_header assert "(" not in ua_header assert " sample_python_plugin/1.0.2" in ua_header @patch("requests.adapters.HTTPAdapter.send") def test_custom_user_agent_with_app_info(mock_send): mock_send.return_value = _build_test_response() old_user_agent = deepl.http_client.user_agent deepl.http_client.user_agent = "my custom user agent" translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"]).set_app_info( "sample_python_plugin", "1.0.2" ) translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert ua_header == "my custom user agent sample_python_plugin/1.0.2" deepl.http_client.user_agent = old_user_agent @patch("requests.adapters.HTTPAdapter.send") @patch("platform.platform") def test_user_agent_exception(platform_mock, mock_send): mock_send.return_value = _build_test_response() platform_mock.side_effect = OSError("mocked test exception") translator = deepl.Translator(os.environ["DEEPL_AUTH_KEY"]) translator.translate_text(example_text["EN"], target_lang="DA") ua_header = mock_send.call_args[0][0].headers["User-agent"] assert "deepl-python" in ua_header assert "requests/" not in ua_header assert " python/" not in ua_header assert "(" not in ua_header @needs_mock_proxy_server def test_proxy_usage( server, translator_with_random_auth_key, translator_with_random_auth_key_and_proxy, ): server.expect_proxy() translator_with_random_auth_key_and_proxy.get_usage() with pytest.raises(deepl.DeepLException): translator_with_random_auth_key.get_usage() @needs_mock_server def test_usage_no_response(translator, server, monkeypatch): server.no_response(2) # Lower the retry count and timeout for this test, and restore after test monkeypatch.setattr(deepl.http_client, "max_network_retries", 0) monkeypatch.setattr(deepl.http_client, "min_connection_timeout", 1.0) with pytest.raises(deepl.exceptions.ConnectionException): translator.get_usage() @needs_mock_server def test_translate_too_many_requests(translator, server, monkeypatch): server.respond_with_429(2) # Lower the retry count and timeout for this test, and restore after test monkeypatch.setattr(deepl.http_client, "max_network_retries", 1) monkeypatch.setattr(deepl.http_client, "min_connection_timeout", 1.0) with pytest.raises(deepl.exceptions.TooManyRequestsException): translator.translate_text(example_text["EN"], target_lang="DE") @needs_mock_server def test_usage_overrun(translator_with_random_auth_key, server, tmpdir): character_limit = 20 document_limit = 1 server.init_character_limit(character_limit) server.init_document_limit(document_limit) translator = translator_with_random_auth_key usage = translator.get_usage() assert usage.character.limit == character_limit assert usage.document.limit == document_limit assert "Characters: 0 of 20" in str(usage) assert "Documents: 0 of 1" in str(usage) tmpdir = pathlib.Path(tmpdir) input_path = tmpdir / "example_document.txt" input_path.write_text("a" * character_limit) output_path = tmpdir / "example_document_output.txt" translator.translate_document_from_filepath( input_path, output_path, target_lang="DE" ) usage = translator.get_usage() assert usage.any_limit_reached assert usage.document.limit_reached assert usage.character.limit_reached assert not usage.team_document.limit_reached # Test deprecated properties as well assert usage.any_limit_exceeded assert usage.document.limit_exceeded assert usage.character.limit_exceeded assert not usage.team_document.limit_exceeded with pytest.raises(deepl.exceptions.QuotaExceededException): translator.translate_document_from_filepath( input_path, output_path, target_lang="DE" ) with pytest.raises(deepl.exceptions.QuotaExceededException): translator.translate_text(example_text["EN"], target_lang="DE") @needs_mock_server def test_usage_team_document_limit( translator_with_random_auth_key, server, tmpdir ): team_document_limit = 1 server.init_character_limit(0) server.init_document_limit(0) server.init_team_document_limit(team_document_limit) translator = translator_with_random_auth_key usage = translator.get_usage() assert not usage.any_limit_reached assert "Characters" not in str(usage) assert "Documents" not in str(usage) assert "Team documents: 0 of 1" in str(usage) tmpdir = pathlib.Path(tmpdir) input_path = tmpdir / "example_document.txt" input_path.write_text("a") output_path = tmpdir / "example_document_output.txt" translator.translate_document_from_filepath( input_path, output_path, target_lang="DE" ) usage = translator.get_usage() assert usage.any_limit_reached assert not usage.document.limit_reached assert not usage.character.limit_reached assert usage.team_document.limit_reached def _build_test_response(): response = Mock(spec=Response) response.status_code = 200 response.text = ( '{"translations": [{' '"detected_source_language": "EN", ' '"text": "protonstråle", ' f'"billed_characters": {len(example_text["EN"])}' "}]}" ) response.headers = { "Content-Type": "application/json", "Server": "nginx", "Content-Length": str(len(response.text.encode("utf-8"))), "Connection": "keep-alive", "Access-Control-Allow-Origin": "*", } response.encoding = "utf-8" response.history = None response.raw = None response.is_redirect = False response.stream = False response.url = "https://api.deepl.com/v2/translate" return response DeepL-deepl-python-df50df2/tests/test_glossary.py000066400000000000000000000324571523706276600222540ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from .conftest import needs_real_server import deepl import pytest INVALID_GLOSSARY_ID = "invalid_glossary_id" NONEXISTENT_GLOSSARY_ID = "96ab91fd-e715-41a1-adeb-5d701f84a483" def test_glossary_create( translator, glossary_name, cleanup_matching_glossaries ): source_lang = "EN" target_lang = "DE" try: glossary = translator.create_glossary( glossary_name, source_lang, target_lang, {"Hello": "Hallo"} ) assert glossary.name == glossary_name assert glossary.source_lang == source_lang assert glossary.target_lang == target_lang # Note: ready field is indeterminate # Note: creation_time according to server might differ from local clock assert glossary.entry_count == 1 get_result = translator.get_glossary(glossary.glossary_id) assert get_result.name == glossary.name assert get_result.source_lang == glossary.source_lang assert get_result.target_lang == glossary.target_lang assert get_result.creation_time == glossary.creation_time assert get_result.entry_count == glossary.entry_count finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_create_csv( translator, glossary_name, cleanup_matching_glossaries, example_glossary_csv, example_glossary_csv_entries, ): source_lang = "EN" target_lang = "DE" try: with open(example_glossary_csv, "r") as csv_data: glossary = translator.create_glossary_from_csv( glossary_name, source_lang, target_lang, csv_data=csv_data ) assert glossary.entry_count == len(example_glossary_csv_entries) entries = translator.get_glossary_entries(glossary.glossary_id) assert entries == example_glossary_csv_entries finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_create_invalid( translator, glossary_name, cleanup_matching_glossaries ): try: with pytest.raises(ValueError): translator.create_glossary("", "EN", "DE", {"Hello": "Hallo"}) with pytest.raises(deepl.DeepLException): translator.create_glossary( glossary_name, "EN", "XX", {"Hello": "Hallo"} ) with pytest.raises(ValueError): translator.create_glossary(glossary_name, "EN", "DE", {}) finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_create_large(translator, glossary_manager): source_lang = "EN" target_lang = "DE" entries = {f"Source-${i}": f"Target-${i}" for i in range(10000)} with glossary_manager( source_lang=source_lang, target_lang=target_lang, entries=entries ) as created_glossary: assert created_glossary.entry_count == len(entries) retrieved_entries = translator.get_glossary_entries(created_glossary) assert entries == retrieved_entries def test_glossary_get(translator, glossary_manager): source_lang = "EN" target_lang = "DE" entries = {"Hello": "Hallo"} with glossary_manager( source_lang=source_lang, target_lang=target_lang, entries=entries ) as created_glossary: glossary = translator.get_glossary(created_glossary.glossary_id) assert glossary.glossary_id == created_glossary.glossary_id assert glossary.name == created_glossary.name assert glossary.source_lang == source_lang assert glossary.target_lang == target_lang assert glossary.entry_count == len(entries) with pytest.raises(deepl.DeepLException): translator.get_glossary(INVALID_GLOSSARY_ID) with pytest.raises(deepl.GlossaryNotFoundException): translator.get_glossary(NONEXISTENT_GLOSSARY_ID) def test_glossary_get_entries(translator, glossary_manager): source_lang = "EN" target_lang = "DE" entries = { "Apple": "Apfel", "Banana": "Banane", "A%=&": "B&=%", "\u0394\u3041": "\u6df1", "\U0001faa8": "\U0001fab5", } with glossary_manager( source_lang=source_lang, target_lang=target_lang, entries=entries ) as created_glossary: assert translator.get_glossary_entries(created_glossary) == entries assert ( translator.get_glossary_entries(created_glossary.glossary_id) == entries ) with pytest.raises(deepl.DeepLException): translator.get_glossary_entries(INVALID_GLOSSARY_ID) with pytest.raises(deepl.GlossaryNotFoundException): translator.get_glossary_entries(NONEXISTENT_GLOSSARY_ID) def test_glossary_list(translator, glossary_manager): with glossary_manager("EN", "DE", {"Hello": "Hallo"}) as created_glossary: glossaries = translator.list_glossaries() assert any( glossary.name == created_glossary.name for glossary in glossaries ) def test_glossary_delete(translator, glossary_manager): with glossary_manager() as created_glossary: translator.delete_glossary(created_glossary) with pytest.raises(deepl.GlossaryNotFoundException): translator.get_glossary(created_glossary.glossary_id) with pytest.raises(deepl.DeepLException): translator.delete_glossary(INVALID_GLOSSARY_ID) with pytest.raises(deepl.GlossaryNotFoundException): translator.delete_glossary(NONEXISTENT_GLOSSARY_ID) @needs_real_server def test_glossary_translate_text_sentence(translator, glossary_manager): source_lang = "EN" target_lang = "DE" input_text = "The artist was awarded a prize." with glossary_manager( source_lang=source_lang, target_lang=target_lang, entries={"artist": "Maler", "prize": "Gewinn"}, ) as created_glossary: result = translator.translate_text( input_text, source_lang="EN", target_lang="DE", glossary=created_glossary, ) assert "Maler" in result.text assert "Gewinn" in result.text def test_glossary_translate_text_basic(translator, glossary_manager): texts_en = ["Apple", "Banana"] texts_de = ["Apfel", "Banane"] entries_ende = {en: de for en, de in zip(texts_en, texts_de)} entries_deen = {de: en for de, en in zip(texts_de, texts_en)} with glossary_manager( source_lang="EN", target_lang="DE", entries=entries_ende, glossary_name_suffix="_ende", ) as glossary_ende, glossary_manager( source_lang="DE", target_lang="EN", entries=entries_deen, glossary_name_suffix="_deen", ) as glossary_deen: result = translator.translate_text_with_glossary( texts_en, glossary_ende ) assert [r.text for r in result] == texts_de # Using glossary with target=EN is possible, British English is assumed result = translator.translate_text_with_glossary( texts_de, glossary_deen ) assert [r.text for r in result] == texts_en # Can override to American English result = translator.translate_text_with_glossary( texts_de, glossary_deen, target_lang="EN-US" ) assert [r.text for r in result] == texts_en result = translator.translate_text( texts_de, source_lang="DE", target_lang="EN-US", glossary=glossary_deen.glossary_id, ) assert [r.text for r in result] == texts_en result = translator.translate_text( texts_de, source_lang="DE", target_lang="EN-US", glossary=glossary_deen, ) assert [r.text for r in result] == texts_en def test_glossary_translate_document( translator, glossary_manager, example_document_path, output_document_path, ): input_text = "artist\nprize" expected_output_text = "Maler\nGewinn" example_document_path.write_text(input_text) with glossary_manager( entries={"artist": "Maler", "prize": "Gewinn"}, source_lang="EN", target_lang="DE", ) as glossary: translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, source_lang="EN", target_lang="DE", glossary=glossary, ) assert expected_output_text == output_document_path.read_text() def test_glossary_ids_translate_text_basic(translator, glossary_manager): texts_en = ["Apple", "Banana"] texts_de = ["Apfel", "Banane"] entries_apple = {"Apple": "Apfel"} entries_banana = {"Banana": "Banane"} with glossary_manager( source_lang="EN", target_lang="DE", entries=entries_apple, glossary_name_suffix="_apple", ) as glossary_apple, glossary_manager( source_lang="EN", target_lang="DE", entries=entries_banana, glossary_name_suffix="_banana", ) as glossary_banana: # glossary_ids accepts GlossaryInfo objects result = translator.translate_text( texts_en, source_lang="EN", target_lang="DE", glossary_ids=[glossary_apple, glossary_banana], ) assert [r.text for r in result] == texts_de # glossary_ids accepts raw glossary ID strings result = translator.translate_text( texts_en, source_lang="EN", target_lang="DE", glossary_ids=[ glossary_apple.glossary_id, glossary_banana.glossary_id, ], ) assert [r.text for r in result] == texts_de def test_glossary_ids_translate_document( translator, glossary_manager, example_document_path, output_document_path, ): input_text = "artist\nprize" expected_output_text = "Maler\nGewinn" example_document_path.write_text(input_text) with glossary_manager( entries={"artist": "Maler"}, source_lang="EN", target_lang="DE", glossary_name_suffix="_artist", ) as glossary_artist, glossary_manager( entries={"prize": "Gewinn"}, source_lang="EN", target_lang="DE", glossary_name_suffix="_prize", ) as glossary_prize: translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, source_lang="EN", target_lang="DE", glossary_ids=[glossary_artist, glossary_prize], ) assert expected_output_text == output_document_path.read_text() def test_glossary_ids_translate_text_invalid(translator, glossary_manager): text = "Test" with glossary_manager( source_lang="EN", target_lang="DE", glossary_name_suffix="_ende" ) as glossary_ende, glossary_manager( source_lang="EN", target_lang="DE", glossary_name_suffix="_ende2" ) as glossary_ende2: # glossary_ids requires source_lang with pytest.raises(ValueError, match="source_lang is required"): translator.translate_text( text, target_lang="DE", glossary_ids=[glossary_ende], ) # glossary_ids cannot be combined with glossary with pytest.raises( ValueError, match="cannot be used together with the glossary" ): translator.translate_text( text, source_lang="EN", target_lang="DE", glossary=glossary_ende, glossary_ids=[glossary_ende2], ) # glossary_ids must not contain more than 5 IDs with pytest.raises( ValueError, match="not contain more than 5 glossary IDs" ): translator.translate_text( text, source_lang="EN", target_lang="DE", glossary_ids=[glossary_ende.glossary_id] * 6, ) def test_glossary_translate_text_invalid(translator, glossary_manager): text = "Test" with glossary_manager( source_lang="EN", target_lang="DE", glossary_name_suffix="_ende" ) as glossary_ende, glossary_manager( source_lang="DE", target_lang="EN", glossary_name_suffix="_deen" ) as glossary_deen: with pytest.raises(ValueError, match="source_lang is required"): translator.translate_text( text, target_lang="DE", glossary=glossary_ende ) with pytest.raises(ValueError, match="lang must match glossary"): translator.translate_text( text, source_lang="DE", target_lang="EN", glossary=glossary_ende, ) with pytest.raises( deepl.DeepLException, match='target_lang="EN" is deprecated' ): translator.translate_text( text, source_lang="DE", target_lang="EN", glossary=glossary_deen.glossary_id, ) with pytest.raises(ValueError, match="GlossaryInfo"): translator.translate_text_with_glossary( text, glossary=glossary_deen.glossary_id, ) DeepL-deepl-python-df50df2/tests/test_multilingual_glossary.py000066400000000000000000000435431523706276600250400ustar00rootroot00000000000000# Copyright 2025 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from deepl import util from deepl.api_data import MultilingualGlossaryDictionaryEntries from .conftest import needs_real_server import deepl import pytest INVALID_GLOSSARY_ID = "invalid_glossary_id" NONEXISTENT_GLOSSARY_ID = "96ab91fd-e715-41a1-adeb-5d701f84a483" def test_glossary_create( deepl_client, glossary_name, cleanup_matching_glossaries ): try: glossary_dicts = [] glossary_dicts.append( MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) ) glossary_dicts.append( MultilingualGlossaryDictionaryEntries( "DE", "EN", {"Hallo": "Hello"} ) ) glossary = deepl_client.create_multilingual_glossary( glossary_name, glossary_dicts ) assert glossary.name == glossary_name assert len(glossary.dictionaries) == len(glossary_dicts) for i in range(len(glossary.dictionaries)): assert ( glossary.dictionaries[i].source_lang == glossary_dicts[i].source_lang ) assert ( glossary.dictionaries[i].target_lang == glossary_dicts[i].target_lang ) assert glossary.dictionaries[i].entry_count == len( glossary_dicts[i].entries ) get_result = deepl_client.get_multilingual_glossary( glossary.glossary_id ) assert get_result.name == glossary.name assert get_result.creation_time == glossary.creation_time assert len(get_result.dictionaries) == len(glossary.dictionaries) for i in range(len(get_result.dictionaries)): assert ( get_result.dictionaries[i].source_lang == glossary.dictionaries[i].source_lang ) assert ( get_result.dictionaries[i].target_lang == glossary.dictionaries[i].target_lang ) assert ( get_result.dictionaries[i].entry_count == glossary.dictionaries[i].entry_count ) finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_dictionary_update( deepl_client, glossary_name, cleanup_matching_glossaries ): try: dict_ende = MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) dict_deen = MultilingualGlossaryDictionaryEntries( "DE", "EN", {"Hallo": "Hello"} ) glossary_dicts = [dict_deen, dict_ende] created_glossary = deepl_client.create_multilingual_glossary( glossary_name, glossary_dicts ) updated_entries = {"Hello": "Guten Tag", "Apple": "Apfel"} updated_glossary = ( deepl_client.update_multilingual_glossary_dictionary( created_glossary, MultilingualGlossaryDictionaryEntries( "EN", "DE", updated_entries ), ) ) assert updated_glossary.name == created_glossary.name assert len(updated_glossary.dictionaries) == len(glossary_dicts) assert updated_glossary.dictionaries for glossary_dict in updated_glossary.dictionaries: if ( glossary_dict.source_lang == "EN" and glossary_dict.target_lang == "DE" ): assert glossary_dict.entry_count == len(updated_entries) else: assert glossary_dict.source_lang == "DE" assert glossary_dict.target_lang == "EN" assert glossary_dict.entry_count == len(dict_deen.entries) finally: cleanup_matching_glossaries( lambda glossary: glossary.name == updated_glossary.name ) def test_glossary_name_update( deepl_client, glossary_name, cleanup_matching_glossaries ): try: glossary_dicts = [ MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) ] glossary = deepl_client.create_multilingual_glossary( glossary_name, glossary_dicts ) new_name = "New Glossary Name" updated_glossary = deepl_client.update_multilingual_glossary_name( glossary, new_name ) assert updated_glossary.name == new_name get_result = deepl_client.get_multilingual_glossary( glossary.glossary_id ) assert get_result.name == updated_glossary.name with pytest.raises(ValueError): deepl_client.update_multilingual_glossary_name(glossary, "") finally: cleanup_matching_glossaries(lambda glossary: glossary.name == new_name) def test_glossary_dictionary_replace( deepl_client, glossary_name, cleanup_matching_glossaries ): try: entries = {"Hello": "Hallo"} source_lang = "EN" target_lang = "DE" glossary_dicts = [ MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ) ] created_glossary = deepl_client.create_multilingual_glossary( glossary_name, glossary_dicts ) entries = {"Apple": "Apfel"} updated_glossary_dict = ( deepl_client.replace_multilingual_glossary_dictionary( created_glossary, MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ), ) ) assert updated_glossary_dict.entry_count == len(entries) entries_response = deepl_client.get_multilingual_glossary_entries( created_glossary.glossary_id, source_lang, target_lang ) assert len(entries_response.dictionaries) == len(glossary_dicts) assert entries_response.dictionaries[ 0 ].entries == util.convert_dict_to_tsv(entries) finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_create_csv( deepl_client, glossary_name, cleanup_matching_glossaries, example_glossary_csv, example_glossary_csv_entries, ): source_lang = "EN" target_lang = "DE" try: with open(example_glossary_csv, "r") as csv_data: glossary = deepl_client.create_multilingual_glossary_from_csv( glossary_name, source_lang, target_lang, csv_data=csv_data ) assert len(glossary.dictionaries) == 1 assert glossary.dictionaries[0].entry_count == len( example_glossary_csv_entries ) assert glossary.dictionaries[0].source_lang == source_lang assert glossary.dictionaries[0].target_lang == target_lang entries_response = deepl_client.get_multilingual_glossary_entries( glossary.glossary_id, source_lang, target_lang ) assert len(entries_response.dictionaries) == 1 assert ( util.convert_tsv_to_dict(entries_response.dictionaries[0].entries) == example_glossary_csv_entries ) finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_create_invalid( deepl_client, glossary_name, cleanup_matching_glossaries ): try: with pytest.raises(ValueError): glossary_dict = MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) deepl_client.create_multilingual_glossary("", [glossary_dict]) with pytest.raises(deepl.DeepLException): glossary_dict = MultilingualGlossaryDictionaryEntries( "EN", "XX", {"Hallo": "Hello"} ) deepl_client.create_multilingual_glossary( glossary_name, [glossary_dict] ) with pytest.raises(ValueError): glossary_dict = MultilingualGlossaryDictionaryEntries( "EN", "XX", {} ) deepl_client.create_multilingual_glossary( glossary_name, [glossary_dict] ) finally: cleanup_matching_glossaries( lambda glossary: glossary.name == glossary_name ) def test_glossary_create_large(deepl_client, multilingual_glossary_manager): source_lang = "EN" target_lang = "DE" entries = {f"Source-${i}": f"Target-${i}" for i in range(10000)} glossary_dicts = [ MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ) ] with multilingual_glossary_manager(glossary_dicts) as created_glossary: assert len(created_glossary.dictionaries) == len(glossary_dicts) assert created_glossary.dictionaries[0].entry_count == len(entries) glossary_dicts = deepl_client.get_multilingual_glossary_entries( created_glossary, source_lang, target_lang ).dictionaries assert entries == util.convert_tsv_to_dict(glossary_dicts[0].entries) def test_glossary_get(deepl_client, multilingual_glossary_manager): source_lang = "EN" target_lang = "DE" entries = {"Hello": "Hallo"} dictionaries = [ MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ) ] with multilingual_glossary_manager( dictionaries=dictionaries ) as created_glossary: glossary = deepl_client.get_multilingual_glossary( created_glossary.glossary_id ) assert glossary.glossary_id == created_glossary.glossary_id assert glossary.name == created_glossary.name with pytest.raises(deepl.DeepLException): deepl_client.get_multilingual_glossary(INVALID_GLOSSARY_ID) with pytest.raises(deepl.GlossaryNotFoundException): deepl_client.get_multilingual_glossary(NONEXISTENT_GLOSSARY_ID) def test_glossary_get_entries(deepl_client, multilingual_glossary_manager): source_lang = "EN" target_lang = "DE" entries = { "Apple": "Apfel", "Banana": "Banane", "A%=&": "B&=%", "\u0394\u3041": "\u6df1", "\U0001faa8": "\U0001fab5", } glossary_dicts = [ MultilingualGlossaryDictionaryEntries( source_lang, target_lang, entries ) ] with multilingual_glossary_manager(glossary_dicts) as created_glossary: result = deepl_client.get_multilingual_glossary_entries( created_glossary, source_lang, target_lang ).dictionaries assert util.convert_tsv_to_dict(result[0].entries) == entries get_by_id_result = deepl_client.get_multilingual_glossary_entries( created_glossary.glossary_id, source_lang, target_lang ).dictionaries assert len(get_by_id_result) == len(glossary_dicts) assert util.convert_tsv_to_dict(get_by_id_result[0].entries) == entries with pytest.raises(deepl.DeepLException): deepl_client.get_multilingual_glossary_entries( INVALID_GLOSSARY_ID, source_lang, target_lang ) with pytest.raises(deepl.GlossaryNotFoundException): deepl_client.get_multilingual_glossary_entries( NONEXISTENT_GLOSSARY_ID, source_lang, target_lang ) def test_glossary_list(deepl_client, multilingual_glossary_manager): glossary_dict = MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) with multilingual_glossary_manager([glossary_dict]) as created_glossary: glossaries = deepl_client.list_multilingual_glossaries() assert any( glossary.name == created_glossary.name for glossary in glossaries ) def test_glossary_delete(deepl_client, multilingual_glossary_manager): glossary_dicts = [ MultilingualGlossaryDictionaryEntries("EN", "DE", {"Hello": "Hallo"}) ] with multilingual_glossary_manager(glossary_dicts) as created_glossary: deepl_client.delete_multilingual_glossary(created_glossary) with pytest.raises(deepl.GlossaryNotFoundException): deepl_client.get_multilingual_glossary( created_glossary.glossary_id ) with pytest.raises(deepl.DeepLException): deepl_client.delete_multilingual_glossary(INVALID_GLOSSARY_ID) with pytest.raises(deepl.GlossaryNotFoundException): deepl_client.delete_multilingual_glossary(NONEXISTENT_GLOSSARY_ID) def test_glossary_dictionary_delete( deepl_client, multilingual_glossary_manager ): to_be_deleted = MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) to_remain = MultilingualGlossaryDictionaryEntries( "DE", "EN", {"Hallo": "Hello"} ) glossary_dicts = [to_be_deleted, to_remain] with multilingual_glossary_manager(glossary_dicts) as created_glossary: deepl_client.delete_multilingual_glossary_dictionary( created_glossary, source_lang=to_be_deleted.source_lang, target_lang=to_be_deleted.target_lang, ) get_result = deepl_client.get_multilingual_glossary( created_glossary.glossary_id ) assert len(get_result.dictionaries) == len(glossary_dicts) - 1 assert get_result.dictionaries[0].source_lang == to_remain.source_lang assert get_result.dictionaries[0].target_lang == to_remain.target_lang with pytest.raises(deepl.DeepLException): deepl_client.delete_multilingual_glossary_dictionary( INVALID_GLOSSARY_ID, source_lang="EN", target_lang="DE" ) with pytest.raises(deepl.GlossaryNotFoundException): deepl_client.delete_multilingual_glossary_dictionary( NONEXISTENT_GLOSSARY_ID, source_lang="EN", target_lang="DE" ) with pytest.raises( ValueError, match="must provide dictionary or both source_lang and target_lang", ): deepl_client.delete_multilingual_glossary_dictionary(created_glossary) @needs_real_server def test_glossary_translate_text_sentence( deepl_client, multilingual_glossary_manager ): source_lang = "EN" target_lang = "DE" input_text = "The artist was awarded a prize." glossary_dict = MultilingualGlossaryDictionaryEntries( source_lang, target_lang, {"artist": "Maler", "prize": "Gewinn"} ) with multilingual_glossary_manager( [glossary_dict], ) as created_glossary: result = deepl_client.translate_text( input_text, source_lang="EN", target_lang="DE", glossary=created_glossary, ) assert "Maler" in result.text assert "Gewinn" in result.text def test_glossary_translate_text_basic( deepl_client, multilingual_glossary_manager ): texts_en = ["Apple", "Banana"] texts_de = ["Apfel", "Banane"] entries_ende = {en: de for en, de in zip(texts_en, texts_de)} entries_deen = {de: en for de, en in zip(texts_de, texts_en)} ende_dict = MultilingualGlossaryDictionaryEntries("EN", "DE", entries_ende) deen_dict = MultilingualGlossaryDictionaryEntries("DE", "EN", entries_deen) glossary_dicts = [ende_dict, deen_dict] with multilingual_glossary_manager(glossary_dicts) as created_glossary: result = deepl_client.translate_text( texts_de, source_lang="DE", target_lang="EN-US", glossary=created_glossary.glossary_id, ) assert [r.text for r in result] == texts_en result = deepl_client.translate_text( texts_de, source_lang="DE", target_lang="EN-US", glossary=created_glossary, ) assert [r.text for r in result] == texts_en def test_glossary_translate_document( deepl_client, multilingual_glossary_manager, example_document_path, output_document_path, ): input_text = "artist\nprize" expected_output_text = "Maler\nGewinn" example_document_path.write_text(input_text) entries = {"artist": "Maler", "prize": "Gewinn"} glossary_dict = MultilingualGlossaryDictionaryEntries("EN", "DE", entries) with multilingual_glossary_manager([glossary_dict]) as glossary: deepl_client.translate_document_from_filepath( example_document_path, output_path=output_document_path, source_lang="EN", target_lang="DE", glossary=glossary, ) assert expected_output_text == output_document_path.read_text() def test_glossary_translate_text_invalid( deepl_client, multilingual_glossary_manager ): text = "Test" ende_dict = MultilingualGlossaryDictionaryEntries( "EN", "DE", {"Hello": "Hallo"} ) deen_dict = MultilingualGlossaryDictionaryEntries( "DE", "EN", {"Hallo": "Hello"} ) glossary_dicts = [ende_dict, deen_dict] with multilingual_glossary_manager(glossary_dicts) as glossary: with pytest.raises(ValueError, match="source_lang is required"): deepl_client.translate_text( text, target_lang="DE", glossary=glossary ) with pytest.raises( ValueError, match="must have a glossary with a " "dictionary for the given source_lang and target_lang", ): deepl_client.translate_text( text, source_lang="ES", target_lang="DE", glossary=glossary ) with pytest.raises( deepl.DeepLException, match='target_lang="EN" is deprecated' ): deepl_client.translate_text( text, source_lang="DE", target_lang="EN", glossary=glossary.glossary_id, ) with pytest.raises(ValueError, match="GlossaryInfo"): deepl_client.translate_text_with_glossary( text, glossary=glossary.glossary_id, ) DeepL-deepl-python-df50df2/tests/test_rephrase_text.py000066400000000000000000000022451523706276600232560ustar00rootroot00000000000000# Copyright 2025 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from .conftest import example_text, needs_real_server from deepl.api_data import WriteResult, WritingStyle def test_single_text(deepl_client): result = deepl_client.rephrase_text( example_text["EN"], target_lang="EN-GB" ) _check_sanity_of_improvements(example_text["EN"], result) @needs_real_server def test_business_style(deepl_client): input_text = "As Gregor Samsa awoke one morning from uneasy dreams he found himself transformed in his bed into a gigantic insect." # noqa result = deepl_client.rephrase_text( input_text, target_lang="EN-US", style=WritingStyle.BUSINESS.value ) _check_sanity_of_improvements(input_text, result) def _check_sanity_of_improvements( input_text: str, result: WriteResult, expected_lang_uppercase="EN", epsilon=0.2, ): assert result.detected_source_language.upper() == expected_lang_uppercase n_improved = len(result.text) n_original = len(input_text) assert 1 / (1.0 + epsilon) <= n_improved / n_original <= (1.0 + epsilon) DeepL-deepl-python-df50df2/tests/test_style_rules.py000066400000000000000000000203471523706276600227560ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import pytest from .conftest import example_text, needs_mock_server DEFAULT_STYLE_ID = "dca2e053-8ae5-45e6-a0d2-881156e7f4e4" @needs_mock_server def test_get_all_style_rules(deepl_client): style_rules = deepl_client.get_all_style_rules(detailed=True) assert isinstance(style_rules, list) assert len(style_rules) == 1 assert style_rules[0].style_id == DEFAULT_STYLE_ID assert style_rules[0].name == "Default Style Rule" assert style_rules[0].creation_time is not None assert style_rules[0].updated_time is not None assert style_rules[0].language == "en" assert style_rules[0].version == 1 assert style_rules[0].configured_rules is not None assert style_rules[0].custom_instructions is not None def test_style_rule_crud(deepl_client): # Create a style rule style_rule = deepl_client.create_style_rule( name="Test Style Rule", language="en", ) assert style_rule.style_id is not None assert style_rule.name == "Test Style Rule" assert style_rule.language == "en" style_id = style_rule.style_id # Get the style rule by ID retrieved = deepl_client.get_style_rule(style_id) assert retrieved.style_id == style_id assert retrieved.name == "Test Style Rule" # Update the name updated = deepl_client.update_style_rule_name(style_id, "Updated Name") assert updated.name == "Updated Name" # Update configured rules configured = deepl_client.update_style_rule_configured_rules( style_id, {"dates_and_times": {"calendar_era": "use_bc_and_ad"}}, ) assert configured.style_id == style_id # Create a custom instruction instruction = deepl_client.create_style_rule_custom_instruction( style_id, label="Test Instruction", prompt="Always use formal language", ) assert instruction.label == "Test Instruction" assert instruction.prompt == "Always use formal language" assert instruction.id is not None instruction_id = instruction.id # Get the custom instruction retrieved_instruction = deepl_client.get_style_rule_custom_instruction( style_id, instruction_id ) assert retrieved_instruction.label == "Test Instruction" # Update the custom instruction updated_instruction = deepl_client.update_style_rule_custom_instruction( style_id, instruction_id, label="Updated Instruction", prompt="Use very formal language", ) assert updated_instruction.label == "Updated Instruction" # Delete the custom instruction deepl_client.delete_style_rule_custom_instruction(style_id, instruction_id) # Delete the style rule deepl_client.delete_style_rule(style_id) def test_style_rule_accepts_style_rule_info(deepl_client): """Methods accepting Union[str, StyleRuleInfo] should work with objects.""" style_rule = deepl_client.create_style_rule( name="Object Test", language="en" ) # get_style_rule with StyleRuleInfo object retrieved = deepl_client.get_style_rule(style_rule) assert retrieved.style_id == style_rule.style_id # update_style_rule_name with StyleRuleInfo object updated = deepl_client.update_style_rule_name(style_rule, "New Name") assert updated.name == "New Name" # update_style_rule_configured_rules with StyleRuleInfo object configured = deepl_client.update_style_rule_configured_rules( style_rule, {"dates_and_times": {"calendar_era": "use_bc_and_ad"}} ) assert configured.style_id == style_rule.style_id # create/get/update/delete custom instruction with StyleRuleInfo object instruction = deepl_client.create_style_rule_custom_instruction( style_rule, label="Test", prompt="Be formal" ) assert instruction.id is not None deepl_client.get_style_rule_custom_instruction(style_rule, instruction.id) deepl_client.update_style_rule_custom_instruction( style_rule, instruction.id, label="Updated", prompt="Be casual" ) deepl_client.delete_style_rule_custom_instruction( style_rule, instruction.id ) # delete_style_rule with StyleRuleInfo object deepl_client.delete_style_rule(style_rule) def test_style_rule_validation(deepl_client): # create_style_rule with pytest.raises(ValueError, match="name must not be empty"): deepl_client.create_style_rule(name="", language="en") with pytest.raises(ValueError, match="language must not be empty"): deepl_client.create_style_rule(name="Test", language="") # get_style_rule with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.get_style_rule("") # update_style_rule_name with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.update_style_rule_name("", "New Name") with pytest.raises(ValueError, match="name must not be empty"): deepl_client.update_style_rule_name("some-id", "") # delete_style_rule with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.delete_style_rule("") # update_style_rule_configured_rules with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.update_style_rule_configured_rules("", {}) # create_style_rule_custom_instruction with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.create_style_rule_custom_instruction( "", label="L", prompt="P" ) with pytest.raises(ValueError, match="label must not be empty"): deepl_client.create_style_rule_custom_instruction( "some-id", label="", prompt="P" ) with pytest.raises(ValueError, match="prompt must not be empty"): deepl_client.create_style_rule_custom_instruction( "some-id", label="L", prompt="" ) # get_style_rule_custom_instruction with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.get_style_rule_custom_instruction("", "instr-id") with pytest.raises(ValueError, match="instruction_id must not be empty"): deepl_client.get_style_rule_custom_instruction("some-id", "") # update_style_rule_custom_instruction with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.update_style_rule_custom_instruction( "", "instr-id", label="L", prompt="P" ) with pytest.raises(ValueError, match="instruction_id must not be empty"): deepl_client.update_style_rule_custom_instruction( "some-id", "", label="L", prompt="P" ) with pytest.raises(ValueError, match="label must not be empty"): deepl_client.update_style_rule_custom_instruction( "some-id", "instr-id", label="", prompt="P" ) with pytest.raises(ValueError, match="prompt must not be empty"): deepl_client.update_style_rule_custom_instruction( "some-id", "instr-id", label="L", prompt="" ) # delete_style_rule_custom_instruction with pytest.raises(ValueError, match="style_rule must not be empty"): deepl_client.delete_style_rule_custom_instruction("", "instr-id") with pytest.raises(ValueError, match="instruction_id must not be empty"): deepl_client.delete_style_rule_custom_instruction("some-id", "") @needs_mock_server def test_translate_text_with_style_rule(deepl_client): # Note: this test may use the mock server that will not translate the text, # therefore we do not check the translated result. _ = deepl_client.translate_text( example_text["DE"], target_lang="EN-US", style_rule=DEFAULT_STYLE_ID ) @needs_mock_server def test_translate_document_with_style_rule( deepl_client, example_document_path, output_document_path, ): # Note: this test may use the mock server that will not translate the text, # therefore we do not check the translated result. example_document_path.write_text(example_text["DE"]) deepl_client.translate_document_from_filepath( example_document_path, output_path=output_document_path, source_lang="DE", target_lang="EN-US", style_rule=DEFAULT_STYLE_ID, ) DeepL-deepl-python-df50df2/tests/test_translate_document.py000066400000000000000000000257631523706276600243060ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import re from .conftest import example_text, needs_mock_server, needs_real_server import deepl import io import pathlib import pytest import time default_lang_args = {"target_lang": "DE", "source_lang": "EN"} def test_translate_document_from_filepath( translator, example_document_path, example_document_translation, output_document_path, ): status = translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, **default_lang_args, ) assert example_document_translation == output_document_path.read_text() assert status.billed_characters == len(example_text["EN"]) assert status.status == deepl.DocumentStatus.Status.DONE assert status.done # Note: cases with invalid file paths are not tested, because standard # library functions are used. @needs_mock_server def test_translate_document_with_retry( translator, server, example_document_path, example_document_translation, output_document_path, monkeypatch, ): server.no_response(1) # Lower the timeout for this test, and restore after test monkeypatch.setattr(deepl.http_client, "min_connection_timeout", 1.0) translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, **default_lang_args, ) assert example_document_translation == output_document_path.read_text() @needs_mock_server def test_translate_document_with_waiting( translator, server, example_document_path, example_document_translation, output_document_path, ): server.set_doc_queue_time(2000) server.set_doc_translate_time(2000) translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, **default_lang_args, ) assert example_document_translation == output_document_path.read_text() @needs_mock_server def test_translate_document_with_timeout_succeeds( translator, server, example_document_path, example_document_translation, output_document_path, ): server.set_doc_queue_time(2 * 1000) server.set_doc_translate_time(2 * 1000) translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, timeout_s=10, **default_lang_args, ) assert example_document_translation == output_document_path.read_text() @needs_mock_server def test_translate_document_with_manual_timeout_fails( translator, server, example_document_path, output_document_path, ): server.set_doc_queue_time(11 * 1000) server.set_doc_translate_time(11 * 1000) with pytest.raises( deepl.DeepLException, match="Manual timeout of 10s exceeded for document translation", ): translator.translate_document_from_filepath( example_document_path, output_path=output_document_path, timeout_s=10, **default_lang_args, ) @needs_mock_server def test_translate_large_document( translator, example_large_document_path, example_large_document_translation ): with io.BytesIO() as output_file: with open(example_large_document_path, "rb") as input_file: translator.translate_document( input_file, output_file, **default_lang_args ) assert ( example_large_document_translation == output_file.getvalue().decode() ) @needs_real_server def test_translate_document_formality( translator, example_document_path, output_document_path ): example_document_path.write_text("How are you?") translator.translate_document_from_filepath( example_document_path, output_document_path, formality=deepl.Formality.MORE, **default_lang_args, ) assert "Ihnen" in output_document_path.read_text() # Wie geht es Ihnen?" translator.translate_document_from_filepath( example_document_path, output_document_path, formality=deepl.Formality.LESS, **default_lang_args, ) assert "dir" in output_document_path.read_text() # Wie geht es dir? example_document_path.write_text("How are you?") translator.translate_document_from_filepath( example_document_path, output_document_path, formality=deepl.Formality.PREFER_MORE, **default_lang_args, ) assert "Ihnen" in output_document_path.read_text() translator.translate_document_from_filepath( example_document_path, output_document_path, formality=deepl.Formality.PREFER_LESS, **default_lang_args, ) assert "dir" in output_document_path.read_text() @needs_mock_server def test_document_output_format( translator, example_document_path, output_dir_path, ): # Mock server supports only TXT and HTML files, so translate TXT->HTML example_document_path.write_text(example_text["EN"]) output_document_path = output_dir_path / "example.html" translator.translate_document_from_filepath( example_document_path, output_document_path, **default_lang_args, ) output = output_document_path.read_text() assert example_text["DE"] == output def test_document_failure_during_translation( translator, example_document_path, output_document_path ): # Translating text from DE to DE will trigger error example_document_path.write_text(example_text["DE"]) with pytest.raises( deepl.exceptions.DocumentTranslationException, ) as exc_info: translator.translate_document_from_filepath( example_document_path, output_document_path, target_lang="DE" ) # Ensure that document translation error contains document handle exception = exc_info.value assert "Source and target language" in str(exception) assert exception.document_handle is not None match = re.compile("ID: [0-9A-F]{32}, key: [0-9A-F]{64}") assert match.search(str(exception)) is not None # document_request is a deprecated alias for document_handle assert exception.document_request is not None def test_invalid_document(translator, tmpdir): tmpdir = pathlib.Path(tmpdir) output_dir = tmpdir / "output" output_dir.mkdir() input_document = tmpdir / "document.invalid" input_document.write_text(example_text["EN"]) output_document = output_dir / "document.invalid" with pytest.raises( deepl.DeepLException, match="(Invalid file)|(file extension)" ): translator.translate_document_from_filepath( input_document, output_path=output_document, **default_lang_args ) @needs_mock_server def test_translate_document_low_level( translator, example_document_path, example_document_translation, output_document_path, server, ): # Set a small document queue time to attempt downloading a queued document server.set_doc_queue_time(100) with open(example_document_path, "rb") as infile: handle = translator.translate_document_upload( infile, **default_lang_args ) status = translator.translate_document_get_status(handle) assert status.ok and not status.done # Test recreating a document handle from id & key doc_id, doc_key = handle.document_id, handle.document_key del handle handle = deepl.DocumentHandle(doc_id, doc_key) status = translator.translate_document_get_status(handle) assert status.ok while status.ok and not status.done: status = translator.translate_document_get_status(handle) time.sleep(0.2) assert status.ok and status.done with open(output_document_path, "wb") as outfile: translator.translate_document_download(handle, outfile) assert output_document_path.read_text() == example_document_translation def test_translate_document_string(translator, server): input_string = example_text["EN"] handle = translator.translate_document_upload( input_string, source_lang="EN", target_lang="DE", filename="test.txt", ) status = translator.translate_document_get_status(handle) while status.ok and not status.done: status = translator.translate_document_get_status(handle) time.sleep(status.seconds_remaining or 1) assert status.ok response = translator.translate_document_download(handle) try: output = bytes() for chunk in response.iter_content(chunk_size=128): output += chunk output_string = output.decode() assert output_string == example_text["DE"] finally: response.close() @needs_mock_server def test_translate_document_request_fields( translator, example_document_path, server, output_document_path ): server.set_doc_queue_time(2000) server.set_doc_translate_time(2000) time_before = time.time() with open(example_document_path, "rb") as infile: handle = translator.translate_document_upload( infile, **default_lang_args ) status = translator.translate_document_get_status(handle) assert status.ok while status.ok and not status.done: status = translator.translate_document_get_status(handle) assert ( status.status == deepl.DocumentStatus.Status.QUEUED or status.seconds_remaining >= 0 ) time.sleep(0.2) with open(output_document_path, "wb") as outfile: translator.translate_document_download(handle, outfile) time_after = time.time() assert status.billed_characters == len(example_document_path.read_text()) assert time_after - time_before > 4.0 assert example_text["DE"] == output_document_path.read_text() @needs_mock_server def test_translate_document_download_generator( translator, example_large_document_path, output_document_path, example_large_document_translation, ): with open(example_large_document_path, "rb") as infile: handle = translator.translate_document_upload( infile, **default_lang_args ) status = translator.translate_document_get_status(handle) while status.ok and not status.done: status = translator.translate_document_get_status(handle) time.sleep(0.2) assert status.ok and status.done response = translator.translate_document_download(handle) with open(output_document_path, "wb") as outfile: for chunk in response.iter_content(chunk_size=128): outfile.write(chunk) assert ( output_document_path.read_text() == example_large_document_translation ) @needs_mock_server def test_recreate_document_handle_invalid(translator): doc_id = "12AB" * 8 # IDs are 32 hex characters doc_key = "CD34" * 16 # Keys are 64 hex characters handle = deepl.DocumentHandle(doc_id, doc_key) with pytest.raises(deepl.DeepLException, match="Not found"): translator.translate_document_get_status(handle) DeepL-deepl-python-df50df2/tests/test_translate_text.py000066400000000000000000000351461523706276600234500ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. from .conftest import example_text, needs_mock_server, needs_real_server import deepl import pytest import re import time def test_single_text(translator): result = translator.translate_text(example_text["EN"], target_lang="DE") assert example_text["DE"] == result.text assert "EN" == result.detected_source_lang assert example_text["DE"] == str(result) assert result.billed_characters == len(example_text["EN"]) @pytest.mark.parametrize( "model_type", [model_type for model_type in deepl.ModelType], ) def test_model_type(translator, model_type): result = translator.translate_text( example_text["EN"], target_lang="DE", model_type=model_type ) assert result.model_type_used is not None def test_string_list(translator): texts = [example_text["FR"], example_text["EN"]] result = translator.translate_text(texts, target_lang="DE") assert example_text["DE"] == result[0].text assert example_text["DE"] == result[1].text def test_string_generator(translator): def gen(): yield example_text["EN"] yield example_text["EN"] yield example_text["EN"] result = translator.translate_text(gen(), target_lang="DE") assert [example_text["DE"]] * 3 == [r.text for r in result] assert ["EN"] * 3 == [r.detected_source_lang for r in result] def test_source_lang(translator): def check_result(result): assert result.text == example_text["DE"] assert result.detected_source_lang == "EN" check_result( translator.translate_text(example_text["EN"], target_lang="DE") ) check_result( translator.translate_text( example_text["EN"], source_lang="En", target_lang="DE" ) ) check_result( translator.translate_text( example_text["EN"], source_lang="en", target_lang="DE" ) ) source_languages = translator.get_source_languages() source_language_en = next( language for language in source_languages if language.code == "EN" ) check_result( translator.translate_text( example_text["EN"], source_lang=source_language_en, target_lang="DE", ) ) def test_target_lang(translator): def check_result(result): assert result.text == example_text["DE"] assert result.detected_source_lang == "EN" check_result( translator.translate_text(example_text["EN"], target_lang="De") ) check_result( translator.translate_text(example_text["EN"], target_lang="de") ) target_languages = translator.get_target_languages() target_language_de = next( language for language in target_languages if language.code == "DE" ) check_result( translator.translate_text( example_text["EN"], target_lang=target_language_de, ) ) with pytest.raises(deepl.DeepLException, match="deprecated"): translator.translate_text(example_text["DE"], target_lang="EN") with pytest.raises(deepl.DeepLException, match="deprecated"): translator.translate_text(example_text["DE"], target_lang="PT") def test_invalid_language(translator): with pytest.raises( deepl.DeepLException, match="target_lang.*not supported", ): translator.translate_text(example_text["EN"], target_lang="XX") with pytest.raises( deepl.DeepLException, match="source_lang.*not supported", ): translator.translate_text( example_text["EN"], source_lang="XX", target_lang="DE" ) def test_skip_language_check(server): translator = deepl.Translator( server.auth_key, server_url=server.server_url, skip_language_check=True ) with pytest.raises(deepl.DeepLException, match="target_lang"): translator.translate_text(example_text["EN"], target_lang="XX") with pytest.raises(deepl.DeepLException, match="source_lang"): translator.translate_text( example_text["EN"], source_lang="XX", target_lang="DE" ) def test_invalid_text(translator): with pytest.raises(TypeError, match="text parameter"): translator.translate_text(123, target_lang="DE") with pytest.raises(TypeError): translator.translate_text(target_lang="DE") @needs_mock_server def test_translate_with_retries(translator, server): server.respond_with_429(2) time_before = time.time() result = translator.translate_text(example_text["EN"], target_lang="DE") time_after = time.time() assert example_text["DE"] == result.text assert "EN" == result.detected_source_lang assert time_after - time_before > 1.0 def test_formality(translator, server): input_text = "How are you?" informal = "dir" # Wie geht's dir? formal = "Ihnen" # Wie geht's Ihnen? result = translator.translate_text( input_text, target_lang="DE", formality=deepl.Formality.LESS ) if not server.is_mock_server: assert informal in result.text # Default formality is automatic, so the output may be either # formal or informal result = translator.translate_text( input_text, target_lang="DE", formality=deepl.Formality.DEFAULT ) if not server.is_mock_server: assert formal in result.text or informal in result.text result = translator.translate_text( input_text, target_lang="DE", formality=deepl.Formality.MORE ) if not server.is_mock_server: assert formal in result.text # Specifying formality as string is also permitted result = translator.translate_text( input_text, target_lang="DE", formality="less" ) if not server.is_mock_server: assert informal in result.text result = translator.translate_text( input_text, target_lang="DE", formality="default" ) if not server.is_mock_server: assert formal in result.text or informal in result.text result = translator.translate_text( input_text, target_lang="DE", formality="more" ) if not server.is_mock_server: assert formal in result.text # formality parameter is case-insensitive result = translator.translate_text( input_text, target_lang="DE", formality="Less" ) if not server.is_mock_server: assert informal in result.text with pytest.raises(deepl.DeepLException, match=r".*formality.*"): _ = translator.translate_text( input_text, target_lang="DE", formality="invalid" ) with pytest.raises( deepl.DeepLException, match=r".*Target language.*formality.*" ): _ = translator.translate_text( "Test", target_lang="EN-US", formality="more" ) result = translator.translate_text( input_text, target_lang="DE", formality=deepl.Formality.PREFER_LESS ) if not server.is_mock_server: assert informal in result.text result = translator.translate_text( input_text, target_lang="DE", formality=deepl.Formality.PREFER_MORE ) if not server.is_mock_server: assert formal in result.text # Using prefer_ * with a language that does not support formality is not # an error translator.translate_text( input_text, target_lang="TR", formality=deepl.Formality.PREFER_MORE ) with pytest.raises( deepl.DeepLException, match=r".*Target language.*formality.*" ): _ = translator.translate_text( input_text, target_lang="TR", formality="more" ) def test_preserve_formatting(translator): # Note: this test may use the mock server that will not translate the text, # therefore we do not check the translated result. _ = translator.translate_text( example_text["EN"], target_lang="DE", preserve_formatting=True ) _ = translator.translate_text( example_text["EN"], target_lang="DE", preserve_formatting=False ) def test_context(translator): # In German, "scharf" can mean: # - spicy/hot when referring to food, or # - sharp when referring to other objects such as a knife (Messer). text = "Das ist scharf!" _ = translator.translate_text(text, target_lang="en-US") # Result: "That is hot!" _ = translator.translate_text( text, target_lang="en-US", context="Das ist ein Messer" ) # Result: "That is sharp!" def test_split_sentences_basic(translator): text = """If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea.""" # Note: this test may use the mock server that will not translate the text, # therefore we do not check the translated result. _ = translator.translate_text( text, target_lang="DE", split_sentences=deepl.SplitSentences.OFF ) _ = translator.translate_text( text, target_lang="DE", split_sentences=deepl.SplitSentences.ALL ) _ = translator.translate_text( text, target_lang="DE", split_sentences=deepl.SplitSentences.NO_NEWLINES, ) _ = translator.translate_text(text, target_lang="DE", split_sentences="0") _ = translator.translate_text(text, target_lang="DE", split_sentences="1") _ = translator.translate_text( text, target_lang="DE", split_sentences="nonewlines" ) with pytest.raises(deepl.DeepLException, match=r".*split_sentences.*"): _ = translator.translate_text( text, target_lang="DE", split_sentences="invalid" ) def test_tag_handling_basic(translator): text = """

This is an example sentence.

""" # Note: this test may use the mock server that will not translate the text, # therefore we do not check the translated result. _ = translator.translate_text(text, target_lang="DE", tag_handling="xml") _ = translator.translate_text(text, target_lang="DE", tag_handling="html") @needs_real_server def test_tag_handling_xml(translator): text = """ A document's title This is a sentence split across two <span> tags that should be treated as one. Here is a sentence. Followed by a second one. This sentence will not be translated. """ result = translator.translate_text( text, target_lang="DE", tag_handling="xml", outline_detection=False, non_splitting_tags="span", splitting_tags=["title", "par"], ignore_tags=["raw"], ) assert "This sentence will not be translated." in result.text assert re.compile(".*Der Titel.*").search(result.text) @needs_real_server def test_tag_handling_html(translator): text = """

My First Heading

My first paragraph.

""" result = translator.translate_text( text, target_lang="DE", tag_handling="html" ) assert "

Meine erste Überschrift

" in result.text assert '

My first paragraph.

' in result.text def test_empty_auth_key(server): with pytest.raises(ValueError, match=r"auth_key must not be empty"): deepl.Translator("", server_url=server.server_url) def test_invalid_auth_key(server): translator = deepl.Translator("invalid", server_url=server.server_url) with pytest.raises( deepl.AuthorizationException, match=r".*Authorization failure.*" ): translator.translate_text("Hello, world!", target_lang="DE") def test_empty_text(translator): with pytest.raises(ValueError, match=r".*empty.*"): translator.translate_text("", target_lang="DE") def test_mixed_case_languages(translator): result = translator.translate_text(example_text["DE"], target_lang="en-us") assert example_text["EN-US"] == result.text.lower() assert "DE" == result.detected_source_lang result = translator.translate_text(example_text["DE"], target_lang="EN-us") assert example_text["EN-US"] == result.text.lower() assert "DE" == result.detected_source_lang result = translator.translate_text( example_text["DE"], source_lang="de", target_lang="EN-US" ) assert example_text["EN-US"] == result.text.lower() assert "DE" == result.detected_source_lang result = translator.translate_text( example_text["DE"], source_lang="dE", target_lang="EN-US" ) assert example_text["EN-US"] == result.text.lower() assert "DE" == result.detected_source_lang def test_extra_body_params(translator): # Checks `target_lang` is overwritten, does not check # debug is actually sent extra = {"target_lang": "FR", "debug": "1"} res = translator.translate_text( example_text["EN"], target_lang="DE", extra_body_parameters=extra, ) assert isinstance(res, deepl.TextResult) assert example_text["FR"] == res.text assert "EN" == res.detected_source_lang assert res.billed_characters == len(example_text["EN"]) @needs_real_server def test_custom_instructions(translator): text = "I am testing if custom instructions are working correctly." no_custom_instructions_result = translator.translate_text( text, target_lang="DE", ) custom_instructions_result = translator.translate_text( text, target_lang="DE", custom_instructions=["Use informal language", "Be concise"], ) assert isinstance(custom_instructions_result, deepl.TextResult) assert custom_instructions_result.text is not None assert len(custom_instructions_result.text) > 0 assert custom_instructions_result.detected_source_lang == "EN" # Check that applying custom instructions results in different translations assert ( custom_instructions_result.text != no_custom_instructions_result.text ) @needs_real_server def test_tag_handling_version_v1(translator): text = "

Hello world

" result = translator.translate_text( text, target_lang="DE", tag_handling="html", tag_handling_version="v1" ) assert isinstance(result, deepl.TextResult) assert result.text is not None assert len(result.text) > 0 @needs_real_server def test_tag_handling_version_v2(translator): text = "

Hello world

" result = translator.translate_text( text, target_lang="DE", tag_handling="html", tag_handling_version="v2" ) assert isinstance(result, deepl.TextResult) assert result.text is not None assert len(result.text) > 0 DeepL-deepl-python-df50df2/tests/test_translation_memories.py000066400000000000000000000242051523706276600246370ustar00rootroot00000000000000# Copyright 2022 DeepL SE (https://www.deepl.com) # Use of this source code is governed by an MIT # license that can be found in the LICENSE file. import deepl import pathlib import pytest from .conftest import example_text, needs_mock_server DEFAULT_TM_ID = "a74d88fb-ed2a-4943-a664-a4512398b994" EXAMPLE_TMX = ( '\n' '' 'Hallo' 'Hello' "\n" ) @pytest.fixture def example_tmx_path(tmpdir): path = pathlib.Path(tmpdir) / "example.tmx" path.write_text(EXAMPLE_TMX) return path @pytest.fixture def imported_translation_memory(deepl_client, example_tmx_path): """Imports a translation memory and yields its ID, deleting it afterwards.""" job = deepl_client.import_translation_memory_from_filepath( example_tmx_path, display_name="Test TM" ) translation_memory_id = job.result.translation_memory_id yield translation_memory_id try: deepl_client.delete_translation_memory(translation_memory_id) except deepl.DeepLException: pass @needs_mock_server def test_list_translation_memories(deepl_client): translation_memories = deepl_client.list_translation_memories() assert isinstance(translation_memories, list) assert len(translation_memories) > 0 assert translation_memories[0].translation_memory_id is not None assert translation_memories[0].name is not None assert translation_memories[0].source_language is not None assert isinstance(translation_memories[0].target_languages, list) assert isinstance(translation_memories[0].segment_count, int) @needs_mock_server def test_translate_text_with_translation_memory(deepl_client): _ = deepl_client.translate_text( example_text["DE"], target_lang="EN-US", translation_memory=DEFAULT_TM_ID, ) @needs_mock_server def test_translate_text_with_translation_memory_and_threshold(deepl_client): _ = deepl_client.translate_text( example_text["DE"], target_lang="EN-US", translation_memory=DEFAULT_TM_ID, translation_memory_threshold=80, ) @needs_mock_server def test_translate_document_with_translation_memory( deepl_client, example_document_path, output_document_path, ): example_document_path.write_text(example_text["DE"]) deepl_client.translate_document_from_filepath( example_document_path, output_path=output_document_path, source_lang="DE", target_lang="EN-US", translation_memory=DEFAULT_TM_ID, ) @needs_mock_server def test_translate_document_with_translation_memory_and_threshold( deepl_client, example_document_path, output_document_path, ): example_document_path.write_text(example_text["DE"]) deepl_client.translate_document_from_filepath( example_document_path, output_path=output_document_path, source_lang="DE", target_lang="EN-US", translation_memory=DEFAULT_TM_ID, translation_memory_threshold=80, ) @needs_mock_server def test_get_translation_memory(deepl_client): translation_memory = deepl_client.get_translation_memory(DEFAULT_TM_ID) assert translation_memory.translation_memory_id == DEFAULT_TM_ID assert translation_memory.name assert translation_memory.source_language assert isinstance(translation_memory.target_languages, list) assert isinstance(translation_memory.segment_count, int) assert translation_memory.creation_time is not None assert translation_memory.updated_time is not None @needs_mock_server def test_get_translation_memory_accepts_info_object(deepl_client): listed = deepl_client.list_translation_memories()[0] translation_memory = deepl_client.get_translation_memory(listed) assert ( translation_memory.translation_memory_id == listed.translation_memory_id ) @needs_mock_server def test_get_translation_memory_not_found(deepl_client): with pytest.raises(deepl.DeepLException): deepl_client.get_translation_memory( "00000000-0000-0000-0000-000000000000" ) @needs_mock_server def test_list_translation_memory_segments(deepl_client): page = deepl_client.list_translation_memory_segments(DEFAULT_TM_ID) assert len(page.segments) > 0 assert page.segment_count > 0 segment = page.segments[0] assert segment.source_segment_id assert segment.source_text assert len(segment.targets) > 0 assert segment.targets[0].target_language assert segment.targets[0].target_text @needs_mock_server def test_list_translation_memory_segments_pagination(deepl_client): first = deepl_client.list_translation_memory_segments( DEFAULT_TM_ID, page_size=5 ) assert len(first.segments) == 5 assert first.next_page_cursor is not None second = deepl_client.list_translation_memory_segments( DEFAULT_TM_ID, page_size=5, page_cursor=first.next_page_cursor ) assert len(second.segments) > 0 first_ids = {segment.source_segment_id for segment in first.segments} second_ids = {segment.source_segment_id for segment in second.segments} assert first_ids.isdisjoint(second_ids) @needs_mock_server def test_list_translation_memory_segments_filter(deepl_client): unfiltered = deepl_client.list_translation_memory_segments(DEFAULT_TM_ID) filtered = deepl_client.list_translation_memory_segments( DEFAULT_TM_ID, filter_text="Nummer 7" ) assert len(filtered.segments) < len(unfiltered.segments) # segment_count is TM-level metadata and unaffected by the filter assert filtered.segment_count == unfiltered.segment_count @needs_mock_server def test_import_translation_memory_from_filepath( deepl_client, example_tmx_path ): job = deepl_client.import_translation_memory_from_filepath( example_tmx_path, display_name="Imported TM" ) assert job.operation == "import" assert job.product == "translation_memory" assert job.done assert job.ok assert job.result.status == "completed" assert job.result.translation_memory_id imported = deepl_client.get_translation_memory( job.result.translation_memory_id ) assert imported.name == "Imported TM" deepl_client.delete_translation_memory(imported) @needs_mock_server def test_create_translation_memory_import_awaits_upload( deepl_client, example_tmx_path ): created = deepl_client.create_translation_memory_import( file_name=example_tmx_path.name, content_length=example_tmx_path.stat().st_size, display_name="Awaiting Upload TM", ) assert created.job_id assert created.upload_url job = deepl_client.get_translation_memory_job(created.job_id) assert job.status == "awaiting_input" assert job.result.required_action assert not job.done @needs_mock_server def test_create_translation_memory_import_rejects_invalid_file(deepl_client): with pytest.raises(ValueError): deepl_client.create_translation_memory_import("", 100) with pytest.raises(ValueError): deepl_client.create_translation_memory_import("example.tmx", 0) @needs_mock_server def test_export_translation_memory_to_filepath( deepl_client, imported_translation_memory, tmpdir ): output_path = pathlib.Path(tmpdir) / "exported.tmx" job = deepl_client.export_translation_memory_to_filepath( imported_translation_memory, output_path ) assert job.operation == "export" assert job.done assert job.ok assert output_path.exists() assert " MultilingualGlossaryInfo` - **Description**: Use this method to update or create a glossary dictionary's entries - **Parameters**: - `glossary`: The ID or `MultilingualGlossaryInfo` of the glossary to update. - `glossary_dict`: The glossary dictionary including its new entries. - **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary. - **Note**: This method will either update the glossary's entries if they exist for the given glossary dictionary's language pair, or adds any new ones to the dictionary if not. It will also create a new glossary dictionary if one did not exist for the given language pair. - **Example**: ```python entries = {"artist": "Maler", "hello": "guten tag"} dictionaries = [MultilingualGlossaryDictionaryEntries("EN", "DE", entries)] my_glossary = deepl_client.create_multilingual_glossary( "My glossary", dictionaries ) new_entries = {"hello": "hallo", "prize": "Gewinn"} glossary_dict = MultilingualGlossaryDictionaryEntries("EN", "DE", new_entries) updated_glossary = deepl_client.update_multilingual_glossary_dictionary( my_glossary, glossary_dict ) entries_response = deepl_client.get_multilingual_glossary_entries(my_glossary, "EN", "DE") print(entries_response.dictionaries[0]) # "{'artist': 'Maler', 'hello': 'hallo', 'prize': 'Gewinn'}" ``` ### Update Multilingual Glossary Dictionary from CSV - **Method**: `update_multilingual_glossary_dictionary_from_csv(glossary: Union[str, MultilingualGlossaryInfo], source_lang: Union[str, Language], target_lang: Union[str, Language], csv_data: Union[TextIO, BinaryIO, str, bytes, Any]) -> MultilingualGlossaryInfo` - **Description**: This method allows you to update or create a glossary dictionary using entries in CSV format. - **Parameters**: - `glossary`: The ID or `MultilingualGlossaryInfo` of the glossary to update. - `source_lang`: Language of source entries. - `target_lang`: Language of target entries. - `csv_data`: CSV data containing glossary entries. - **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary. - **Example**: ```python # Replace a glossary dictionary from CSV # Open the CSV file assuming UTF-8 encoding. If your file contains a BOM, # consider using encoding='utf-8-sig' instead. with open('/path/to/glossary_file.csv', 'r', encoding='utf-8') as csv_file: csv_data = csv_file.read() # Read the file contents as a string my_csv_glossary = deepl_client.update_multilingual_glossary_dictionary_from_csv( glossary="4c81ffb4-2e...", source_lang="EN", target_lang="DE", csv_data=csv_data, ) ### Update Multilingual Glossary Name - **Method**: `update_multilingual_glossary_name(glossary: Union[str, MultilingualGlossaryInfo], name: str) -> MultilingualGlossaryInfo` - **Description**: This method allows you to update the name of an existing glossary. - **Parameters**: - `glossary`: The ID or `MultilingualGlossaryInfo` of the glossary to update. - `name`: The new name for the glossary. - **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary. - **Example**: ```python updated_glossary = deepl_client.update_multilingual_glossary_name("4c81ffb4-2e...", "New Glossary Name") ### Replace a Multilingual Glossary Dictionary - **Method**: `replace_multilingual_glossary_dictionary(glossary: Union[str, MultilingualGlossaryInfo], glossary_dict: MultilingualGlossaryDictionaryEntries) -> MultilingualGlossaryInfo` - **Description**: This method replaces the existing glossary dictionary with a new set of entries. - **Parameters**: - `glossary`: The ID or `MultilingualGlossaryInfo` of the glossary whose dictionary will be replaced. - `glossary_dict`: The new glossary dictionary entries that will replace any existing ones for that language pair. - **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary. - **Note**: Ensure that the new dictionary entries are complete and valid, as this method will completely overwrite the existing entries. It will also create a new glossary dictionary if one did not exist for the given language pair. - **Example**: ```python new_glossary_dict = MultilingualGlossaryDictionaryEntries("EN", "DE", {"goodbye": "auf Wiedersehen"}) replaced_glossary = deepl_client.replace_multilingual_glossary_dictionary("4c81ffb4-2e...", new_glossary_dict) ### Replace Multilingual Glossary Dictionary from CSV - **Method**: `replace_multilingual_glossary_dictionary_from_csv(glossary: Union[str, MultilingualGlossaryInfo], source_lang: Union[str, Language], target_lang: Union[str, Language], csv_data: Union[TextIO, BinaryIO, str, bytes, Any]) -> MultilingualGlossaryInfo` - **Description**: This method allows you to replace or create a glossary dictionary using entries in CSV format. - **Parameters**: - `glossary`: The ID or `MultilingualGlossaryInfo` of the glossary to update. - `source_lang`: Language of source entries. - `target_lang`: Language of target entries. - `csv_data`: CSV data containing glossary entries. - **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary. - **Example**: ```python # Replace a glossary dictionary from CSV # Open the CSV file assuming UTF-8 encoding. If your file contains a BOM, # consider using encoding='utf-8-sig' instead. with open('/path/to/glossary_file.csv', 'r', encoding='utf-8') as csv_file: csv_data = csv_file.read() # Read the file contents as a string my_csv_glossary = deepl_client.replace_multilingual_glossary_dictionary_from_csv( glossary="4c81ffb4-2e...", source_lang="EN", target_lang="DE", csv_data=csv_data, ) ### Delete a Multilingual Glossary Dictionary - **Method**: `delete_multilingual_glossary_dictionary(glossary: Union[str, MultilingualGlossaryInfo], dictionary: Optional[MultilingualGlossaryDictionaryInfo] = None, source_lang: Optional[str] = None, target_lang: Optional[str] = None) -> None` - **Description**: This method deletes a specified glossary dictionary from a given glossary. - **Parameters**: - `glossary`: The ID or `MultilingualGlossaryInfo` of the glossary containing the dictionary to delete. - `dictionary`: An optional parameter that specifies the dictionary to delete. This can be a `MultilingualGlossaryDictionaryInfo` object or both `source_lang` and `target_lang` to identify the dictionary. - `source_lang`: An optional parameter representing the source language of the dictionary. - `target_lang`: An optional parameter representing the target language of the dictionary. - **Returns**: None - **Migration Note**: Ensure that your application logic correctly identifies the dictionary to delete. If using `source_lang` and `target_lang`, both must be provided to specify the dictionary. - **Example**: ```python glossary_dict_deen = MultilingualGlossaryDictionaryEntries("EN", "DE", {"hello": "hallo"}) glossary_dict_ende = MultilingualGlossaryDictionaryEntries("DE", "EN", {"hallo": "hello"}) glossary_dicts = [glossary_dict_deen, glossary_dict_ende] created_glossary = deepl_client.create_multilingual_glossary("My Glossary", glossary_dicts) # Delete via specifying the glossary dictionary deepl_client.delete_multilingual_glossary_dictionary(created_glossary, created_glossary.dictionaries[0]) # Delete via specifying the language pair deepl_client.delete_multilingual_glossary_dictionary(created_glossary, source_lang="DE", target_lang="EN") ```