pax_global_header00006660000000000000000000000064151046527730014524gustar00rootroot0000000000000052 comment=e39c8b3778802227ca2549c1946648e8a1034ef5 aio-libs-aiobotocore-e39c8b3/000077500000000000000000000000001510465277300161505ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/.codecov.yml000066400000000000000000000006021510465277300203710ustar00rootroot00000000000000--- codecov: branch: master coverage: range: "95..100" status: project: false flags: library: paths: - aiobotocore/ configs: paths: - requirements/ - ".git*" - "*.toml" - "*.yml" changelog: paths: - CHANGES/ - CHANGES.rst docs: paths: - docs/ - "*.md" - "*.rst" - "*.txt" tests: paths: - tests/ aio-libs-aiobotocore-e39c8b3/.git-blame-ignore-revs000066400000000000000000000003441510465277300222510ustar00rootroot00000000000000# You can configure git to automatically use this file with the following config: # git config --global blame.ignoreRevsFile .git-blame-ignore-revs # Apply codestyle across all codebase 821a649c41e1a5295c7cbd9fd85d1a6d3596b94b aio-libs-aiobotocore-e39c8b3/.github/000077500000000000000000000000001510465277300175105ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/.github/FUNDING.yml000066400000000000000000000002671510465277300213320ustar00rootroot00000000000000--- # These are supported funding model platforms github: - thehesiod - terrycain patreon: open_collective: ko_fi: tidelift: community_bridge: liberapay: issuehunt: otechie: custom: aio-libs-aiobotocore-e39c8b3/.github/ISSUE_TEMPLATE/000077500000000000000000000000001510465277300216735ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000015541510465277300243720ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Checklist** - [ ] I have reproduced in environment where `pip check` passes without errors - [ ] I have provided `pip freeze` results - [ ] I have provided sample code or detailed way to reproduce - [ ] I have tried the same code in botocore to ensure this is an aiobotocore specific issue - [ ] I have tried similar code in aiohttp to ensure this is is an aiobotocore specific issue - [ ] I have checked the latest and older versions of aiobotocore/aiohttp/python to see if this is a regression / injection **pip freeze results** **Environment:** - Python Version: [e.g. 3.6] - OS name and version: [e.g. darwin stretch] **Additional context** Add any other context about the problem here. aio-libs-aiobotocore-e39c8b3/.github/dependabot.yml000066400000000000000000000012701510465277300223400ustar00rootroot00000000000000--- # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" cooldown: default-days: 4 - package-ecosystem: "pip" directory: "/docs" schedule: interval: "weekly" cooldown: default-days: 4 - package-ecosystem: "uv" directory: "/" schedule: interval: "weekly" cooldown: default-days: 4 aio-libs-aiobotocore-e39c8b3/.github/pull_request_template.md000066400000000000000000000025701510465277300244550ustar00rootroot00000000000000### Description of Change *Replace this text with why this change is required and how it was accomplished* ### Assumptions *Replace this text with any assumptions made (if any)* ### Checklist for All Submissions * [ ] I have added change info to [CHANGES.rst](https://github.com/aio-libs/aiobotocore/blob/master/CHANGES.rst) * [ ] If this is resolving an issue (needed so future developers can determine if change is still necessary and under what conditions) (can be provided via link to issue with these details): * [ ] Detailed description of issue * [ ] Alternative methods considered (if any) * [ ] How issue is being resolved * [ ] How issue can be reproduced * [ ] If this is providing a new feature (can be provided via link to issue with these details): * [ ] Detailed description of new feature * [ ] Why needed * [ ] Alternatives methods considered (if any) ### Checklist when updating botocore and/or aiohttp versions * [ ] I have read and followed [CONTRIBUTING.rst](https://github.com/aio-libs/aiobotocore/blob/master/CONTRIBUTING.rst#how-to-upgrade-botocore) * [ ] I have updated test_patches.py where/if appropriate (also check if no changes necessary) * [ ] I have ensured that the awscli/boto3 versions match the updated botocore version * [ ] I have added URL to diff: https://github.com/boto/botocore/compare/[CURRENT_BOTO_VERSION_TAG]...[NEW_BOTO_VERSION_TAG] aio-libs-aiobotocore-e39c8b3/.github/workflows/000077500000000000000000000000001510465277300215455ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/.github/workflows/ci-cd.yml000066400000000000000000000057121510465277300232540ustar00rootroot00000000000000--- name: CI/CD permissions: {} on: merge_group: push: branches: - master tags: - >- [0-9]+.[0-9]+.[0-9]+ - >- [0-9]+.[0-9]+.[0-9]+.dev[0-9]+ pull_request: branches: - master - >- [0-9].[0-9]+.[0-9]+ env: FORCE_COLOR: 1 jobs: build: uses: ./.github/workflows/reusable-build.yml test: # yamllint disable-line rule:line-length name: test${{ '' }} # zizmor: ignore[obfuscation] nest jobs under the same sidebar category strategy: matrix: python-version: - 3.9 - >- 3.10 - 3.11 - 3.12 - 3.13 - 3.14 os: - ubuntu-24.04 - ubuntu-24.04-arm no-httpx: - false experimental: - false include: - python-version: 3.14 # add a no-httpx run os: ubuntu-24.04 no-httpx: true experimental: false fail-fast: false uses: ./.github/workflows/reusable-test.yml with: python-version: ${{ matrix.python-version }} os: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental }} enable-cache: ${{ github.ref_type == 'tag' && 'false' || 'auto' }} no-httpx: ${{ matrix.no-httpx }} secrets: codecov-token: ${{ secrets.CODECOV_TOKEN }} zizmor: name: 🌈 zizmor permissions: security-events: write # yamllint disable-line rule:line-length uses: zizmorcore/workflow/.github/workflows/reusable-zizmor.yml@3bb5e95068d0f44b6d2f3f7e91379bed1d2f96a8 check: # This job does nothing and is only used for the branch protection if: always() needs: - build - test - zizmor runs-on: ubuntu-24.04 timeout-minutes: 5 steps: - name: Decide whether the needed jobs succeeded or failed # yamllint disable-line rule:line-length uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} pypi-publish: name: Publish Python 🐍 distribution 📦 to PyPI if: github.ref_type == 'tag' # only publish on tag pushes needs: - check runs-on: ubuntu-24.04 environment: name: pypi url: https://pypi.org/project/aiobotocore/${{ github.ref_name }} permissions: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write timeout-minutes: 5 steps: - name: Download distribution 📦 uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ - name: Check if distribution 📦 names match git tag run: | test -f "dist/aiobotocore-${REF_NAME}.tar.gz" test -f "dist/aiobotocore-${REF_NAME}-py3-none-any.whl" env: REF_NAME: ${{ github.ref_name }} - name: Publish distribution 📦 to PyPI # yamllint disable-line rule:line-length uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 aio-libs-aiobotocore-e39c8b3/.github/workflows/reusable-build.yml000066400000000000000000000016701510465277300251730ustar00rootroot00000000000000--- name: Reusable build permissions: {} on: workflow_call: env: FORCE_COLOR: 1 jobs: build: name: Build distribution 📦 runs-on: ubuntu-24.04 timeout-minutes: 5 steps: - uses: actions/checkout@v5 with: persist-credentials: false - name: Install uv # yamllint disable-line rule:line-length uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: # yamllint disable-line rule:line-length enable-cache: | # zizmor: ignore[cache-poisoning] cache is disabled when publishing to prevent poisoning ${{ github.ref_type == 'tag' && 'false' || 'auto' }} - name: Build distribution 📦 run: uv build - name: Check distribution 📦 run: uvx twine check --strict dist/* - name: Upload distribution 📦 uses: actions/upload-artifact@v5 with: name: python-package-distributions path: dist/ aio-libs-aiobotocore-e39c8b3/.github/workflows/reusable-test.yml000066400000000000000000000041721510465277300250530ustar00rootroot00000000000000--- name: Reusable test permissions: {} on: workflow_call: inputs: python-version: type: string required: true os: type: string required: true continue-on-error: type: boolean enable-cache: type: string required: true no-httpx: type: boolean required: true secrets: codecov-token: required: false env: FORCE_COLOR: 1 jobs: test: name: Test Python ${{ inputs.python-version }} on ${{ inputs.os }}${{ inputs.no-httpx && ' no-httpx' || '' }} runs-on: ${{ inputs.os }} continue-on-error: ${{ inputs.continue-on-error }} env: UV_FROZEN: 1 timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@v5 with: persist-credentials: false submodules: true - name: Install uv # yamllint disable-line rule:line-length uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ inputs.python-version }} enable-cache: ${{ inputs.enable-cache }} - name: Run pre-commit hooks run: | uv run make pre-commit - name: Run unittests if: ${{ ! inputs.no-httpx }} env: COLOR: 'yes' run: | uv run --extra=httpx make mototest - name: Run unittests without httpx installed if: ${{ inputs.no-httpx }} env: COLOR: 'yes' HTTP_BACKEND: 'aiohttp' run: | uv run make mototest - name: Upload coverage to Codecov # yamllint disable-line rule:line-length uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: token: ${{ secrets.codecov-token }} # not required for public repos files: ./coverage.xml # yamllint disable-line rule:line-length flags: unittests,os-${{ inputs.os }},python-${{ inputs.python-version }}${{ inputs.no-httpx && ',no-httpx' || '' }} # optional name: codecov-umbrella # optional fail_ci_if_error: true # optional (default = false) verbose: true # optional (default = false) aio-libs-aiobotocore-e39c8b3/.github/workflows/stale.yml000066400000000000000000000017731510465277300234100ustar00rootroot00000000000000--- # This workflow warns and then closes issues and PRs that have had no activity # for a specified amount of time. # # You can adjust the behavior by modifying this file. # For more information, see: # https://github.com/actions/stale name: Mark stale issues and pull requests on: schedule: - cron: '31 21 * * *' workflow_dispatch: jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write timeout-minutes: 5 steps: - uses: actions/stale@v10 with: stale-issue-message: >- This issue has been marked as stale because it has been inactive for more than 60 days. Please update this pull request or it will be automatically closed in 7 days. stale-pr-message: >- This pull request has been marked as stale because it has been inactive for more than 60 days. Please update this pull request or it will be automatically closed in 7 days. operations-per-run: '60' aio-libs-aiobotocore-e39c8b3/.gitignore000066400000000000000000000016421510465277300201430ustar00rootroot00000000000000# generated by users and intentionally not pushed to the repo requirements.txt # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ pyvenv/ build/ develop-eggs/ dist/ downloads/ eggs/ lib/ lib64/ parts/ .run/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage* .cache nosetests.xml coverage.xml cover .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # PyCharm .idea *.iml # rope .ropeproject # we can't check in lock-file because it needs to be generated per python version Pipfile.lock aio-libs-aiobotocore-e39c8b3/.pre-commit-config.yaml000066400000000000000000000017441510465277300224370ustar00rootroot00000000000000--- ci: autofix_prs: false autoupdate_schedule: quarterly repos: - repo: 'https://github.com/pre-commit/pre-commit-hooks' rev: v5.0.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.12.0 hooks: - id: ruff-check args: [--fix] - id: ruff-format - repo: https://github.com/astral-sh/uv-pre-commit rev: 0.8.24 hooks: - id: uv-lock - repo: https://github.com/adrienverge/yamllint rev: v1.37.1 hooks: - id: yamllint args: [--strict] - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.34.0 hooks: - id: check-dependabot - id: check-github-workflows - id: check-readthedocs - id: check-jsonschema alias: check-github-workflows-require-timeout name: Check GitHub Actions workflow jobs set timeout-minutes args: - --builtin-schema - github-workflows-require-timeout files: ^\.github/workflows/[^/]+$ types: - yaml aio-libs-aiobotocore-e39c8b3/.readthedocs.yml000066400000000000000000000003201510465277300212310ustar00rootroot00000000000000--- version: 2 build: os: ubuntu-22.04 tools: python: "3.12" sphinx: configuration: docs/conf.py python: install: - path: . - requirements: docs/requirements.txt formats: - htmlzip - epub aio-libs-aiobotocore-e39c8b3/.yamllint000066400000000000000000000003531510465277300200030ustar00rootroot00000000000000--- extends: default rules: indentation: level: error indent-sequences: false truthy: allowed-values: - >- false - >- true - >- # Allow "on" key name in GHA CI/CD workflow definitions on aio-libs-aiobotocore-e39c8b3/CHANGES.rst000066400000000000000000000365531510465277300177660ustar00rootroot00000000000000Changes ------- 2.25.2 (2025-11-10) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification 2.25.1 (2025-10-28) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification 2.25.0 (2025-10-10) ^^^^^^^^^^^^^^^^^^^ * switch async test runner from pytest-asyncio to AnyIO * turn ``AioClientArgsCreator.get_client_args()`` and ``AioClientCreator._get_client_args()`` into asynchronous methods * bump botocore dependency specification 2.24.3 (2025-10-06) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.24.2 (2025-09-05) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.24.1 (2025-08-15) ^^^^^^^^^^^^^^^^^^^ * fix endpoint circular import error 2.24.0 (2025-07-31) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.23.2 (2025-07-24) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.23.1 (2025-07-16) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.23.0 (2025-06-12) ^^^^^^^^^^^^^^^^^^^ * drop support for Python 3.8 (EOL) * bump botocore dependency specification * add experimental support for ``httpx``. The backend can be activated when creating a new session: ``session.create_client(..., config=AioConfig(http_session_cls=aiobotocore.httpxsession.HttpxSession))``. It's not fully tested and some features from aiohttp have not been ported, but feedback on what you're missing and bug reports are very welcome. 2.22.0 (2025-04-29) ^^^^^^^^^^^^^^^^^^^ * fully patch ``ClientArgsCreator.get_client_args()`` * patch ``AioEndpoint.__init__()`` * patch ``EventStream._parse_event()``, ``ResponseParser`` and subclasses * use SPDX license identifier for project metadata * upstream support for the smithy-rpc-v2-cbor protocol * bump botocore dependency specification 2.21.1 (2025-03-04) ^^^^^^^^^^^^^^^^^^^ * fix for refreshable credential account-id lookup 2.21.0 (2025-02-28) ^^^^^^^^^^^^^^^^^^^ * make `AioDeferredRefreshableCredentials` subclass of `DeferredRefreshableCredentials` * make `AioSSOCredentialFetcher` subclass of `SSOCredentialFetcher` * bump botocore dependency specification 2.20.1.dev0 (2025-02-24) ^^^^^^^^^^^^^^^^^^^^^^^^ * upstream http response header fixes to be more in-line with botocore 2.20.0 (2025-02-19) ^^^^^^^^^^^^^^^^^^^ * patch `AwsChunkedWrapper.read` * bump botocore dependency specification 2.19.0 (2025-01-22) ^^^^^^^^^^^^^^^^^^^ * support custom `ttl_dns_cache` connector configuration * relax botocore dependency specification 2.18.0 (2025-01-17) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.17.0 (2025-01-06) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification * add missing dependencies `python-dateutil`, `jmespath`, `multidict`, and `urllib3` 2.16.1 (2024-12-26) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification 2.16.0 (2024-12-16) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.15.2 (2024-10-09) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification 2.15.1 (2024-09-19) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification 2.15.0 (2024-09-10) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.14.0 (2024-08-28) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.13.3 (2024-08-22) ^^^^^^^^^^^^^^^^^^^ * fix ``create_waiter_with_client()`` * relax botocore dependency specification 2.13.2 (2024-07-18) ^^^^^^^^^^^^^^^^^^^ * fix for #1125 due to missing patch of StreamingChecksumBody 2.13.1 (2024-06-24) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.13.0 (2024-05-16) ^^^^^^^^^^^^^^^^^^^ * address breaking change introduced in `aiohttp==3.9.2` #882 2.12.4 (2024-05-16) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.12.3 (2024-04-11) ^^^^^^^^^^^^^^^^^^^ * relax botocore dependency specification 2.12.2 (2024-04-01) ^^^^^^^^^^^^^^^^^^^ * expose configuration of ``http_session_cls`` in ``AioConfig`` 2.12.1 (2024-03-04) ^^^^^^^^^^^^^^^^^^^ * fix use of proxies #1070 2.12.0 (2024-02-28) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.11.2 (2024-02-02) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.11.1 (2024-01-25) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.11.0 (2024-01-19) ^^^^^^^^^^^^^^^^^^^ * send project-specific `User-Agent` HTTP header #853 2.10.0 (2024-01-18) ^^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.9.1 (2024-01-17) ^^^^^^^^^^^^^^^^^^ * fix race condition in S3 Express identity cache #1072 2.9.0 (2023-12-12) ^^^^^^^^^^^^^^^^^^ * bump botocore dependency specification 2.8.0 (2023-11-28) ^^^^^^^^^^^^^^^^^^ * add AioStubber that returns AioAWSResponse() * remove confusing `aiobotocore.session.Session` symbol * bump botocore dependency specification 2.7.0 (2023-10-17) ^^^^^^^^^^^^^^^^^^ * add support for Python 3.12 * drop more Python 3.7 support (EOL) * relax botocore dependency specification 2.6.0 (2023-08-11) ^^^^^^^^^^^^^^^^^^ * bump aiohttp minimum version to 3.7.4.post0 * drop python 3.7 support (EOL) 2.5.4 (2023-08-07) ^^^^^^^^^^^^^^^^^^ * fix __aenter__ attribute error introduced in refresh bugfix (#1031) 2.5.3 (2023-08-06) ^^^^^^^^^^^^^^^^^^ * add more support for Python 3.11 * bump botocore to 1.31.17 * add waiter.wait return * fix SSO token refresh bug #1025 2.5.2 (2023-07-06) ^^^^^^^^^^^^^^^^^^ * fix issue #1020 2.5.1 (2023-06-27) ^^^^^^^^^^^^^^^^^^ * bump botocore to 1.29.161 2.5.0 (2023-03-06) ^^^^^^^^^^^^^^^^^^ * bump botocore to 1.29.76 (thanks @jakob-keller #999) 2.4.2 (2022-12-22) ^^^^^^^^^^^^^^^^^^ * fix retries (#988) 2.4.1 (2022-11-28) ^^^^^^^^^^^^^^^^^^ * Adds support for checksums in streamed request trailers (thanks @terrycain #962) 2.4.0 (2022-08-25) ^^^^^^^^^^^^^^^^^^ * bump botocore to 1.27.59 2.3.4 (2022-06-23) ^^^^^^^^^^^^^^^^^^ * fix select_object_content 2.3.3 (2022-06-07) ^^^^^^^^^^^^^^^^^^ * fix connect timeout while getting IAM creds * fix test files appearing in distribution package 2.3.2 (2022-05-08) ^^^^^^^^^^^^^^^^^^ * fix 3.6 testing and and actually fix 3.6 support 2.3.1 (2022-05-06) ^^^^^^^^^^^^^^^^^^ * fix 3.6 support * AioConfig: allow keepalive_timeout to be None (thanks @dnlserrano #933) 2.3.0 (2022-05-05) ^^^^^^^^^^^^^^^^^^ * fix encoding issue by swapping to AioAWSResponse and AioAWSRequest to behave more like botocore * fix exceptions mappings 2.2.0 (2022-03-16) ^^^^^^^^^^^^^^^^^^ * remove deprecated APIs * bump to botocore 1.24.21 * re-enable retry of aiohttp.ClientPayloadError 2.1.2 (2022-03-03) ^^^^^^^^^^^^^^^^^^ * fix httpsession close call 2.1.1 (2022-02-10) ^^^^^^^^^^^^^^^^^^ * implement asynchronous non-blocking adaptive retry strategy 2.1.0 (2021-12-14) ^^^^^^^^^^^^^^^^^^ * bump to botocore 1.23.24 * fix aiohttp resolver config param #906 2.0.1 (2021-11-25) ^^^^^^^^^^^^^^^^^^ * revert accidental dupe of _register_s3_events #867 (thanks @eoghanmurray) * Support customizing the aiohttp connector resolver class #893 (thanks @orf) * fix timestream query #902 2.0.0 (2021-11-02) ^^^^^^^^^^^^^^^^^^ * bump to botocore 1.22.8 * turn off default ``AIOBOTOCORE_DEPRECATED_1_4_0_APIS`` env var to match botocore module. See notes in 1.4.0. 1.4.2 (2021-09-03) ^^^^^^^^^^^^^^^^^^ * Fix missing close() method on http session (thanks `@terrycain `_) * Fix for verify=False 1.4.1 (2021-08-24) ^^^^^^^^^^^^^^^^^^ * put backwards incompatible changes behind ``AIOBOTOCORE_DEPRECATED_1_4_0_APIS`` env var. This means that `#876 `_ will not work unless this env var has been set to 0. 1.4.0 (2021-08-20) ^^^^^^^^^^^^^^^^^^ * fix retries via config `#877 `_ * remove AioSession and get_session top level names to match botocore_ * change exceptions raised to match those of botocore_, see `mappings `_ 1.3.3 (2021-07-12) ^^^^^^^^^^^^^^^^^^ * fix AioJSONParser `#872 `_ 1.3.2 (2021-07-07) ^^^^^^^^^^^^^^^^^^ * Bump to botocore_ to `1.20.106 `_ 1.3.1 (2021-06-11) ^^^^^^^^^^^^^^^^^^ * TCPConnector: change deprecated ssl_context to ssl * fix non awaited generate presigned url calls `#868 `_ 1.3.0 (2021-04-09) ^^^^^^^^^^^^^^^^^^ * Bump to botocore_ to `1.20.49 `_ `#856 `_ 1.2.2 (2021-03-11) ^^^^^^^^^^^^^^^^^^ * Await call to async method _load_creds_via_assume_role `#858 `_ (thanks `@puzza007 `_) 1.2.1 (2021-02-10) ^^^^^^^^^^^^^^^^^^ * verify strings are now correctly passed to aiohttp.TCPConnector `#851 `_ (thanks `@FHTMitchell `_) 1.2.0 (2021-01-11) ^^^^^^^^^^^^^^^^^^ * bump botocore to `1.19.52 `_ * use passed in http_session_cls param to create_client `#797 `_ 1.1.2 (2020-10-07) ^^^^^^^^^^^^^^^^^^ * fix AioPageIterator search method #831 (thanks `@joseph-jones `_) 1.1.1 (2020-08-31) ^^^^^^^^^^^^^^^^^^ * fix s3 region redirect bug #825 1.1.0 (2020-08-18) ^^^^^^^^^^^^^^^^^^ * bump botocore to 1.17.44 1.0.7 (2020-06-04) ^^^^^^^^^^^^^^^^^^ * fix generate_db_auth_token via #816 1.0.6 (2020-06-04) ^^^^^^^^^^^^^^^^^^ * revert __getattr__ fix as it breaks ddtrace 1.0.5 (2020-06-03) ^^^^^^^^^^^^^^^^^^ * Fixed AioSession.get_service_data emit call #811 via #812 * Fixed async __getattr__ #789 via #803 1.0.4 (2020-04-15) ^^^^^^^^^^^^^^^^^^ * Fixed S3 Presigned Post not being async 1.0.3 (2020-04-09) ^^^^^^^^^^^^^^^^^^ * Fixes typo when using credential process 1.0.2 (2020-04-05) ^^^^^^^^^^^^^^^^^^ * Disable Client.__getattr__ emit for now #789 1.0.1 (2020-04-01) ^^^^^^^^^^^^^^^^^^ * Fixed signing requests with explicit credentials 1.0.0 (2020-03-31) ^^^^^^^^^^^^^^^^^^ * API breaking: The result of create_client is now a required async context class * Credential refresh should now work * generate_presigned_url is now an async call along with other credential methods * Credentials.[access_key/secret_key/token] now raise NotImplementedError because they won't call refresh like botocore. Instead should use get_frozen_credentials async method * Bump botocore and extras 0.12.0 (2020-02-23) ^^^^^^^^^^^^^^^^^^^ * Bump botocore and extras * Drop support for 3.5 given we are unable to test it with moto and it will soon be unsupported * Remove loop parameters for Python 3.8 compliance * Remove deprecated AioPageIterator.next_page 0.11.1 (2020-01-03) ^^^^^^^^^^^^^^^^^^^ * Fixed event streaming API calls like S3 Select. 0.11.0 (2019-11-12) ^^^^^^^^^^^^^^^^^^^ * replace CaseInsensitiveDict with urllib3 equivalent #744 (thanks to inspiration from @craigmccarter and @kevchentw) * bump botocore to 1.13.14 * fix for mismatched botocore method replacements 0.10.4 (2019-10-24) ^^^^^^^^^^^^^^^^^^^ * Make AioBaseClient.close method async #724 (thanks @bsitruk) * Bump awscli, boto3, botocore #735 (thanks @bbrendon) * switch paginator to async_generator, add result_key_iters (deprecate next_page method) 0.10.3 (2019-07-17) ^^^^^^^^^^^^^^^^^^^ * Bump botocore and extras 0.10.2 (2019-02-11) ^^^^^^^^^^^^^^^^^^^ * Fix response-received emitted event #682 0.10.1 (2019-02-08) ^^^^^^^^^^^^^^^^^^^ * Make tests pass with pytest 4.1 #669 (thanks @yan12125) * Support Python 3.7 #671 (thanks to @yan12125) * Update RTD build config #672 (thanks @willingc) * Bump to botocore 1.12.91 #679 0.10.0 (2018-12-09) ^^^^^^^^^^^^^^^^^^^ * Update to botocore 1.12.49 #639 (thanks @terrycain) 0.9.4 (2018-08-08) ^^^^^^^^^^^^^^^^^^ * Add ClientPayloadError as retryable exception 0.9.3 (2018-07-16) ^^^^^^^^^^^^^^^^^^ * Bring botocore up to date 0.9.2 (2018-05-05) ^^^^^^^^^^^^^^^^^^ * bump aiohttp requirement to fix read timeouts 0.9.1 (2018-05-04) ^^^^^^^^^^^^^^^^^^ * fix timeout bug introduced in last release 0.9.0 (2018-06-01) ^^^^^^^^^^^^^^^^^^ * bump aiohttp to 3.3.x * remove unneeded set_socket_timeout 0.8.0 (2018-05-07) ^^^^^^^^^^^^^^^^^^ * Fix pagination #573 (thanks @adamrothman) * Enabled several s3 tests via moto * Bring botocore up to date 0.7.0 (2018-05-01) ^^^^^^^^^^^^^^^^^^ * Just version bump 0.6.1a0 (2018-05-01) ^^^^^^^^^^^^^^^^^^^^ * bump to aiohttp 3.1.x * switch tests to Python 3.5+ * switch to native coroutines * fix non-streaming body timeout retries 0.6.0 (2018-03-04) ^^^^^^^^^^^^^^^^^^ * Upgrade to aiohttp>=3.0.0 #536 (thanks @Gr1N) 0.5.3 (2018-02-23) ^^^^^^^^^^^^^^^^^^ * Fixed waiters #523 (thanks @dalazx) * fix conn_timeout #485 0.5.2 (2017-12-06) ^^^^^^^^^^^^^^^^^^ * Updated awscli dependency #461 0.5.1 (2017-11-10) ^^^^^^^^^^^^^^^^^^ * Disabled compressed response #430 0.5.0 (2017-11-10) ^^^^^^^^^^^^^^^^^^ * Fix error botocore error checking #190 * Update supported botocore requirement to: >=1.7.28, <=1.7.40 * Bump aiohttp requirement to support compressed responses correctly #298 0.4.5 (2017-09-05) ^^^^^^^^^^^^^^^^^^ * Added SQS examples and tests #336 * Changed requirements.txt structure #336 * bump to botocore 1.7.4 * Added DynamoDB examples and tests #340 0.4.4 (2017-08-16) ^^^^^^^^^^^^^^^^^^ * add the supported versions of boto3 to extras require #324 0.4.3 (2017-07-05) ^^^^^^^^^^^^^^^^^^ * add the supported versions of awscli to extras require #273 (thanks @graingert) 0.4.2 (2017-07-03) ^^^^^^^^^^^^^^^^^^ * update supported aiohttp requirement to: >=2.0.4, <=2.3.0 * update supported botocore requirement to: >=1.5.71, <=1.5.78 0.4.1 (2017-06-27) ^^^^^^^^^^^^^^^^^^ * fix redirects #268 0.4.0 (2017-06-19) ^^^^^^^^^^^^^^^^^^ * update botocore requirement to: botocore>=1.5.34, <=1.5.70 * fix read_timeout due to #245 * implement set_socket_timeout 0.3.3 (2017-05-22) ^^^^^^^^^^^^^^^^^^ * switch to PEP 440 version parser to support 'dev' versions 0.3.2 (2017-05-22) ^^^^^^^^^^^^^^^^^^ * Fix botocore integration * Provisional fix for aiohttp 2.x stream support * update botocore requirement to: botocore>=1.5.34, <=1.5.52 0.3.1 (2017-04-18) ^^^^^^^^^^^^^^^^^^ * Fixed Waiter support 0.3.0 (2017-04-01) ^^^^^^^^^^^^^^^^^^ * Added support for aiohttp>=2.0.4 (thanks @achimnol) * update botocore requirement to: botocore>=1.5.0, <=1.5.33 0.2.3 (2017-03-22) ^^^^^^^^^^^^^^^^^^ * update botocore requirement to: botocore>=1.5.0, <1.5.29 0.2.2 (2017-03-07) ^^^^^^^^^^^^^^^^^^ * set aiobotocore.__all__ for * imports #121 (thanks @graingert) * fix ETag in head_object response #132 0.2.1 (2017-02-01) ^^^^^^^^^^^^^^^^^^ * Normalize headers and handle redirection by botocore #115 (thanks @Fedorof) 0.2.0 (2017-01-30) ^^^^^^^^^^^^^^^^^^ * add support for proxies (thanks @jjonek) * remove AioConfig verify_ssl connector_arg as this is handled by the create_client verify param * remove AioConfig limit connector_arg as this is now handled by by the Config `max_pool_connections` property (note default is 10) 0.1.1 (2017-01-16) ^^^^^^^^^^^^^^^^^^ * botocore updated to version 1.5.0 0.1.0 (2017-01-12) ^^^^^^^^^^^^^^^^^^ * Pass timeout to aiohttp.request to enforce read_timeout #86 (thanks @vharitonsky) (bumped up to next semantic version due to read_timeout enabling change) 0.0.6 (2016-11-19) ^^^^^^^^^^^^^^^^^^ * Added enforcement of plain response #57 (thanks @rymir) * botocore updated to version 1.4.73 #74 (thanks @vas3k) 0.0.5 (2016-06-01) ^^^^^^^^^^^^^^^^^^ * Initial alpha release aio-libs-aiobotocore-e39c8b3/CONTRIBUTING.rst000066400000000000000000000142211510465277300206110ustar00rootroot00000000000000Contributing ============ Running Tests ------------- .. _GitHub: https://github.com/aio-libs/aiobotocore Thanks for your interest in contributing to ``aiobotocore``, there are multiple ways and places you can contribute. First of all, clone the repository:: $ git clone git@github.com:aio-libs/aiobotocore.git Make sure the Python package and project manager `uv `_ is installed. Create a virtual environment:: $ cd aiobotocore $ uv venv Install pre-commit hooks:: $ uv run pre-commit install Congratulations, you are ready to run the test suite. There are two set of tests, those that can be mocked through `moto `_ running in docker, and those that require running against a personal amazon key. The CI only runs the moto tests. To run the moto tests:: $ uv run make mototest To run the non-moto tests, make sure you have your amazon key and secret accessible via environment variables:: $ export AWS_ACCESS_KEY_ID=xxx $ export AWS_SECRET_ACCESS_KEY=xxx $ export AWS_DEFAULT_REGION=xxx # e.g. us-west-2 Execute full tests suite:: $ uv run make test Execute full tests suite with coverage:: $ uv run make cov To run individual use following command:: $ uv run pytest -sv tests/test_monitor.py -k test_name Reporting an Issue ------------------ If you have found issue with ``aiobotocore`` please do not hesitate to file an issue on the GitHub_ project. When filing your issue please make sure you can express the issue with a reproducible test case. When reporting an issue we also need as much information about your environment that you can include. We never know what information will be pertinent when trying narrow down the issue. Please include at least the following information: * Version of ``aiobotocore`` and ``python``. * Version of ``botocore``. * Platform you're running on (OS X, Linux). Background and Implementation ----------------------------- aiobotocore adds async functionality to botocore by replacing certain critical methods in botocore classes with async versions. The best way to see how this works is by working backwards from ``AioEndpoint._request``. Because of this tight integration aiobotocore is typically version locked to a particular release of botocore. How to Upgrade Botocore ----------------------- aiobotocore's file names, and ordering of functions in files try to match the botocore files they override. For the most part botocore classes are sub-classed with the majority of the botocore calls eventually called. The best way I've seen to upgrade botocore support is by performing the following: 1. Download sources of the release of botocore you're trying to upgrade to, and the version of botocore that aiobotocore is currently locked to (see :file:`pyproject.toml`) and do a folder based file comparison of the botocore folders (tools like DiffMerge are nice). 2. Manually apply the relevant changes to their aiobotocore equivalent(s). Note that sometimes new functions are added which will need to be overridden (like ``__enter__`` -> ``__aenter__``) 3. Update the "project.optional-dependencies" in :file:`pyproject.toml` to the versions which match the botocore version you are targeting. 4. Now do a directory diff between aiobotocore and your target version botocore directory to ensure the changes were propagated. See next section describing types of changes we must validate and support. Hashes of Botocore Code (important) ----------------------------------- Because of the way aiobotocore is implemented (see Background section), it is very tightly coupled with botocore. The validity of these couplings are enforced in :file:`test_patches.py`. We also depend on some private properties in aiohttp, and because of this have entries in :file:`test_patches.py` for this too. These patches are important to catch cases where botocore functionality was added/removed and needs to be reflected in our overridden methods. Changes include: * parameters to methods added/removed * classes/methods being moved to new files * bodies of overridden methods updated To ensure we catch and reflect this changes in aiobotocore, the :file:`test_patches.py` file has the hashes of the parts of botocore we need to manually validate changes in. :file:`test_patches.py` file needs to be updated in two scenarios: 1. You're bumping the supported botocore/aiohttp version. In this case a failure in :file:`test_patches.py` means you need to validate the section of code in aiohttp/botocore that no longer matches the hash in test_patches.py to see if any changes need to be reflected in aiobotocore which overloads, on depends on the code which triggered the hash mismatch. This could there are new parameters we weren't expecting, parameters that are no longer passed to said overridden function(s), or an overridden function which calls a modified botocore method. If this is a whole class collision the checks will be more extensive. 2. You're implementing missing aiobotocore functionality, in which case you need to add entries for all the methods in botocore/aiohttp which you are overriding or depending on private functionality. For special cases, like when private attributes are used, you may have to hash the whole class so you can catch any case where the private property is used/updated to ensure it matches our expectations. After you've validated the changes, you can update the hash in :file:`test_patches.py`. One would think we could just write enough unittests to catch all cases, however, this is impossible for two reasons: 1. We do not support all botocore unittests, for future work see discussion: https://github.com/aio-libs/aiobotocore/issues/213 2. Even if we did all the unittests from 1, we would not support NEW functionality added, unless we automatically pulled all new unittests as well from botocore. Until we can perform ALL unittests from new releases of botocore, we are stuck with the patches. The Future ---------- The long term goal is that botocore will implement async functionality directly. See botocore issue: https://github.com/boto/botocore/issues/458 for details, tracked in aiobotocore here: https://github.com/aio-libs/aiobotocore/issues/36 aio-libs-aiobotocore-e39c8b3/LICENSE000066400000000000000000000263741510465277300171710ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2015-2016 Nikolai Novik Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. aio-libs-aiobotocore-e39c8b3/Makefile000066400000000000000000000026441510465277300176160ustar00rootroot00000000000000# Some simple testing tasks (sorry, UNIX only). # ?= is conditional assign, so users can pass options on the CLI instead of manually editing this file HTTP_BACKEND?='all' FLAGS?= pre-commit: pre-commit run --all --show-diff-on-failure test: pre-commit python -Wd -m pytest -s -vv $(FLAGS) ./tests/ vtest: python -Wd -X tracemalloc=5 -X faulthandler -m pytest -s -vv $(FLAGS) ./tests/ cov cover coverage: pre-commit python -Wd -m pytest -s -vv --cov-report term --cov-report html --cov aiobotocore ./tests @echo "open file://`pwd`/htmlcov/index.html" mototest: python -Wd -X tracemalloc=5 -X faulthandler -m pytest -vv -m "not localonly" -n auto --cov-report term --cov-report html --cov-report xml --cov=aiobotocore --cov=tests --log-cli-level=DEBUG --http-backend=$(HTTP_BACKEND) $(FLAGS) aiobotocore tests clean: rm -rf `find . -name __pycache__` rm -rf `find . -name .pytest_cache` rm -rf `find . -name *.egg-info` rm -f `find . -type f -name '*.py[co]' ` rm -f `find . -type f -name '*~' ` rm -f `find . -type f -name '.*~' ` rm -f `find . -type f -name '@*' ` rm -f `find . -type f -name '#*#' ` rm -f `find . -type f -name '*.orig' ` rm -f `find . -type f -name '*.rej' ` rm -f .coverage* rm -rf coverage rm -rf coverage.xml rm -rf htmlcov rm -rf build rm -rf cover rm -rf dist doc: make -C docs html @echo "open file://`pwd`/docs/_build/html/index.html" .PHONY: all pre-commit test vtest cov clean doc aio-libs-aiobotocore-e39c8b3/README.rst000066400000000000000000000206441510465277300176450ustar00rootroot00000000000000aiobotocore =========== .. |ci badge| image:: https://github.com/aio-libs/aiobotocore/actions/workflows/ci-cd.yml/badge.svg?branch=master :target: https://github.com/aio-libs/aiobotocore/actions/workflows/ci-cd.yml :alt: CI status of master branch .. |pre-commit badge| image:: https://results.pre-commit.ci/badge/github/aio-libs/aiobotocore/master.svg :target: https://results.pre-commit.ci/latest/github/aio-libs/aiobotocore/master :alt: pre-commit.ci status .. |coverage badge| image:: https://codecov.io/gh/aio-libs/aiobotocore/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiobotocore :alt: Coverage status on master branch .. |docs badge| image:: https://readthedocs.org/projects/aiobotocore/badge/?version=latest :target: https://aiobotocore.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. |pypi badge| image:: https://img.shields.io/pypi/v/aiobotocore.svg :target: https://pypi.python.org/pypi/aiobotocore :alt: Latest version on pypi .. |gitter badge| image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/aiobotocore :alt: Chat on Gitter .. |pypi downloads badge| image:: https://img.shields.io/pypi/dm/aiobotocore.svg?label=PyPI%20downloads :target: https://pypi.org/project/aiobotocore/ :alt: Downloads Last Month .. |conda badge| image:: https://img.shields.io/conda/dn/conda-forge/aiobotocore.svg?label=Conda%20downloads :target: https://anaconda.org/conda-forge/aiobotocore :alt: Conda downloads .. |stackoverflow badge| image:: https://img.shields.io/badge/stackoverflow-Ask%20questions-blue.svg :target: https://stackoverflow.com/questions/tagged/aiobotocore :alt: Stack Overflow |ci badge| |pre-commit badge| |coverage badge| |docs badge| |pypi badge| |gitter badge| |pypi downloads badge| |conda badge| |stackoverflow badge| Async client for amazon services using botocore_ and aiohttp_/asyncio_. This library is a mostly full featured asynchronous version of botocore. Install ------- :: $ pip install aiobotocore Basic Example ------------- .. code:: python import asyncio from aiobotocore.session import get_session AWS_ACCESS_KEY_ID = "xxx" AWS_SECRET_ACCESS_KEY = "xxx" async def go(): bucket = 'dataintake' filename = 'dummy.bin' folder = 'aiobotocore' key = '{}/{}'.format(folder, filename) session = get_session() async with session.create_client('s3', region_name='us-west-2', aws_secret_access_key=AWS_SECRET_ACCESS_KEY, aws_access_key_id=AWS_ACCESS_KEY_ID) as client: # upload object to amazon s3 data = b'\x01'*1024 resp = await client.put_object(Bucket=bucket, Key=key, Body=data) print(resp) # getting s3 object properties of file we just uploaded resp = await client.get_object_acl(Bucket=bucket, Key=key) print(resp) # get object from s3 response = await client.get_object(Bucket=bucket, Key=key) # this will ensure the connection is correctly re-used/closed async with response['Body'] as stream: assert await stream.read() == data # list s3 objects using paginator paginator = client.get_paginator('list_objects_v2') async for result in paginator.paginate(Bucket=bucket, Prefix=folder): for c in result.get('Contents', []): print(c) # delete object from s3 resp = await client.delete_object(Bucket=bucket, Key=key) print(resp) loop = asyncio.get_event_loop() loop.run_until_complete(go()) Context Manager Examples ------------------------ .. code:: python from contextlib import AsyncExitStack from aiobotocore.session import AioSession # How to use in existing context manager class Manager: def __init__(self): self._exit_stack = AsyncExitStack() self._s3_client = None async def __aenter__(self): session = AioSession() self._s3_client = await self._exit_stack.enter_async_context(session.create_client('s3')) async def __aexit__(self, exc_type, exc_val, exc_tb): await self._exit_stack.__aexit__(exc_type, exc_val, exc_tb) # How to use with an external exit_stack async def create_s3_client(session: AioSession, exit_stack: AsyncExitStack): # Create client and add cleanup client = await exit_stack.enter_async_context(session.create_client('s3')) return client async def non_manager_example(): session = AioSession() async with AsyncExitStack() as exit_stack: s3_client = await create_s3_client(session, exit_stack) # do work with s3_client Supported AWS Services ---------------------- This is a non-exuastive list of what tests aiobotocore runs against AWS services. Not all methods are tested but we aim to test the majority of commonly used methods. +----------------+-----------------------+ | Service | Status | +================+=======================+ | S3 | Working | +----------------+-----------------------+ | DynamoDB | Basic methods tested | +----------------+-----------------------+ | SNS | Basic methods tested | +----------------+-----------------------+ | SQS | Basic methods tested | +----------------+-----------------------+ | CloudFormation | Stack creation tested | +----------------+-----------------------+ | Kinesis | Basic methods tested | +----------------+-----------------------+ Due to the way boto3 is implemented, its highly likely that even if services are not listed above that you can take any ``boto3.client('service')`` and stick ``await`` in front of methods to make them async, e.g. ``await client.list_named_queries()`` would asynchronous list all of the named Athena queries. If a service is not listed here and you could do with some tests or examples feel free to raise an issue. Enable type checking and code completion ---------------------------------------- Install types-aiobotocore_ that contains type annotations for ``aiobotocore`` and all supported botocore_ services. .. code:: bash # install aiobotocore type annotations # for ec2, s3, rds, lambda, sqs, dynamo and cloudformation python -m pip install 'types-aiobotocore[essential]' # or install annotations for services you use python -m pip install 'types-aiobotocore[acm,apigateway]' # Lite version does not provide session.create_client overloads # it is more RAM-friendly, but requires explicit type annotations python -m pip install 'types-aiobotocore-lite[essential]' Now you should be able to run Pylance_, pyright_, or mypy_ for type checking as well as code completion in your IDE. For ``types-aiobotocore-lite`` package use explicit type annotations: .. code:: python from aiobotocore.session import get_session from types_aiobotocore_s3.client import S3Client session = get_session() async with session.create_client("s3") as client: client: S3Client # type checking and code completion is now enabled for client Full documentation for ``types-aiobotocore`` can be found here: https://youtype.github.io/types_aiobotocore_docs/ Requirements ------------ * Python_ 3.9+ * aiohttp_ * botocore_ .. _Python: https://www.python.org .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _botocore: https://github.com/boto/botocore .. _aiohttp: https://github.com/aio-libs/aiohttp .. _types-aiobotocore: https://youtype.github.io/types_aiobotocore_docs/ .. _Pylance: https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance .. _pyright: https://github.com/microsoft/pyright .. _mypy: http://mypy-lang.org/ awscli & boto3 -------------- awscli and boto3 depend on a single version, or a narrow range of versions, of botocore. However, aiobotocore only supports a specific range of botocore versions. To ensure you install the latest version of awscli and boto3 that your specific combination or aiobotocore and botocore can support use:: pip install -U 'aiobotocore[awscli,boto3]' If you only need awscli and not boto3 (or vice versa) you can just install one extra or the other. aio-libs-aiobotocore-e39c8b3/aiobotocore/000077500000000000000000000000001510465277300204555ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/aiobotocore/__init__.py000066400000000000000000000000271510465277300225650ustar00rootroot00000000000000__version__ = '2.25.2' aio-libs-aiobotocore-e39c8b3/aiobotocore/_constants.py000066400000000000000000000003771510465277300232110ustar00rootroot00000000000000# AWS has a 20 second idle timeout: # https://web.archive.org/web/20150926192339/https://forums.aws.amazon.com/message.jspa?messageID=215367 # and aiohttp default timeout is 30s so we set it to something # reasonable here DEFAULT_KEEPALIVE_TIMEOUT = 12 aio-libs-aiobotocore-e39c8b3/aiobotocore/_endpoint_helpers.py000066400000000000000000000030501510465277300245260ustar00rootroot00000000000000import asyncio import aiohttp.http_exceptions import botocore.retryhandler import wrapt try: import httpx except ImportError: httpx = None # Monkey patching: We need to insert the aiohttp exception equivalents # The only other way to do this would be to have another config file :( _aiohttp_retryable_exceptions = [ aiohttp.ClientConnectionError, aiohttp.ClientPayloadError, aiohttp.ServerDisconnectedError, aiohttp.http_exceptions.HttpProcessingError, asyncio.TimeoutError, ] botocore.retryhandler.EXCEPTION_MAP['GENERAL_CONNECTION_ERROR'].extend( _aiohttp_retryable_exceptions ) if httpx is not None: # See https://www.python-httpx.org/exceptions/#the-exception-hierarchy # All child exceptions of TransportError, except ProxyError, # UnsupportedProtocol and CloseError. _httpx_retryable_exceptions = [ httpx.TimeoutException, httpx.ProtocolError, httpx.ConnectError, httpx.ReadError, httpx.WriteError, ] botocore.retryhandler.EXCEPTION_MAP['GENERAL_CONNECTION_ERROR'].extend( _httpx_retryable_exceptions ) def _text(s, encoding='utf-8', errors='strict'): if isinstance(s, bytes): return s.decode(encoding, errors) return s # pragma: no cover # Unfortunately aiohttp changed the behavior of streams: # github.com/aio-libs/aiohttp/issues/1907 # We need this wrapper until we have a final resolution class _IOBaseWrapper(wrapt.ObjectProxy): def close(self): # this stream should not be closed by aiohttp, like 1.x pass aio-libs-aiobotocore-e39c8b3/aiobotocore/_helpers.py000066400000000000000000000004061510465277300226300ustar00rootroot00000000000000import inspect async def resolve_awaitable(obj): if inspect.isawaitable(obj): return await obj return obj async def async_any(items): for item in items: if await resolve_awaitable(item): return True return False aio-libs-aiobotocore-e39c8b3/aiobotocore/args.py000066400000000000000000000177131510465277300217740ustar00rootroot00000000000000import copy import botocore.parsers import botocore.serialize from botocore.args import ClientArgsCreator, EPRBuiltins from .config import AioConfig from .endpoint import DEFAULT_HTTP_SESSION_CLS, AioEndpointCreator from .parsers import create_parser from .regions import AioEndpointRulesetResolver from .signers import AioRequestSigner class AioClientArgsCreator(ClientArgsCreator): # NOTE: we override this so we can pull out the custom AioConfig params and # use an AioEndpointCreator async def get_client_args( self, service_model, region_name, is_secure, endpoint_url, verify, credentials, scoped_config, client_config, endpoint_bridge, auth_token=None, endpoints_ruleset_data=None, partition_data=None, ): final_args = self.compute_client_args( service_model, client_config, endpoint_bridge, region_name, endpoint_url, is_secure, scoped_config, ) service_name = final_args['service_name'] # noqa parameter_validation = final_args['parameter_validation'] endpoint_config = final_args['endpoint_config'] protocol = final_args['protocol'] config_kwargs = final_args['config_kwargs'] s3_config = final_args['s3_config'] partition = endpoint_config['metadata'].get('partition', None) socket_options = final_args['socket_options'] configured_endpoint_url = final_args['configured_endpoint_url'] signing_region = endpoint_config['signing_region'] endpoint_region_name = endpoint_config['region_name'] account_id_endpoint_mode = config_kwargs['account_id_endpoint_mode'] event_emitter = copy.copy(self._event_emitter) signer = AioRequestSigner( service_model.service_id, signing_region, endpoint_config['signing_name'], endpoint_config['signature_version'], credentials, event_emitter, auth_token, ) config_kwargs['s3'] = s3_config # aiobotocore addition if isinstance(client_config, AioConfig): connector_args = client_config.connector_args http_session_cls = client_config.http_session_cls else: connector_args = None http_session_cls = DEFAULT_HTTP_SESSION_CLS new_config = AioConfig(connector_args, **config_kwargs) endpoint_creator = AioEndpointCreator(event_emitter) endpoint = endpoint_creator.create_endpoint( service_model, region_name=endpoint_region_name, endpoint_url=endpoint_config['endpoint_url'], verify=verify, response_parser_factory=self._response_parser_factory, timeout=(new_config.connect_timeout, new_config.read_timeout), max_pool_connections=new_config.max_pool_connections, http_session_cls=http_session_cls, proxies=new_config.proxies, socket_options=socket_options, client_cert=new_config.client_cert, proxies_config=new_config.proxies_config, connector_args=new_config.connector_args, ) # Emit event to allow service-specific or customer customization of serializer kwargs event_name = f'creating-serializer.{service_name}' serializer_kwargs = { 'timestamp_precision': botocore.serialize.TIMESTAMP_PRECISION_DEFAULT } await event_emitter.emit( event_name, protocol_name=protocol, service_model=service_model, serializer_kwargs=serializer_kwargs, ) serializer = botocore.serialize.create_serializer( protocol, parameter_validation, timestamp_precision=serializer_kwargs['timestamp_precision'], ) response_parser = create_parser(protocol) ruleset_resolver = self._build_endpoint_resolver( endpoints_ruleset_data, partition_data, client_config, service_model, endpoint_region_name, region_name, configured_endpoint_url, endpoint, is_secure, endpoint_bridge, event_emitter, credentials, account_id_endpoint_mode, ) # Copy the session's user agent factory and adds client configuration. client_ua_creator = self._session_ua_creator.with_client_config( new_config ) supplied_ua = client_config.user_agent if client_config else None new_config._supplied_user_agent = supplied_ua return { 'serializer': serializer, 'endpoint': endpoint, 'response_parser': response_parser, 'event_emitter': event_emitter, 'request_signer': signer, 'service_model': service_model, 'loader': self._loader, 'client_config': new_config, 'partition': partition, 'exceptions_factory': self._exceptions_factory, 'endpoint_ruleset_resolver': ruleset_resolver, 'user_agent_creator': client_ua_creator, } def _build_endpoint_resolver( self, endpoints_ruleset_data, partition_data, client_config, service_model, endpoint_region_name, region_name, endpoint_url, endpoint, is_secure, endpoint_bridge, event_emitter, credentials, account_id_endpoint_mode, ): if endpoints_ruleset_data is None: return None # The legacy EndpointResolver is global to the session, but # EndpointRulesetResolver is service-specific. Builtins for # EndpointRulesetResolver must not be derived from the legacy # endpoint resolver's output, including final_args, s3_config, # etc. s3_config_raw = self.compute_s3_config(client_config) or {} service_name_raw = service_model.endpoint_prefix # Maintain complex logic for s3 and sts endpoints for backwards # compatibility. if service_name_raw in ['s3', 'sts'] or region_name is None: eprv2_region_name = endpoint_region_name else: eprv2_region_name = region_name resolver_builtins = self.compute_endpoint_resolver_builtin_defaults( region_name=eprv2_region_name, service_name=service_name_raw, s3_config=s3_config_raw, endpoint_bridge=endpoint_bridge, client_endpoint_url=endpoint_url, legacy_endpoint_url=endpoint.host, credentials=credentials, account_id_endpoint_mode=account_id_endpoint_mode, ) # replace with async version resolver_builtins[EPRBuiltins.ACCOUNT_ID] = ( credentials.get_account_id if credentials else None ) # Client context params for s3 conflict with the available settings # in the `s3` parameter on the `Config` object. If the same parameter # is set in both places, the value in the `s3` parameter takes priority. if client_config is not None: client_context = client_config.client_context_params or {} else: client_context = {} if self._is_s3_service(service_name_raw): client_context.update(s3_config_raw) sig_version = ( client_config.signature_version if client_config is not None else None ) return AioEndpointRulesetResolver( endpoint_ruleset_data=endpoints_ruleset_data, partition_data=partition_data, service_model=service_model, builtins=resolver_builtins, client_context=client_context, event_emitter=event_emitter, use_ssl=is_secure, requested_auth_scheme=sig_version, ) aio-libs-aiobotocore-e39c8b3/aiobotocore/awsrequest.py000066400000000000000000000021361510465277300232340ustar00rootroot00000000000000import botocore.utils from botocore.awsrequest import AWSResponse class AioAWSResponse(AWSResponse): # Unlike AWSResponse, these return awaitables async def _content_prop(self): """Content of the response as bytes.""" if self._content is None: # NOTE: this will cache the data in self.raw self._content = await self.raw.read() or b'' return self._content @property def content(self): return self._content_prop() async def _text_prop(self): encoding = botocore.utils.get_encoding_from_headers(self.headers) if encoding: return (await self.content).decode(encoding) else: return (await self.content).decode('utf-8') @property def text(self): return self._text_prop() class HttpxAWSResponse(AioAWSResponse): async def _content_prop(self): """Content of the response as bytes.""" if self._content is None: # NOTE: this will cache the data in self.raw self._content = await self.raw.aread() or b'' return self._content aio-libs-aiobotocore-e39c8b3/aiobotocore/client.py000066400000000000000000000600511510465277300223070ustar00rootroot00000000000000from botocore.auth import resolve_auth_type from botocore.awsrequest import prepare_request_dict from botocore.client import ( BaseClient, ClientCreator, ClientEndpointBridge, PaginatorDocstring, logger, resolve_checksum_context, ) from botocore.compress import maybe_compress_request from botocore.discovery import block_endpoint_discovery_required_operations from botocore.exceptions import OperationNotPageableError, UnknownServiceError from botocore.history import get_global_history_recorder from botocore.hooks import first_non_none_response from botocore.useragent import register_feature_id from botocore.utils import get_service_module_name from botocore.waiter import xform_name from . import waiter from .args import AioClientArgsCreator from .context import with_current_context from .credentials import AioRefreshableCredentials from .discovery import AioEndpointDiscoveryHandler, AioEndpointDiscoveryManager from .httpchecksum import apply_request_checksum from .paginate import AioPaginator from .retries import adaptive, standard from .utils import AioS3ExpressIdentityResolver, AioS3RegionRedirectorv2 history_recorder = get_global_history_recorder() class AioClientCreator(ClientCreator): async def create_client( self, service_name, region_name, is_secure=True, endpoint_url=None, verify=None, credentials=None, scoped_config=None, api_version=None, client_config=None, auth_token=None, ): responses = await self._event_emitter.emit( 'choose-service-name', service_name=service_name ) service_name = first_non_none_response(responses, default=service_name) service_model = self._load_service_model(service_name, api_version) try: endpoints_ruleset_data = self._load_service_endpoints_ruleset( service_name, api_version ) partition_data = self._loader.load_data('partitions') except UnknownServiceError: endpoints_ruleset_data = None partition_data = None logger.info( 'No endpoints ruleset found for service %s, falling back to ' 'legacy endpoint routing.', service_name, ) cls = await self._create_client_class(service_name, service_model) region_name, client_config = self._normalize_fips_region( region_name, client_config ) if auth := service_model.metadata.get('auth'): service_signature_version = resolve_auth_type(auth) else: service_signature_version = service_model.metadata.get( 'signatureVersion' ) endpoint_bridge = ClientEndpointBridge( self._endpoint_resolver, scoped_config, client_config, service_signing_name=service_model.metadata.get('signingName'), config_store=self._config_store, service_signature_version=service_signature_version, ) if token := self._evaluate_client_specific_token( service_model.signing_name ): auth_token = token client_args = await self._get_client_args( service_model, region_name, is_secure, endpoint_url, verify, credentials, scoped_config, client_config, endpoint_bridge, auth_token, endpoints_ruleset_data, partition_data, ) service_client = cls(**client_args) self._register_retries(service_client) self._register_s3_events( client=service_client, endpoint_bridge=None, endpoint_url=None, client_config=client_config, scoped_config=scoped_config, ) self._register_s3express_events(client=service_client) self._register_s3_control_events(client=service_client) self._register_importexport_events(client=service_client) self._register_endpoint_discovery( service_client, endpoint_url, client_config ) return service_client async def _create_client_class(self, service_name, service_model): class_attributes = self._create_methods(service_model) py_name_to_operation_name = self._create_name_mapping(service_model) class_attributes['_PY_TO_OP_NAME'] = py_name_to_operation_name bases = [AioBaseClient] service_id = service_model.service_id.hyphenize() await self._event_emitter.emit( f'creating-client-class.{service_id}', class_attributes=class_attributes, base_classes=bases, ) class_name = get_service_module_name(service_model) cls = type(str(class_name), tuple(bases), class_attributes) return cls def _register_retries(self, client): # botocore retry handlers may block. We add our own implementation here. # botocore provides three implementations: # # 1) standard # This one doesn't block. A threading.Lock is used in quota.RetryQuota, # but it's only used to protect concurrent modifications of internal # state inside multithreaded programs. When running under a single # asyncio thread, this lock will be acquired and released in the same # coroutine, and the coroutine will never block waiting for the lock. # Thus, we don't need to redefine this strategy. # # 2) adaptive # This one blocks when the client is applying self rate limiting. # We override the corresponding definition to replace it with async # objects. # # 3) legacy # This one probably doesn't block. # # The code for this method comes directly from botocore. We could # override `_register_v2_adaptive_retries` only. The override for # `_register_retries` is only included for clarity. retry_mode = client.meta.config.retries['mode'] if retry_mode == 'standard': self._register_v2_standard_retries(client) elif retry_mode == 'adaptive': self._register_v2_standard_retries(client) self._register_v2_adaptive_retries(client) elif retry_mode == 'legacy': self._register_legacy_retries(client) else: return register_feature_id(f'RETRY_MODE_{retry_mode.upper()}') def _register_v2_standard_retries(self, client): max_attempts = client.meta.config.retries.get('total_max_attempts') kwargs = {'client': client} if max_attempts is not None: kwargs['max_attempts'] = max_attempts standard.register_retry_handler(**kwargs) def _register_v2_adaptive_retries(self, client): # See comment in `_register_retries`. # Note that this `adaptive` module is an aiobotocore reimplementation. adaptive.register_retry_handler(client) def _register_legacy_retries(self, client): endpoint_prefix = client.meta.service_model.endpoint_prefix service_id = client.meta.service_model.service_id service_event_name = service_id.hyphenize() # First, we load the entire retry config for all services, # then pull out just the information we need. original_config = self._loader.load_data('_retry') if not original_config: return retries = self._transform_legacy_retries(client.meta.config.retries) retry_config = self._retry_config_translator.build_retry_config( endpoint_prefix, original_config.get('retry', {}), original_config.get('definitions', {}), retries, ) logger.debug( "Registering retry handlers for service: %s", client.meta.service_model.service_name, ) handler = self._retry_handler_factory.create_retry_handler( retry_config, endpoint_prefix ) unique_id = f'retry-config-{service_event_name}' client.meta.events.register( f"needs-retry.{service_event_name}", handler, unique_id=unique_id ) def _register_endpoint_discovery(self, client, endpoint_url, config): if endpoint_url is not None: # Don't register any handlers in the case of a custom endpoint url return # Only attach handlers if the service supports discovery if client.meta.service_model.endpoint_discovery_operation is None: return events = client.meta.events service_id = client.meta.service_model.service_id.hyphenize() enabled = False if config and config.endpoint_discovery_enabled is not None: enabled = config.endpoint_discovery_enabled elif self._config_store: enabled = self._config_store.get_config_variable( 'endpoint_discovery_enabled' ) enabled = self._normalize_endpoint_discovery_config(enabled) if enabled and self._requires_endpoint_discovery(client, enabled): discover = enabled is True manager = AioEndpointDiscoveryManager( client, always_discover=discover ) handler = AioEndpointDiscoveryHandler(manager) handler.register(events, service_id) else: events.register( 'before-parameter-build', block_endpoint_discovery_required_operations, ) def _register_s3express_events( self, client, endpoint_bridge=None, endpoint_url=None, client_config=None, scoped_config=None, ): if client.meta.service_model.service_name != 's3': return AioS3ExpressIdentityResolver( client, AioRefreshableCredentials ).register() def _register_s3_events( self, client, endpoint_bridge, endpoint_url, client_config, scoped_config, ): if client.meta.service_model.service_name != 's3': return AioS3RegionRedirectorv2(None, client).register() self._set_s3_presign_signature_version( client.meta, client_config, scoped_config ) client.meta.events.register( 'before-parameter-build.s3', self._inject_s3_input_parameters ) async def _get_client_args( self, service_model, region_name, is_secure, endpoint_url, verify, credentials, scoped_config, client_config, endpoint_bridge, auth_token, endpoints_ruleset_data, partition_data, ): # This is a near copy of ClientCreator. What's replaced # is ClientArgsCreator->AioClientArgsCreator args_creator = AioClientArgsCreator( self._event_emitter, self._user_agent, self._response_parser_factory, self._loader, self._exceptions_factory, config_store=self._config_store, user_agent_creator=self._user_agent_creator, ) return await args_creator.get_client_args( service_model, region_name, is_secure, endpoint_url, verify, credentials, scoped_config, client_config, endpoint_bridge, auth_token, endpoints_ruleset_data, partition_data, ) class AioBaseClient(BaseClient): async def _async_getattr(self, item): event_name = ( f'getattr.{self._service_model.service_id.hyphenize()}.{item}' ) handler, event_response = await self.meta.events.emit_until_response( event_name, client=self ) return event_response def __getattr__(self, item): # NOTE: we can not reliably support this because if we were to make this a # deferred attrgetter (See #803), it would resolve in hasattr always returning # true. This ends up breaking ddtrace for example when it tries to set a pin. raise AttributeError( f"'{self.__class__.__name__}' object has no attribute '{item}'" ) async def close(self): """Closes underlying endpoint connections.""" await self._endpoint.close() @with_current_context() async def _make_api_call(self, operation_name, api_params): operation_model = self._service_model.operation_model(operation_name) service_name = self._service_model.service_name history_recorder.record( 'API_CALL', { 'service': service_name, 'operation': operation_name, 'params': api_params, }, ) if operation_model.deprecated: logger.debug( 'Warning: %s.%s() is deprecated', service_name, operation_name ) request_context = { 'client_region': self.meta.region_name, 'client_config': self.meta.config, 'has_streaming_input': operation_model.has_streaming_input, 'auth_type': operation_model.resolved_auth_type, 'unsigned_payload': operation_model.unsigned_payload, 'auth_options': self._service_model.metadata.get('auth'), } api_params = await self._emit_api_params( api_params=api_params, operation_model=operation_model, context=request_context, ) ( endpoint_url, additional_headers, properties, ) = await self._resolve_endpoint_ruleset( operation_model, api_params, request_context ) if properties: # Pass arbitrary endpoint info with the Request # for use during construction. request_context['endpoint_properties'] = properties request_dict = await self._convert_to_request_dict( api_params=api_params, operation_model=operation_model, endpoint_url=endpoint_url, context=request_context, headers=additional_headers, ) resolve_checksum_context(request_dict, operation_model, api_params) service_id = self._service_model.service_id.hyphenize() handler, event_response = await self.meta.events.emit_until_response( f'before-call.{service_id}.{operation_name}', model=operation_model, params=request_dict, request_signer=self._request_signer, context=request_context, ) if event_response is not None: http, parsed_response = event_response else: maybe_compress_request( self.meta.config, request_dict, operation_model ) apply_request_checksum(request_dict) http, parsed_response = await self._make_request( operation_model, request_dict, request_context ) await self.meta.events.emit( f'after-call.{service_id}.{operation_name}', http_response=http, parsed=parsed_response, model=operation_model, context=request_context, ) if http.status_code >= 300: error_info = parsed_response.get("Error", {}) error_code = request_context.get( 'error_code_override' ) or error_info.get("Code") error_class = self.exceptions.from_code(error_code) raise error_class(parsed_response, operation_name) else: return parsed_response async def _make_request( self, operation_model, request_dict, request_context ): try: return await self._endpoint.make_request( operation_model, request_dict ) except Exception as e: await self.meta.events.emit( f'after-call-error.{self._service_model.service_id.hyphenize()}.{operation_model.name}', exception=e, context=request_context, ) raise async def _convert_to_request_dict( self, api_params, operation_model, endpoint_url, context=None, headers=None, set_user_agent_header=True, ): request_dict = self._serializer.serialize_to_request( api_params, operation_model ) if not self._client_config.inject_host_prefix: request_dict.pop('host_prefix', None) if headers is not None: request_dict['headers'].update(headers) if set_user_agent_header: user_agent = self._user_agent_creator.to_string() else: user_agent = None prepare_request_dict( request_dict, endpoint_url=endpoint_url, user_agent=user_agent, context=context, ) return request_dict async def _emit_api_params(self, api_params, operation_model, context): # Given the API params provided by the user and the operation_model # we can serialize the request to a request_dict. operation_name = operation_model.name # Emit an event that allows users to modify the parameters at the # beginning of the method. It allows handlers to modify existing # parameters or return a new set of parameters to use. service_id = self._service_model.service_id.hyphenize() responses = await self.meta.events.emit( f'provide-client-params.{service_id}.{operation_name}', params=api_params, model=operation_model, context=context, ) api_params = first_non_none_response(responses, default=api_params) await self.meta.events.emit( f'before-parameter-build.{service_id}.{operation_name}', params=api_params, model=operation_model, context=context, ) return api_params async def _resolve_endpoint_ruleset( self, operation_model, params, request_context, ignore_signing_region=False, ): """Returns endpoint URL and list of additional headers returned from EndpointRulesetResolver for the given operation and params. If the ruleset resolver is not available, for example because the service has no endpoints ruleset file, the legacy endpoint resolver's value is returned. Use ignore_signing_region for generating presigned URLs or any other situation where the signing region information from the ruleset resolver should be ignored. Returns tuple of URL and headers dictionary. Additionally, the request_context dict is modified in place with any signing information returned from the ruleset resolver. """ if self._ruleset_resolver is None: endpoint_url = self.meta.endpoint_url additional_headers = {} endpoint_properties = {} else: endpoint_info = await self._ruleset_resolver.construct_endpoint( operation_model=operation_model, call_args=params, request_context=request_context, ) endpoint_url = endpoint_info.url additional_headers = endpoint_info.headers endpoint_properties = endpoint_info.properties # If authSchemes is present, overwrite default auth type and # signing context derived from service model. auth_schemes = endpoint_info.properties.get('authSchemes') if auth_schemes is not None: auth_info = self._ruleset_resolver.auth_schemes_to_signing_ctx( auth_schemes ) auth_type, signing_context = auth_info request_context['auth_type'] = auth_type if 'region' in signing_context and ignore_signing_region: del signing_context['region'] if 'signing' in request_context: request_context['signing'].update(signing_context) else: request_context['signing'] = signing_context return endpoint_url, additional_headers, endpoint_properties def get_paginator(self, operation_name): """Create a paginator for an operation. :type operation_name: string :param operation_name: The operation name. This is the same name as the method name on the client. For example, if the method name is ``create_foo``, and you'd normally invoke the operation as ``client.create_foo(**kwargs)``, if the ``create_foo`` operation can be paginated, you can use the call ``client.get_paginator("create_foo")``. :raise OperationNotPageableError: Raised if the operation is not pageable. You can use the ``client.can_paginate`` method to check if an operation is pageable. :rtype: ``botocore.paginate.Paginator`` :return: A paginator object. """ if not self.can_paginate(operation_name): raise OperationNotPageableError(operation_name=operation_name) else: actual_operation_name = self._PY_TO_OP_NAME[operation_name] # Create a new paginate method that will serve as a proxy to # the underlying Paginator.paginate method. This is needed to # attach a docstring to the method. def paginate(self, **kwargs): return AioPaginator.paginate(self, **kwargs) paginator_config = self._cache['page_config'][ actual_operation_name ] # Add the docstring for the paginate method. paginate.__doc__ = PaginatorDocstring( paginator_name=actual_operation_name, event_emitter=self.meta.events, service_model=self.meta.service_model, paginator_config=paginator_config, include_signature=False, ) # Rename the paginator class based on the type of paginator. service_module_name = get_service_module_name( self.meta.service_model ) paginator_class_name = ( f"{service_module_name}.Paginator.{actual_operation_name}" ) # Create the new paginator class documented_paginator_cls = type( paginator_class_name, (AioPaginator,), {'paginate': paginate} ) operation_model = self._service_model.operation_model( actual_operation_name ) paginator = documented_paginator_cls( getattr(self, operation_name), paginator_config, operation_model, ) return paginator # NOTE: this method does not differ from botocore, however it's important to keep # as the "waiter" value points to our own asyncio waiter module def get_waiter(self, waiter_name): """Returns an object that can wait for some condition. :type waiter_name: str :param waiter_name: The name of the waiter to get. See the waiters section of the service docs for a list of available waiters. :returns: The specified waiter object. :rtype: ``botocore.waiter.Waiter`` """ config = self._get_waiter_config() if not config: raise ValueError(f"Waiter does not exist: {waiter_name}") model = waiter.WaiterModel(config) mapping = {} for name in model.waiter_names: mapping[xform_name(name)] = name if waiter_name not in mapping: raise ValueError(f"Waiter does not exist: {waiter_name}") return waiter.create_waiter_with_client( mapping[waiter_name], model, self ) async def __aenter__(self): await self._endpoint.http_session.__aenter__() return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self._endpoint.http_session.__aexit__(exc_type, exc_val, exc_tb) aio-libs-aiobotocore-e39c8b3/aiobotocore/config.py000066400000000000000000000072341510465277300223020ustar00rootroot00000000000000import copy import botocore.client from botocore.exceptions import ParamValidationError from ._constants import DEFAULT_KEEPALIVE_TIMEOUT from .endpoint import DEFAULT_HTTP_SESSION_CLS from .httpxsession import HttpxSession TIMEOUT_ARGS = frozenset( ('keepalive_timeout', 'write_timeout', 'pool_timeout') ) class AioConfig(botocore.client.Config): def __init__( self, connector_args=None, http_session_cls=DEFAULT_HTTP_SESSION_CLS, **kwargs, ): super().__init__(**kwargs) self._validate_connector_args(connector_args, http_session_cls) self.connector_args = copy.copy(connector_args) self.http_session_cls = http_session_cls if not self.connector_args: self.connector_args = dict() if 'keepalive_timeout' not in self.connector_args: self.connector_args['keepalive_timeout'] = ( DEFAULT_KEEPALIVE_TIMEOUT ) def merge(self, other_config): # Adapted from parent class config_options = copy.copy(self._user_provided_options) config_options.update(other_config._user_provided_options) return AioConfig(self.connector_args, **config_options) @staticmethod def _validate_connector_args(connector_args, http_session_cls): if connector_args is None: return for k, v in connector_args.items(): # verify_ssl is handled by verify parameter to create_client if k == 'use_dns_cache': if http_session_cls is HttpxSession: raise ParamValidationError( report='Httpx does not support dns caching. https://github.com/encode/httpx/discussions/2211' ) if not isinstance(v, bool): raise ParamValidationError( report=f'{k} value must be a boolean' ) elif k == 'ttl_dns_cache': if v is not None and not isinstance(v, int): raise ParamValidationError( report=f'{k} value must be an int or None' ) elif k in TIMEOUT_ARGS: if v is not None and not isinstance(v, (float, int)): raise ParamValidationError( report=f'{k} value must be a float/int or None' ) elif k == 'force_close': if http_session_cls is HttpxSession: raise ParamValidationError( report=f'Httpx backend does not currently support {k}.' ) if not isinstance(v, bool): raise ParamValidationError( report=f'{k} value must be a boolean' ) # limit is handled by max_pool_connections elif k == 'ssl_context': import ssl if not isinstance(v, ssl.SSLContext): raise ParamValidationError( report=f'{k} must be an SSLContext instance' ) elif k == "resolver": from aiohttp.abc import AbstractResolver if http_session_cls is HttpxSession: raise ParamValidationError( report=f'Httpx backend does not support {k}.' ) if not isinstance(v, AbstractResolver): raise ParamValidationError( report=f'{k} must be an instance of a AbstractResolver' ) else: raise ParamValidationError(report=f'invalid connector_arg:{k}') aio-libs-aiobotocore-e39c8b3/aiobotocore/configprovider.py000066400000000000000000000027451510465277300240570ustar00rootroot00000000000000from botocore.configprovider import SmartDefaultsConfigStoreFactory, os class AioSmartDefaultsConfigStoreFactory(SmartDefaultsConfigStoreFactory): async def merge_smart_defaults(self, config_store, mode, region_name): if mode == 'auto': mode = await self.resolve_auto_mode(region_name) default_configs = ( self._default_config_resolver.get_default_config_values(mode) ) for config_var in default_configs: config_value = default_configs[config_var] method = getattr(self, f'_set_{config_var}', None) if method: method(config_store, config_value) async def resolve_auto_mode(self, region_name): current_region = None if os.environ.get('AWS_EXECUTION_ENV'): default_region = os.environ.get('AWS_DEFAULT_REGION') current_region = os.environ.get('AWS_REGION', default_region) if not current_region: if self._instance_metadata_region: current_region = self._instance_metadata_region else: try: current_region = await self._imds_region_provider.provide() self._instance_metadata_region = current_region except Exception: pass if current_region: if region_name == current_region: return 'in-region' else: return 'cross-region' return 'standard' aio-libs-aiobotocore-e39c8b3/aiobotocore/context.py000066400000000000000000000015741510465277300225220ustar00rootroot00000000000000from contextlib import asynccontextmanager from copy import deepcopy from functools import wraps from botocore.context import ( ClientContext, get_context, reset_context, set_context, ) from ._helpers import resolve_awaitable @asynccontextmanager async def start_as_current_context(ctx=None): current = ctx or get_context() if current is None: new = ClientContext() else: new = deepcopy(current) token = set_context(new) try: yield finally: reset_context(token) def with_current_context(hook=None): def decorator(func): @wraps(func) async def wrapper(*args, **kwargs): async with start_as_current_context(): if hook: await resolve_awaitable(hook()) return await func(*args, **kwargs) return wrapper return decorator aio-libs-aiobotocore-e39c8b3/aiobotocore/credentials.py000066400000000000000000001237351510465277300233370ustar00rootroot00000000000000import asyncio import logging import os import subprocess from copy import deepcopy import botocore.compat import dateutil.parser from botocore import UNSIGNED from botocore.compat import compat_shell_split, total_seconds from botocore.config import Config from botocore.credentials import ( _DEFAULT_ADVISORY_REFRESH_TIMEOUT, AssumeRoleCredentialFetcher, AssumeRoleProvider, AssumeRoleWithWebIdentityProvider, BaseAssumeRoleCredentialFetcher, BotoProvider, CachedCredentialFetcher, CanonicalNameCredentialSourcer, ConfigNotFound, ConfigProvider, ContainerMetadataFetcher, ContainerProvider, CredentialResolver, CredentialRetrievalError, Credentials, DeferredRefreshableCredentials, EnvProvider, InstanceMetadataProvider, InvalidConfigError, MetadataRetrievalError, OriginalEC2Provider, PartialCredentialsError, ProcessProvider, ProfileProviderBuilder, ReadOnlyCredentials, RefreshableCredentials, RefreshWithMFAUnsupportedError, SharedCredentialProvider, SSOCredentialFetcher, SSOProvider, SSOTokenLoader, UnauthorizedSSOTokenError, UnknownCredentialError, _local_now, _parse_if_needed, _serialize_if_needed, parse, resolve_imds_endpoint_mode, ) from botocore.useragent import register_feature_id, register_feature_ids from aiobotocore._helpers import resolve_awaitable from aiobotocore.config import AioConfig from aiobotocore.tokens import AioSSOTokenProvider from aiobotocore.utils import ( AioContainerMetadataFetcher, AioInstanceMetadataFetcher, create_nested_client, ) logger = logging.getLogger(__name__) def create_credential_resolver(session, cache=None, region_name=None): """Create a default credential resolver. This creates a pre-configured credential resolver that includes the default lookup chain for credentials. """ profile_name = session.get_config_variable('profile') or 'default' metadata_timeout = session.get_config_variable('metadata_service_timeout') num_attempts = session.get_config_variable('metadata_service_num_attempts') disable_env_vars = session.instance_variables().get('profile') is not None imds_config = { 'ec2_metadata_service_endpoint': session.get_config_variable( 'ec2_metadata_service_endpoint' ), 'ec2_metadata_service_endpoint_mode': resolve_imds_endpoint_mode( session ), 'ec2_credential_refresh_window': _DEFAULT_ADVISORY_REFRESH_TIMEOUT, 'ec2_metadata_v1_disabled': session.get_config_variable( 'ec2_metadata_v1_disabled' ), } if cache is None: cache = {} env_provider = AioEnvProvider() container_provider = AioContainerProvider() instance_metadata_provider = AioInstanceMetadataProvider( iam_role_fetcher=AioInstanceMetadataFetcher( timeout=metadata_timeout, num_attempts=num_attempts, user_agent=session.user_agent(), config=imds_config, ) ) profile_provider_builder = AioProfileProviderBuilder( session, cache=cache, region_name=region_name ) assume_role_provider = AioAssumeRoleProvider( load_config=lambda: session.full_config, client_creator=_get_client_creator(session, region_name), cache=cache, profile_name=profile_name, credential_sourcer=AioCanonicalNameCredentialSourcer( [env_provider, container_provider, instance_metadata_provider] ), profile_provider_builder=profile_provider_builder, ) pre_profile = [ env_provider, assume_role_provider, ] profile_providers = profile_provider_builder.providers( profile_name=profile_name, disable_env_vars=disable_env_vars, ) post_profile = [ AioOriginalEC2Provider(), AioBotoProvider(), container_provider, instance_metadata_provider, ] providers = pre_profile + profile_providers + post_profile if disable_env_vars: # An explicitly provided profile will negate an EnvProvider. # We will defer to providers that understand the "profile" # concept to retrieve credentials. # The one edge case if is all three values are provided via # env vars: # export AWS_ACCESS_KEY_ID=foo # export AWS_SECRET_ACCESS_KEY=bar # export AWS_PROFILE=baz # Then, just like our client() calls, the explicit credentials # will take precedence. # # This precedence is enforced by leaving the EnvProvider in the chain. # This means that the only way a "profile" would win is if the # EnvProvider does not return credentials, which is what we want # in this scenario. providers.remove(env_provider) logger.debug( 'Skipping environment variable credential check' ' because profile name was explicitly set.' ) resolver = AioCredentialResolver(providers=providers) return resolver class AioProfileProviderBuilder(ProfileProviderBuilder): def _create_process_provider(self, profile_name): return AioProcessProvider( profile_name=profile_name, load_config=lambda: self._session.full_config, ) def _create_shared_credential_provider(self, profile_name): credential_file = self._session.get_config_variable('credentials_file') return AioSharedCredentialProvider( profile_name=profile_name, creds_filename=credential_file, ) def _create_config_provider(self, profile_name): config_file = self._session.get_config_variable('config_file') return AioConfigProvider( profile_name=profile_name, config_filename=config_file, ) def _create_web_identity_provider(self, profile_name, disable_env_vars): return AioAssumeRoleWithWebIdentityProvider( load_config=lambda: self._session.full_config, client_creator=_get_client_creator( self._session, self._region_name ), cache=self._cache, profile_name=profile_name, disable_env_vars=disable_env_vars, ) def _create_sso_provider(self, profile_name): return AioSSOProvider( load_config=lambda: self._session.full_config, client_creator=self._session.create_client, profile_name=profile_name, cache=self._cache, token_cache=self._sso_token_cache, token_provider=AioSSOTokenProvider( self._session, cache=self._sso_token_cache, profile_name=profile_name, ), ) async def get_credentials(session): resolver = create_credential_resolver(session) return await resolver.load_credentials() def create_assume_role_refresher(client, params): async def refresh(): async with client as sts: response = await sts.assume_role(**params) credentials = response['Credentials'] # We need to normalize the credential names to # the values expected by the refresh creds. return { 'access_key': credentials['AccessKeyId'], 'secret_key': credentials['SecretAccessKey'], 'token': credentials['SessionToken'], 'expiry_time': _serialize_if_needed(credentials['Expiration']), } return refresh def create_mfa_serial_refresher(actual_refresh): class _Refresher: def __init__(self, refresh): self._refresh = refresh self._has_been_called = False async def call(self): if self._has_been_called: # We can explore an option in the future to support # reprompting for MFA, but for now we just error out # when the temp creds expire. raise RefreshWithMFAUnsupportedError() self._has_been_called = True return await self._refresh() return _Refresher(actual_refresh).call # TODO: deprecate create_aio_mfa_serial_refresher = create_mfa_serial_refresher class AioCredentials(Credentials): # Overrides for property accessors def get_account_id(self): return self.account_id def get_access_key(self): return self.access_key def get_secret_key(self): return self.secret_key def get_token(self): return self.token async def get_frozen_credentials(self): return ReadOnlyCredentials( self.access_key, self.secret_key, self.token, self.account_id ) class AioRefreshableCredentials(RefreshableCredentials): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._refresh_lock = asyncio.Lock() async def get_account_id(self): await self._refresh() return self._account_id async def get_access_key(self): await self._refresh() return self._access_key async def get_secret_key(self): await self._refresh() return self._secret_key async def get_token(self): await self._refresh() return self._token # Redeclaring the properties so it doesn't call refresh # Have to redeclare setter as we're overriding the getter @property def access_key(self): # TODO: this needs to be resolved raise NotImplementedError( "missing call to self._refresh. " "Use get_frozen_credentials or get_access_key" ) return self._access_key @access_key.setter def access_key(self, value): self._access_key = value @property def secret_key(self): # TODO: this needs to be resolved raise NotImplementedError( "missing call to self._refresh. " "Use get_frozen_credentials or get_secret_key instead" ) return self._secret_key @secret_key.setter def secret_key(self, value): self._secret_key = value @property def token(self): # TODO: this needs to be resolved raise NotImplementedError( "missing call to self._refresh. " "Use get_frozen_credentials or get_token instead" ) return self._token @token.setter def token(self, value): self._token = value @property def account_id(self): # TODO: this needs to be resolved raise NotImplementedError( "missing call to self._refresh. " "Use get_frozen_credentials or get_account_id instead" ) return self._account_id @account_id.setter def account_id(self, value): self._account_id = value async def _refresh(self): if not self.refresh_needed(self._advisory_refresh_timeout): return # By this point we need a refresh but its not critical if not self._refresh_lock.locked(): async with self._refresh_lock: if not self.refresh_needed(self._advisory_refresh_timeout): return is_mandatory_refresh = self.refresh_needed( self._mandatory_refresh_timeout ) await self._protected_refresh( is_mandatory=is_mandatory_refresh ) return elif self.refresh_needed(self._mandatory_refresh_timeout): # If we're here, we absolutely need a refresh and the # lock is held so wait for it async with self._refresh_lock: # Might have refreshed by now if not self.refresh_needed(self._mandatory_refresh_timeout): return await self._protected_refresh(is_mandatory=True) async def _protected_refresh(self, is_mandatory): try: # AioEnvProvider._create_credentials_fetcher is not and does not need async metadata = await resolve_awaitable(self._refresh_using()) except Exception: period_name = 'mandatory' if is_mandatory else 'advisory' logger.warning( "Refreshing temporary credentials failed " "during %s refresh period.", period_name, exc_info=True, ) if is_mandatory: # If this is a mandatory refresh, then # all errors that occur when we attempt to refresh # credentials are propagated back to the user. raise # Otherwise we'll just return. # The end result will be that we'll use the current # set of temporary credentials we have. return self._set_from_data(metadata) self._frozen_credentials = ReadOnlyCredentials( self._access_key, self._secret_key, self._token, self._account_id ) if self._is_expired(): msg = ( "Credentials were refreshed, but the " "refreshed credentials are still expired." ) logger.warning(msg) raise RuntimeError(msg) async def get_frozen_credentials(self): await self._refresh() return self._frozen_credentials class AioDeferredRefreshableCredentials( DeferredRefreshableCredentials, AioRefreshableCredentials ): def __init__(self, refresh_using, method, time_fetcher=_local_now): self._refresh_using = refresh_using self._access_key = None self._secret_key = None self._token = None self._account_id = None self._expiry_time = None self._time_fetcher = time_fetcher self._refresh_lock = asyncio.Lock() self.method = method self._frozen_credentials = None class AioCachedCredentialFetcher(CachedCredentialFetcher): async def _get_credentials(self): raise NotImplementedError('_get_credentials()') async def fetch_credentials(self): return await self._get_cached_credentials() async def _get_cached_credentials(self): """Get up-to-date credentials. This will check the cache for up-to-date credentials, calling assume role if none are available. """ response = self._load_from_cache() if response is None: response = await self._get_credentials() self._write_to_cache(response) else: logger.debug("Credentials for role retrieved from cache.") creds = response['Credentials'] expiration = _serialize_if_needed(creds['Expiration'], iso=True) credentials = { 'access_key': creds['AccessKeyId'], 'secret_key': creds['SecretAccessKey'], 'token': creds['SessionToken'], 'expiry_time': expiration, 'account_id': creds.get('AccountId'), } return credentials class AioBaseAssumeRoleCredentialFetcher( BaseAssumeRoleCredentialFetcher, AioCachedCredentialFetcher ): pass class AioAssumeRoleCredentialFetcher( AssumeRoleCredentialFetcher, AioBaseAssumeRoleCredentialFetcher ): async def _get_credentials(self): """Get credentials by calling assume role.""" register_feature_ids(self.feature_ids) kwargs = self._assume_role_kwargs() client = await self._create_client() async with client as sts: response = await sts.assume_role(**kwargs) self._add_account_id_to_response(response) return response async def _create_client(self): """Create an STS client using the source credentials.""" frozen_credentials = ( await self._source_credentials.get_frozen_credentials() ) return self._client_creator( 'sts', aws_access_key_id=frozen_credentials.access_key, aws_secret_access_key=frozen_credentials.secret_key, aws_session_token=frozen_credentials.token, ) class AioAssumeRoleWithWebIdentityCredentialFetcher( AioBaseAssumeRoleCredentialFetcher ): def __init__( self, client_creator, web_identity_token_loader, role_arn, extra_args=None, cache=None, expiry_window_seconds=None, ): self._web_identity_token_loader = web_identity_token_loader super().__init__( client_creator, role_arn, extra_args=extra_args, cache=cache, expiry_window_seconds=expiry_window_seconds, ) async def _get_credentials(self): """Get credentials by calling assume role.""" register_feature_ids(self.feature_ids) kwargs = self._assume_role_kwargs() # Assume role with web identity does not require credentials other than # the token, explicitly configure the client to not sign requests. config = AioConfig(signature_version=UNSIGNED) async with self._client_creator('sts', config=config) as client: response = await client.assume_role_with_web_identity(**kwargs) self._add_account_id_to_response(response) return response def _assume_role_kwargs(self): """Get the arguments for assume role based on current configuration.""" assume_role_kwargs = deepcopy(self._assume_kwargs) identity_token = self._web_identity_token_loader() assume_role_kwargs['WebIdentityToken'] = identity_token return assume_role_kwargs class AioProcessProvider(ProcessProvider): def __init__(self, *args, popen=asyncio.create_subprocess_exec, **kwargs): super().__init__(*args, **kwargs, popen=popen) async def load(self): credential_process = self._credential_process if credential_process is None: return register_feature_id('CREDENTIALS_PROFILE_PROCESS') creds_dict = await self._retrieve_credentials_using(credential_process) register_feature_id('CREDENTIALS_PROCESS') if creds_dict.get('expiry_time') is not None: return AioRefreshableCredentials.create_from_metadata( creds_dict, lambda: self._retrieve_credentials_using(credential_process), self.METHOD, ) return AioCredentials( access_key=creds_dict['access_key'], secret_key=creds_dict['secret_key'], token=creds_dict.get('token'), method=self.METHOD, account_id=creds_dict.get('account_id'), ) async def _retrieve_credentials_using(self, credential_process): # We're not using shell=True, so we need to pass the # command and all arguments as a list. process_list = compat_shell_split(credential_process) p = await self._popen( *process_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout, stderr = await p.communicate() if p.returncode != 0: raise CredentialRetrievalError( provider=self.METHOD, error_msg=stderr.decode('utf-8') ) parsed = botocore.compat.json.loads(stdout.decode('utf-8')) version = parsed.get('Version', '') if version != 1: raise CredentialRetrievalError( provider=self.METHOD, error_msg=( f"Unsupported version '{version}' for credential process " f"provider, supported versions: 1" ), ) try: return { 'access_key': parsed['AccessKeyId'], 'secret_key': parsed['SecretAccessKey'], 'token': parsed.get('SessionToken'), 'expiry_time': parsed.get('Expiration'), 'account_id': self._get_account_id(parsed), } except KeyError as e: raise CredentialRetrievalError( provider=self.METHOD, error_msg=f"Missing required key in response: {e}", ) class AioInstanceMetadataProvider(InstanceMetadataProvider): async def load(self): fetcher = self._role_fetcher metadata = await fetcher.retrieve_iam_role_credentials() if not metadata: return None register_feature_id('CREDENTIALS_IMDS') logger.info( 'Found credentials from IAM Role: %s', metadata['role_name'] ) creds = AioRefreshableCredentials.create_from_metadata( metadata, method=self.METHOD, refresh_using=fetcher.retrieve_iam_role_credentials, ) return creds class AioEnvProvider(EnvProvider): async def load(self): access_key = self.environ.get(self._mapping['access_key'], '') if access_key: logger.info('Found credentials in environment variables.') fetcher = self._create_credentials_fetcher() credentials = fetcher(require_expiry=False) register_feature_id('CREDENTIALS_ENV_VARS') expiry_time = credentials['expiry_time'] if expiry_time is not None: expiry_time = parse(expiry_time) return AioRefreshableCredentials( credentials['access_key'], credentials['secret_key'], credentials['token'], expiry_time, refresh_using=fetcher, method=self.METHOD, account_id=credentials['account_id'], ) return AioCredentials( credentials['access_key'], credentials['secret_key'], credentials['token'], method=self.METHOD, account_id=credentials['account_id'], ) else: return None class AioOriginalEC2Provider(OriginalEC2Provider): async def load(self): if 'AWS_CREDENTIAL_FILE' in self._environ: full_path = os.path.expanduser( self._environ['AWS_CREDENTIAL_FILE'] ) creds = self._parser(full_path) if self.ACCESS_KEY in creds: logger.info('Found credentials in AWS_CREDENTIAL_FILE.') access_key = creds[self.ACCESS_KEY] secret_key = creds[self.SECRET_KEY] # EC2 creds file doesn't support session tokens. return AioCredentials( access_key, secret_key, method=self.METHOD ) else: return None class AioSharedCredentialProvider(SharedCredentialProvider): async def load(self): try: available_creds = self._ini_parser(self._creds_filename) except ConfigNotFound: return None if self._profile_name in available_creds: config = available_creds[self._profile_name] if self.ACCESS_KEY in config: logger.info( "Found credentials in shared credentials file: %s", self._creds_filename, ) access_key, secret_key = self._extract_creds_from_mapping( config, self.ACCESS_KEY, self.SECRET_KEY ) token = self._get_session_token(config) account_id = self._get_account_id(config) register_feature_id('CREDENTIALS_PROFILE') return AioCredentials( access_key, secret_key, token, method=self.METHOD, account_id=account_id, ) class AioConfigProvider(ConfigProvider): async def load(self): try: full_config = self._config_parser(self._config_filename) except ConfigNotFound: return None if self._profile_name in full_config['profiles']: profile_config = full_config['profiles'][self._profile_name] if self.ACCESS_KEY in profile_config: logger.info( "Credentials found in config file: %s", self._config_filename, ) access_key, secret_key = self._extract_creds_from_mapping( profile_config, self.ACCESS_KEY, self.SECRET_KEY ) token = self._get_session_token(profile_config) account_id = self._get_account_id(profile_config) register_feature_id('CREDENTIALS_PROFILE') return AioCredentials( access_key, secret_key, token, method=self.METHOD, account_id=account_id, ) else: return None class AioBotoProvider(BotoProvider): async def load(self): if self.BOTO_CONFIG_ENV in self._environ: potential_locations = [self._environ[self.BOTO_CONFIG_ENV]] else: potential_locations = self.DEFAULT_CONFIG_FILENAMES for filename in potential_locations: try: config = self._ini_parser(filename) except ConfigNotFound: # Move on to the next potential config file name. continue if 'Credentials' in config: credentials = config['Credentials'] if self.ACCESS_KEY in credentials: logger.info( "Found credentials in boto config file: %s", filename ) access_key, secret_key = self._extract_creds_from_mapping( credentials, self.ACCESS_KEY, self.SECRET_KEY ) register_feature_id('CREDENTIALS_BOTO2_CONFIG_FILE') return AioCredentials( access_key, secret_key, method=self.METHOD ) class AioAssumeRoleProvider(AssumeRoleProvider): async def load(self): self._loaded_config = self._load_config() profiles = self._loaded_config.get('profiles', {}) profile = profiles.get(self._profile_name, {}) if self._has_assume_role_config_vars(profile): return await self._load_creds_via_assume_role(self._profile_name) async def _load_creds_via_assume_role(self, profile_name): role_config = self._get_role_config(profile_name) source_credentials = await self._resolve_source_credentials( role_config, profile_name ) extra_args = {} role_session_name = role_config.get('role_session_name') if role_session_name is not None: extra_args['RoleSessionName'] = role_session_name external_id = role_config.get('external_id') if external_id is not None: extra_args['ExternalId'] = external_id mfa_serial = role_config.get('mfa_serial') if mfa_serial is not None: extra_args['SerialNumber'] = mfa_serial duration_seconds = role_config.get('duration_seconds') if duration_seconds is not None: extra_args['DurationSeconds'] = duration_seconds fetcher = AioAssumeRoleCredentialFetcher( client_creator=self._client_creator, source_credentials=source_credentials, role_arn=role_config['role_arn'], extra_args=extra_args, mfa_prompter=self._prompter, cache=self.cache, ) fetcher.feature_ids = self._feature_ids.copy() refresher = fetcher.fetch_credentials if mfa_serial is not None: refresher = create_mfa_serial_refresher(refresher) self._feature_ids.add('CREDENTIALS_STS_ASSUME_ROLE') register_feature_ids(self._feature_ids) # The initial credentials are empty and the expiration time is set # to now so that we can delay the call to assume role until it is # strictly needed. return AioDeferredRefreshableCredentials( method=self.METHOD, refresh_using=refresher, time_fetcher=_local_now, ) async def _resolve_source_credentials(self, role_config, profile_name): credential_source = role_config.get('credential_source') if credential_source is not None: self._feature_ids.add('CREDENTIALS_PROFILE_NAMED_PROVIDER') return await self._resolve_credentials_from_source( credential_source, profile_name ) source_profile = role_config['source_profile'] self._visited_profiles.append(source_profile) self._feature_ids.add('CREDENTIALS_PROFILE_SOURCE_PROFILE') return await self._resolve_credentials_from_profile(source_profile) async def _resolve_credentials_from_profile(self, profile_name): profiles = self._loaded_config.get('profiles', {}) profile = profiles[profile_name] self._feature_ids.add('CREDENTIALS_PROFILE') if ( self._has_static_credentials(profile) and not self._profile_provider_builder ): # This is only here for backwards compatibility. If this provider # isn't given a profile provider builder we still want to be able # to handle the basic static credential case as we would before the # profile provider builder parameter was added. return self._resolve_static_credentials_from_profile(profile) elif self._has_static_credentials( profile ) or not self._has_assume_role_config_vars(profile): profile_providers = self._profile_provider_builder.providers( profile_name=profile_name, disable_env_vars=True, ) profile_chain = AioCredentialResolver(profile_providers) credentials = await profile_chain.load_credentials() if credentials is None: error_message = ( 'The source profile "%s" must have credentials.' ) raise InvalidConfigError( error_msg=error_message % profile_name, ) return credentials return await self._load_creds_via_assume_role(profile_name) def _resolve_static_credentials_from_profile(self, profile): try: return AioCredentials( access_key=profile['aws_access_key_id'], secret_key=profile['aws_secret_access_key'], token=profile.get('aws_session_token'), ) except KeyError as e: raise PartialCredentialsError( provider=self.METHOD, cred_var=str(e) ) async def _resolve_credentials_from_source( self, credential_source, profile_name ): credentials = await self._credential_sourcer.source_credentials( credential_source ) if credentials is None: raise CredentialRetrievalError( provider=credential_source, error_msg=( 'No credentials found in credential_source referenced ' f'in profile {profile_name}' ), ) named_provider_feature_id = self.NAMED_PROVIDER_FEATURE_MAP.get( credential_source ) if named_provider_feature_id: self._feature_ids.add(named_provider_feature_id) return credentials class AioAssumeRoleWithWebIdentityProvider(AssumeRoleWithWebIdentityProvider): async def load(self): return await self._assume_role_with_web_identity() async def _assume_role_with_web_identity(self): token_path = self._get_config('web_identity_token_file') if not token_path: return None token_loader = self._token_loader_cls(token_path) role_arn = self._get_config('role_arn') if not role_arn: error_msg = ( 'The provided profile or the current environment is ' 'configured to assume role with web identity but has no ' 'role ARN configured. Ensure that the profile has the role_arn' 'configuration set or the AWS_ROLE_ARN env var is set.' ) raise InvalidConfigError(error_msg=error_msg) extra_args = {} role_session_name = self._get_config('role_session_name') if role_session_name is not None: extra_args['RoleSessionName'] = role_session_name fetcher = AioAssumeRoleWithWebIdentityCredentialFetcher( client_creator=self._client_creator, web_identity_token_loader=token_loader, role_arn=role_arn, extra_args=extra_args, cache=self.cache, ) fetcher.feature_ids = self._feature_ids.copy() self._feature_ids.add('CREDENTIALS_STS_ASSUME_ROLE_WEB_ID') register_feature_ids(self._feature_ids) # The initial credentials are empty and the expiration time is set # to now so that we can delay the call to assume role until it is # strictly needed. return AioDeferredRefreshableCredentials( method=self.METHOD, refresh_using=fetcher.fetch_credentials, ) class AioCanonicalNameCredentialSourcer(CanonicalNameCredentialSourcer): async def source_credentials(self, source_name): """Loads source credentials based on the provided configuration. :type source_name: str :param source_name: The value of credential_source in the config file. This is the canonical name of the credential provider. :rtype: Credentials """ source = self._get_provider(source_name) if isinstance(source, AioCredentialResolver): return await source.load_credentials() return await source.load() def _get_provider(self, canonical_name): """Return a credential provider by its canonical name. :type canonical_name: str :param canonical_name: The canonical name of the provider. :raises UnknownCredentialError: Raised if no credential provider by the provided name is found. """ provider = self._get_provider_by_canonical_name(canonical_name) # The AssumeRole provider should really be part of the SharedConfig # provider rather than being its own thing, but it is not. It is # effectively part of both the SharedConfig provider and the # SharedCredentials provider now due to the way it behaves. # Therefore if we want either of those providers we should return # the AssumeRole provider with it. if canonical_name.lower() in ['sharedconfig', 'sharedcredentials']: assume_role_provider = self._get_provider_by_method('assume-role') if assume_role_provider is not None: # The SharedConfig or SharedCredentials provider may not be # present if it was removed for some reason, but the # AssumeRole provider could still be present. In that case, # return the assume role provider by itself. if provider is None: return assume_role_provider # If both are present, return them both as a # CredentialResolver so that calling code can treat them as # a single entity. return AioCredentialResolver([assume_role_provider, provider]) if provider is None: raise UnknownCredentialError(name=canonical_name) return provider class AioContainerProvider(ContainerProvider): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # This will always run if no fetcher arg is provided if isinstance(self._fetcher, ContainerMetadataFetcher): self._fetcher = AioContainerMetadataFetcher() async def load(self): if self.ENV_VAR in self._environ or self.ENV_VAR_FULL in self._environ: return await self._retrieve_or_fail() async def _retrieve_or_fail(self): if self._provided_relative_uri(): full_uri = self._fetcher.full_url(self._environ[self.ENV_VAR]) else: full_uri = self._environ[self.ENV_VAR_FULL] fetcher = self._create_fetcher(full_uri) creds = await fetcher() return AioRefreshableCredentials( access_key=creds['access_key'], secret_key=creds['secret_key'], token=creds['token'], method=self.METHOD, expiry_time=_parse_if_needed(creds['expiry_time']), refresh_using=fetcher, account_id=creds.get('account_id'), ) def _create_fetcher(self, full_uri, *args, **kwargs): async def fetch_creds(): try: headers = self._build_headers() response = await self._fetcher.retrieve_full_uri( full_uri, headers=headers ) register_feature_id('CREDENTIALS_HTTP') except MetadataRetrievalError as e: logger.debug( "Error retrieving container metadata: %s", e, exc_info=True ) raise CredentialRetrievalError( provider=self.METHOD, error_msg=str(e) ) return { 'access_key': response['AccessKeyId'], 'secret_key': response['SecretAccessKey'], 'token': response['Token'], 'expiry_time': response['Expiration'], 'account_id': response.get('AccountId'), } return fetch_creds class AioCredentialResolver(CredentialResolver): async def load_credentials(self): """ Goes through the credentials chain, returning the first ``Credentials`` that could be loaded. """ # First provider to return a non-None response wins. for provider in self.providers: logger.debug("Looking for credentials via: %s", provider.METHOD) creds = await provider.load() if creds is not None: return creds # If we got here, no credentials could be found. # This feels like it should be an exception, but historically, ``None`` # is returned. # # +1 # -js return None class AioSSOCredentialFetcher( SSOCredentialFetcher, AioCachedCredentialFetcher ): async def _get_credentials(self): """Get credentials by calling SSO get role credentials.""" config = Config( signature_version=UNSIGNED, region_name=self._sso_region, ) async with self._client_creator('sso', config=config) as client: if self._token_provider: initial_token_data = self._token_provider.load_token() token = (await initial_token_data.get_frozen_token()).token else: token_dict = self._token_loader(self._start_url) token = token_dict['accessToken'] # raise an UnauthorizedSSOTokenError if the loaded legacy token # is expired to save a call to GetRoleCredentials with an # expired token. expiration = dateutil.parser.parse(token_dict['expiresAt']) remaining = total_seconds(expiration - self._time_fetcher()) if remaining <= 0: raise UnauthorizedSSOTokenError() kwargs = { 'roleName': self._role_name, 'accountId': self._account_id, 'accessToken': token, } try: register_feature_ids(self.feature_ids) response = await client.get_role_credentials(**kwargs) except client.exceptions.UnauthorizedException: raise UnauthorizedSSOTokenError() credentials = response['roleCredentials'] credentials = { 'ProviderType': 'sso', 'Credentials': { 'AccessKeyId': credentials['accessKeyId'], 'SecretAccessKey': credentials['secretAccessKey'], 'SessionToken': credentials['sessionToken'], 'Expiration': self._parse_timestamp( credentials['expiration'] ), 'AccountId': self._account_id, }, } return credentials class AioSSOProvider(SSOProvider): async def load(self): sso_config = self._load_sso_config() if not sso_config: return None fetcher_kwargs = { 'start_url': sso_config['sso_start_url'], 'sso_region': sso_config['sso_region'], 'role_name': sso_config['sso_role_name'], 'account_id': sso_config['sso_account_id'], 'client_creator': self._client_creator, 'token_loader': SSOTokenLoader(cache=self._token_cache), 'cache': self.cache, } sso_session_in_config = 'sso_session' in sso_config if sso_session_in_config: fetcher_kwargs['sso_session_name'] = sso_config['sso_session'] fetcher_kwargs['token_provider'] = self._token_provider self._feature_ids.add('CREDENTIALS_PROFILE_SSO') else: self._feature_ids.add('CREDENTIALS_PROFILE_SSO_LEGACY') sso_fetcher = AioSSOCredentialFetcher(**fetcher_kwargs) sso_fetcher.feature_ids = self._feature_ids.copy() if sso_session_in_config: self._feature_ids.add('CREDENTIALS_SSO') else: self._feature_ids.add('CREDENTIALS_SSO_LEGACY') register_feature_ids(self._feature_ids) return AioDeferredRefreshableCredentials( method=self.METHOD, refresh_using=sso_fetcher.fetch_credentials, ) def _get_client_creator(session, region_name): """Create a client creator function for use in credential providers. This is the async version of botocore.credentials._get_client_creator that uses aiobotocore's create_nested_client. """ def client_creator(service_name, **kwargs): create_client_kwargs = {'region_name': region_name} create_client_kwargs.update(**kwargs) return create_nested_client( session, service_name, **create_client_kwargs ) return client_creator aio-libs-aiobotocore-e39c8b3/aiobotocore/discovery.py000066400000000000000000000070351510465277300230430ustar00rootroot00000000000000import inspect from botocore.discovery import ( EndpointDiscoveryHandler, EndpointDiscoveryManager, EndpointDiscoveryRefreshFailed, HTTPClientError, logger, ) class AioEndpointDiscoveryManager(EndpointDiscoveryManager): async def _refresh_current_endpoints(self, **kwargs): cache_key = self._create_cache_key(**kwargs) try: response = self._describe_endpoints(**kwargs) if inspect.isawaitable(response): response = await response endpoints = self._parse_endpoints(response) self._cache[cache_key] = endpoints self._failed_attempts.pop(cache_key, None) return endpoints except (ConnectionError, HTTPClientError): self._failed_attempts[cache_key] = self._time() + 60 return None async def describe_endpoint(self, **kwargs): operation = kwargs['Operation'] discovery_required = self._model.discovery_required_for(operation) if not self._always_discover and not discovery_required: # Discovery set to only run on required operations logger.debug( 'Optional discovery disabled. Skipping discovery for Operation: %s', operation, ) return None # Get the endpoint for the provided operation and identifiers cache_key = self._create_cache_key(**kwargs) endpoints = self._get_current_endpoints(cache_key) if endpoints: return self._select_endpoint(endpoints) # All known endpoints are stale recently_failed = self._recently_failed(cache_key) if not recently_failed: # We haven't failed to discover recently, go ahead and refresh endpoints = await self._refresh_current_endpoints(**kwargs) if endpoints: return self._select_endpoint(endpoints) # Discovery has failed recently, do our best to get an endpoint logger.debug('Endpoint Discovery has failed for: %s', kwargs) stale_entries = self._cache.get(cache_key, None) if stale_entries: # We have stale entries, use those while discovery is failing return self._select_endpoint(stale_entries) if discovery_required: # It looks strange to be checking recently_failed again but, # this informs us as to whether or not we tried to refresh earlier if recently_failed: # Discovery is required and we haven't already refreshed endpoints = await self._refresh_current_endpoints(**kwargs) if endpoints: return self._select_endpoint(endpoints) # No endpoints even refresh, raise hard error raise EndpointDiscoveryRefreshFailed() # Discovery is optional, just use the default endpoint for now return None class AioEndpointDiscoveryHandler(EndpointDiscoveryHandler): async def discover_endpoint(self, request, operation_name, **kwargs): ids = request.context.get('discovery', {}).get('identifiers') if ids is None: return endpoint = await self._manager.describe_endpoint( Operation=operation_name, Identifiers=ids ) if endpoint is None: logger.debug('Failed to discover and inject endpoint') return if not endpoint.startswith('http'): endpoint = 'https://' + endpoint logger.debug('Injecting discovered endpoint: %s', endpoint) request.url = endpoint aio-libs-aiobotocore-e39c8b3/aiobotocore/endpoint.py000066400000000000000000000304071510465277300226530ustar00rootroot00000000000000import asyncio from botocore.endpoint import ( DEFAULT_TIMEOUT, MAX_POOL_CONNECTIONS, Endpoint, EndpointCreator, HTTPClientError, create_request_object, history_recorder, is_valid_endpoint_url, is_valid_ipv6_endpoint_url, logger, ) from botocore.hooks import first_non_none_response from aiobotocore.httpchecksum import handle_checksum_body from aiobotocore.httpsession import AIOHTTPSession from aiobotocore.parsers import AioResponseParserFactory from aiobotocore.response import HttpxStreamingBody, StreamingBody try: import httpx except ImportError: httpx = None DEFAULT_HTTP_SESSION_CLS = AIOHTTPSession async def convert_to_response_dict(http_response, operation_model): """Convert an HTTP response object to a request dict. This converts the HTTP response object to a dictionary. :type http_response: botocore.awsrequest.AWSResponse :param http_response: The HTTP response from an AWS service request. :rtype: dict :return: A response dictionary which will contain the following keys: * headers (dict) * status_code (int) * body (string or file-like object) """ response_dict = { 'headers': http_response.headers, 'status_code': http_response.status_code, 'context': { 'operation_name': operation_model.name, }, } if response_dict['status_code'] >= 300: response_dict['body'] = await http_response.content elif operation_model.has_event_stream_output: response_dict['body'] = http_response.raw elif operation_model.has_streaming_output: if httpx and isinstance(http_response.raw, httpx.Response): response_dict['body'] = HttpxStreamingBody(http_response.raw) else: length = response_dict['headers'].get('content-length') response_dict['body'] = StreamingBody(http_response.raw, length) else: response_dict['body'] = await http_response.content return response_dict class AioEndpoint(Endpoint): def __init__( self, host, endpoint_prefix, event_emitter, response_parser_factory=None, http_session=None, ): if response_parser_factory is None: response_parser_factory = AioResponseParserFactory() if http_session is None: raise ValueError('http_session must be provided') super().__init__( host=host, endpoint_prefix=endpoint_prefix, event_emitter=event_emitter, response_parser_factory=response_parser_factory, http_session=http_session, ) async def close(self): await self.http_session.close() async def create_request(self, params, operation_model=None): request = create_request_object(params) if operation_model: request.stream_output = any( [ operation_model.has_streaming_output, operation_model.has_event_stream_output, ] ) service_id = operation_model.service_model.service_id.hyphenize() event_name = f'request-created.{service_id}.{operation_model.name}' await self._event_emitter.emit( event_name, request=request, operation_name=operation_model.name, ) prepared_request = self.prepare_request(request) return prepared_request async def _send_request(self, request_dict, operation_model): attempts = 1 context = request_dict['context'] self._update_retries_context(context, attempts) request = await self.create_request(request_dict, operation_model) success_response, exception = await self._get_response( request, operation_model, context ) while await self._needs_retry( attempts, operation_model, request_dict, success_response, exception, ): attempts += 1 self._update_retries_context(context, attempts, success_response) # If there is a stream associated with the request, we need # to reset it before attempting to send the request again. # This will ensure that we resend the entire contents of the # body. request.reset_stream() # Create a new request when retried (including a new signature). request = await self.create_request(request_dict, operation_model) success_response, exception = await self._get_response( request, operation_model, context ) if ( success_response is not None and 'ResponseMetadata' in success_response[1] ): # We want to share num retries, not num attempts. total_retries = attempts - 1 success_response[1]['ResponseMetadata']['RetryAttempts'] = ( total_retries ) if exception is not None: raise exception else: return success_response async def _get_response(self, request, operation_model, context): # This will return a tuple of (success_response, exception) # and success_response is itself a tuple of # (http_response, parsed_dict). # If an exception occurs then the success_response is None. # If no exception occurs then exception is None. success_response, exception = await self._do_get_response( request, operation_model, context ) kwargs_to_emit = { 'response_dict': None, 'parsed_response': None, 'context': context, 'exception': exception, } if success_response is not None: http_response, parsed_response = success_response kwargs_to_emit['parsed_response'] = parsed_response kwargs_to_emit['response_dict'] = await convert_to_response_dict( http_response, operation_model ) service_id = operation_model.service_model.service_id.hyphenize() await self._event_emitter.emit( f"response-received.{service_id}.{operation_model.name}", **kwargs_to_emit, ) return success_response, exception async def _do_get_response(self, request, operation_model, context): try: logger.debug("Sending http request: %s", request) history_recorder.record( 'HTTP_REQUEST', { 'method': request.method, 'headers': request.headers, 'streaming': operation_model.has_streaming_input, 'url': request.url, 'body': request.body, }, ) service_id = operation_model.service_model.service_id.hyphenize() event_name = f"before-send.{service_id}.{operation_model.name}" responses = await self._event_emitter.emit( event_name, request=request ) http_response = first_non_none_response(responses) if http_response is None: http_response = await self._send(request) except HTTPClientError as e: return (None, e) except Exception as e: logger.debug( "Exception received when sending HTTP request.", exc_info=True ) return (None, e) # This returns the http_response and the parsed_data. response_dict = await convert_to_response_dict( http_response, operation_model ) await handle_checksum_body( http_response, response_dict, context, operation_model, ) http_response_record_dict = response_dict.copy() http_response_record_dict['streaming'] = ( operation_model.has_streaming_output ) history_recorder.record('HTTP_RESPONSE', http_response_record_dict) protocol = operation_model.service_model.resolved_protocol customized_response_dict = {} await self._event_emitter.emit( f"before-parse.{service_id}.{operation_model.name}", operation_model=operation_model, response_dict=response_dict, customized_response_dict=customized_response_dict, ) parser = self._response_parser_factory.create_parser(protocol) parsed_response = await parser.parse( response_dict, operation_model.output_shape ) parsed_response.update(customized_response_dict) if http_response.status_code >= 300: await self._add_modeled_error_fields( response_dict, parsed_response, operation_model, parser, ) history_recorder.record('PARSED_RESPONSE', parsed_response) return (http_response, parsed_response), None async def _add_modeled_error_fields( self, response_dict, parsed_response, operation_model, parser, ): error_code = parsed_response.get("Error", {}).get("Code") if error_code is None: return service_model = operation_model.service_model error_shape = service_model.shape_for_error_code(error_code) if error_shape is None: return modeled_parse = await parser.parse(response_dict, error_shape) # TODO: avoid naming conflicts with ResponseMetadata and Error parsed_response.update(modeled_parse) # NOTE: The only line changed here changing time.sleep to asyncio.sleep async def _needs_retry( self, attempts, operation_model, request_dict, response=None, caught_exception=None, ): service_id = operation_model.service_model.service_id.hyphenize() event_name = f"needs-retry.{service_id}.{operation_model.name}" responses = await self._event_emitter.emit( event_name, response=response, endpoint=self, operation=operation_model, attempts=attempts, caught_exception=caught_exception, request_dict=request_dict, ) handler_response = first_non_none_response(responses) if handler_response is None: return False else: # Request needs to be retried, and we need to sleep # for the specified number of times. logger.debug( "Response received to retry, sleeping for %s seconds", handler_response, ) await asyncio.sleep(handler_response) return True async def _send(self, request): return await self.http_session.send(request) class AioEndpointCreator(EndpointCreator): def create_endpoint( self, service_model, region_name, endpoint_url, verify=None, response_parser_factory=None, timeout=DEFAULT_TIMEOUT, max_pool_connections=MAX_POOL_CONNECTIONS, http_session_cls=DEFAULT_HTTP_SESSION_CLS, proxies=None, socket_options=None, client_cert=None, proxies_config=None, connector_args=None, ): if not is_valid_endpoint_url( endpoint_url ) and not is_valid_ipv6_endpoint_url(endpoint_url): raise ValueError(f"Invalid endpoint: {endpoint_url}") if proxies is None: proxies = self._get_proxies(endpoint_url) endpoint_prefix = service_model.endpoint_prefix logger.debug('Setting %s timeout as %s', endpoint_prefix, timeout) http_session = http_session_cls( timeout=timeout, proxies=proxies, verify=self._get_verify_value(verify), max_pool_connections=max_pool_connections, socket_options=socket_options, client_cert=client_cert, proxies_config=proxies_config, connector_args=connector_args, ) return AioEndpoint( endpoint_url, endpoint_prefix=endpoint_prefix, event_emitter=self._event_emitter, response_parser_factory=response_parser_factory, http_session=http_session, ) aio-libs-aiobotocore-e39c8b3/aiobotocore/eventstream.py000066400000000000000000000032151510465277300233650ustar00rootroot00000000000000from botocore.eventstream import ( EventStream, EventStreamBuffer, NoInitialResponseError, ) from botocore.exceptions import EventStreamError class AioEventStream(EventStream): def __iter__(self): raise NotImplementedError('Use async-for instead') def __aiter__(self): return self.__anext__() async def __anext__(self): async for event in self._event_generator: parsed_event = await self._parse_event(event) if parsed_event: yield parsed_event async def _create_raw_event_generator(self): event_stream_buffer = EventStreamBuffer() async for chunk, _ in self._raw_stream.content.iter_chunks(): event_stream_buffer.add_data(chunk) for event in event_stream_buffer: yield event # unfortunately no yield from async func support async def _parse_event(self, event): response_dict = event.to_response_dict() parsed_response = await self._parser.parse( response_dict, self._output_shape ) if response_dict['status_code'] == 200: return parsed_response else: raise EventStreamError(parsed_response, self._operation_name) async def get_initial_response(self): try: async for event in self._event_generator: event_type = event.headers.get(':event-type') if event_type == 'initial-response': return event break except StopIteration: pass raise NoInitialResponseError() # self._raw_stream.close() is sync so no override needed aio-libs-aiobotocore-e39c8b3/aiobotocore/handlers.py000066400000000000000000000076571510465277300226460ustar00rootroot00000000000000from botocore.handlers import ( ETree, _get_cross_region_presigned_url, _get_presigned_url_source_and_destination_regions, _looks_like_special_case_error, logger, ) async def check_for_200_error(response, **kwargs): """This function has been deprecated, but is kept for backwards compatibility.""" # From: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html # There are two opportunities for a copy request to return an error. One # can occur when Amazon S3 receives the copy request and the other can # occur while Amazon S3 is copying the files. If the error occurs before # the copy operation starts, you receive a standard Amazon S3 error. If the # error occurs during the copy operation, the error response is embedded in # the 200 OK response. This means that a 200 OK response can contain either # a success or an error. Make sure to design your application to parse the # contents of the response and handle it appropriately. # # So this handler checks for this case. Even though the server sends a # 200 response, conceptually this should be handled exactly like a # 500 response (with respect to raising exceptions, retries, etc.) # We're connected *before* all the other retry logic handlers, so as long # as we switch the error code to 500, we'll retry the error as expected. if response is None: # A None response can happen if an exception is raised while # trying to retrieve the response. See Endpoint._get_response(). return http_response, parsed = response if _looks_like_special_case_error( http_response.status_code, await http_response.content ): logger.debug( "Error found for response with 200 status code, " "errors: %s, changing status code to " "500.", parsed, ) http_response.status_code = 500 async def inject_presigned_url_ec2(params, request_signer, model, **kwargs): # The customer can still provide this, so we should pass if they do. if 'PresignedUrl' in params['body']: return src, dest = _get_presigned_url_source_and_destination_regions( request_signer, params['body'] ) url = await _get_cross_region_presigned_url( request_signer, params, model, src, dest ) params['body']['PresignedUrl'] = url # EC2 Requires that the destination region be sent over the wire in # addition to the source region. params['body']['DestinationRegion'] = dest async def inject_presigned_url_rds(params, request_signer, model, **kwargs): # SourceRegion is not required for RDS operations, so it's possible that # it isn't set. In that case it's probably a local copy so we don't need # to do anything else. if 'SourceRegion' not in params['body']: return src, dest = _get_presigned_url_source_and_destination_regions( request_signer, params['body'] ) # Since SourceRegion isn't actually modeled for RDS, it needs to be # removed from the request params before we send the actual request. del params['body']['SourceRegion'] if 'PreSignedUrl' in params['body']: return url = await _get_cross_region_presigned_url( request_signer, params, model, src, dest ) params['body']['PreSignedUrl'] = url async def parse_get_bucket_location(parsed, http_response, **kwargs): # s3.GetBucketLocation cannot be modeled properly. To # account for this we just manually parse the XML document. # The "parsed" passed in only has the ResponseMetadata # filled out. This handler will fill in the LocationConstraint # value. if http_response.raw is None: return response_body = await http_response.content parser = ETree.XMLParser(target=ETree.TreeBuilder(), encoding='utf-8') parser.feed(response_body) root = parser.close() region = root.text parsed['LocationConstraint'] = region aio-libs-aiobotocore-e39c8b3/aiobotocore/hooks.py000066400000000000000000000065661510465277300221670ustar00rootroot00000000000000from botocore.handlers import ( inject_presigned_url_ec2 as boto_inject_presigned_url_ec2, ) from botocore.handlers import ( inject_presigned_url_rds as boto_inject_presigned_url_rds, ) from botocore.handlers import ( parse_get_bucket_location as boto_parse_get_bucket_location, ) from botocore.hooks import HierarchicalEmitter, logger from botocore.signers import ( add_dsql_generate_db_auth_token_methods as boto_add_dsql_generate_db_auth_token_methods, ) from botocore.signers import ( add_generate_db_auth_token as boto_add_generate_db_auth_token, ) from botocore.signers import ( add_generate_presigned_post as boto_add_generate_presigned_post, ) from botocore.signers import ( add_generate_presigned_url as boto_add_generate_presigned_url, ) from ._helpers import resolve_awaitable from .handlers import ( inject_presigned_url_ec2, inject_presigned_url_rds, parse_get_bucket_location, ) from .signers import ( add_dsql_generate_db_auth_token_methods, add_generate_db_auth_token, add_generate_presigned_post, add_generate_presigned_url, ) _HANDLER_MAPPING = { boto_inject_presigned_url_ec2: inject_presigned_url_ec2, boto_inject_presigned_url_rds: inject_presigned_url_rds, boto_add_generate_presigned_url: add_generate_presigned_url, boto_add_generate_presigned_post: add_generate_presigned_post, boto_add_generate_db_auth_token: add_generate_db_auth_token, boto_parse_get_bucket_location: parse_get_bucket_location, boto_add_dsql_generate_db_auth_token_methods: add_dsql_generate_db_auth_token_methods, } class AioHierarchicalEmitter(HierarchicalEmitter): async def _emit(self, event_name, kwargs, stop_on_response=False): responses = [] # Invoke the event handlers from most specific # to least specific, each time stripping off a dot. handlers_to_call = self._lookup_cache.get(event_name) if handlers_to_call is None: handlers_to_call = self._handlers.prefix_search(event_name) self._lookup_cache[event_name] = handlers_to_call elif not handlers_to_call: # Short circuit and return an empty response is we have # no handlers to call. This is the common case where # for the majority of signals, nothing is listening. return [] kwargs['event_name'] = event_name responses = [] for handler in handlers_to_call: logger.debug('Event %s: calling handler %s', event_name, handler) # Await the handler if its a coroutine. response = await resolve_awaitable(handler(**kwargs)) responses.append((handler, response)) if stop_on_response and response is not None: return responses return responses async def emit_until_response(self, event_name, **kwargs): responses = await self._emit(event_name, kwargs, stop_on_response=True) if responses: return responses[-1] else: return None, None def _verify_and_register( self, event_name, handler, unique_id, register_method, unique_id_uses_count, ): handler = _HANDLER_MAPPING.get(handler, handler) self._verify_is_callable(handler) self._verify_accept_kwargs(handler) register_method(event_name, handler, unique_id, unique_id_uses_count) aio-libs-aiobotocore-e39c8b3/aiobotocore/httpchecksum.py000066400000000000000000000245311510465277300235360ustar00rootroot00000000000000import io from botocore.httpchecksum import ( _CHECKSUM_CLS, AwsChunkedWrapper, FlexibleChecksumError, _apply_request_header_checksum, _register_checksum_algorithm_feature_id, base64, conditionally_calculate_md5, determine_content_length, logger, ) from aiobotocore._helpers import resolve_awaitable from aiobotocore.response import HttpxStreamingBody, StreamingBody try: import httpx except ImportError: httpx = None class AioAwsChunkedWrapper(AwsChunkedWrapper): async def read(self, size=None): # Normalize "read all" size values to None if size is not None and size <= 0: size = None # If the underlying body is done and we have nothing left then # end the stream if self._complete and not self._remaining: return b"" # While we're not done and want more bytes want_more_bytes = size is None or size > len(self._remaining) while not self._complete and want_more_bytes: self._remaining += await self._make_chunk() want_more_bytes = size is None or size > len(self._remaining) # If size was None, we want to return everything if size is None: size = len(self._remaining) # Return a chunk up to the size asked for to_return = self._remaining[:size] self._remaining = self._remaining[size:] return to_return async def _make_chunk(self): # NOTE: Chunk size is not deterministic as read could return less. This # means we cannot know the content length of the encoded aws-chunked # stream ahead of time without ensuring a consistent chunk size raw_chunk = await resolve_awaitable(self._raw.read(self._chunk_size)) hex_len = hex(len(raw_chunk))[2:].encode("ascii") self._complete = not raw_chunk if self._checksum: self._checksum.update(raw_chunk) if self._checksum and self._complete: name = self._checksum_name.encode("ascii") checksum = self._checksum.b64digest().encode("ascii") return b"0\r\n%s:%s\r\n\r\n" % (name, checksum) return b"%s\r\n%s\r\n" % (hex_len, raw_chunk) def __aiter__(self): return self async def __anext__(self): while not self._complete: return await self._make_chunk() raise StopAsyncIteration() # unfortunately we can't inherit from botocore's StreamingChecksumBody due to # subclassing class StreamingChecksumBody(StreamingBody): def __init__(self, raw_stream, content_length, checksum, expected): super().__init__(raw_stream, content_length) self._checksum = checksum self._expected = expected async def read(self, amt=None): chunk = await super().read(amt=amt) self._checksum.update(chunk) if amt is None or (not chunk and amt > 0): self._validate_checksum() return chunk async def readinto(self, b: bytearray): chunk = await self.__wrapped__.content.read(len(b)) amount_read = len(chunk) b[:amount_read] = chunk if amount_read == len(b): view = b else: view = memoryview(b)[:amount_read] self._checksum.update(view) if amount_read == 0 and len(b) > 0: self._validate_checksum() return amount_read def _validate_checksum(self): if self._checksum.digest() != base64.b64decode(self._expected): error_msg = ( f"Expected checksum {self._expected} did not match calculated " f"checksum: {self._checksum.b64digest()}" ) raise FlexibleChecksumError(error_msg=error_msg) # TODO: fix inheritance? read & _validate_checksum are the exact same as above # only diff is super class and how to call __init__ class HttpxStreamingChecksumBody(HttpxStreamingBody): def __init__(self, raw_stream, content_length, checksum, expected): # HttpxStreamingbody doesn't use content_length super().__init__(raw_stream) self._checksum = checksum self._expected = expected # TODO: this class is largely (or possibly entirely) untested. The tests need to be # more thoroughly rewritten wherever they directly create Streamingbody, # StreamingChecksumBody, etc. async def read(self, amt=None): chunk = await super().read(amt=amt) self._checksum.update(chunk) if amt is None or (not chunk and amt > 0): self._validate_checksum() return chunk async def readinto(self, b: bytearray): chunk = await self.__wrapped__.content.read(len(b)) amount_read = len(chunk) b[:amount_read] = chunk if amount_read == len(b): view = b else: view = memoryview(b)[:amount_read] self._checksum.update(view) if amount_read == 0 and len(b) > 0: self._validate_checksum() return amount_read def _validate_checksum(self): if self._checksum.digest() != base64.b64decode(self._expected): error_msg = ( f"Expected checksum {self._expected} did not match calculated " f"checksum: {self._checksum.b64digest()}" ) raise FlexibleChecksumError(error_msg=error_msg) async def handle_checksum_body( http_response, response, context, operation_model ): headers = response["headers"] checksum_context = context.get("checksum", {}) algorithms = checksum_context.get("response_algorithms") if not algorithms: return for algorithm in algorithms: header_name = f"x-amz-checksum-{algorithm}" # If the header is not found, check the next algorithm if header_name not in headers: continue # If a - is in the checksum this is not valid Base64. S3 returns # checksums that include a -# suffix to indicate a checksum derived # from the hash of all part checksums. We cannot wrap this response if "-" in headers[header_name]: continue if operation_model.has_streaming_output: response["body"] = _handle_streaming_response( http_response, response, algorithm ) else: response["body"] = await _handle_bytes_response( http_response, response, algorithm ) # Expose metadata that the checksum check actually occurred checksum_context = response["context"].get("checksum", {}) checksum_context["response_algorithm"] = algorithm response["context"]["checksum"] = checksum_context return logger.debug( 'Skipping checksum validation. Response did not contain one of the following algorithms: %s.', algorithms, ) def _handle_streaming_response(http_response, response, algorithm): checksum_cls = _CHECKSUM_CLS.get(algorithm) header_name = f"x-amz-checksum-{algorithm}" if httpx is not None and isinstance(http_response.raw, httpx.Response): streaming_cls = HttpxStreamingChecksumBody else: streaming_cls = StreamingChecksumBody return streaming_cls( http_response.raw, response["headers"].get("content-length"), checksum_cls(), response["headers"][header_name], ) async def _handle_bytes_response(http_response, response, algorithm): body = await http_response.content header_name = f"x-amz-checksum-{algorithm}" checksum_cls = _CHECKSUM_CLS.get(algorithm) checksum = checksum_cls() checksum.update(body) expected = response["headers"][header_name] if checksum.digest() != base64.b64decode(expected): error_msg = ( f"Expected checksum {expected} did not match calculated " f"checksum: {checksum.b64digest()}" ) raise FlexibleChecksumError(error_msg=error_msg) return body def apply_request_checksum(request): checksum_context = request.get("context", {}).get("checksum", {}) algorithm = checksum_context.get("request_algorithm") if not algorithm: return if algorithm == "conditional-md5": # Special case to handle the http checksum required trait conditionally_calculate_md5(request) elif algorithm["in"] == "header": _apply_request_header_checksum(request) elif algorithm["in"] == "trailer": _apply_request_trailer_checksum(request) else: raise FlexibleChecksumError( error_msg="Unknown checksum variant: {}".format(algorithm["in"]) ) if "request_algorithm_header" in checksum_context: request_algorithm_header = checksum_context["request_algorithm_header"] request["headers"][request_algorithm_header["name"]] = ( request_algorithm_header["value"] ) def _apply_request_trailer_checksum(request): checksum_context = request.get("context", {}).get("checksum", {}) algorithm = checksum_context.get("request_algorithm") location_name = algorithm["name"] checksum_cls = _CHECKSUM_CLS.get(algorithm["algorithm"]) headers = request["headers"] body = request["body"] if location_name in headers: # If the header is already set by the customer, skip calculation return # Cannot set this as aiohttp complains headers["Transfer-Encoding"] = "chunked" if "Content-Encoding" in headers: # We need to preserve the existing content encoding and add # aws-chunked as a new content encoding. headers["Content-Encoding"] += ",aws-chunked" else: headers["Content-Encoding"] = "aws-chunked" headers["X-Amz-Trailer"] = location_name _register_checksum_algorithm_feature_id(algorithm) content_length = determine_content_length(body) if content_length is not None: # Send the decoded content length if we can determine it. Some # services such as S3 may require the decoded content length headers["X-Amz-Decoded-Content-Length"] = str(content_length) if "Content-Length" in headers: del headers["Content-Length"] logger.debug( "Removing the Content-Length header since 'chunked' is specified for Transfer-Encoding." ) if isinstance(body, (bytes, bytearray)): body = io.BytesIO(body) request["body"] = AioAwsChunkedWrapper( body, checksum_cls=checksum_cls, checksum_name=location_name, ) aio-libs-aiobotocore-e39c8b3/aiobotocore/httpsession.py000066400000000000000000000236041510465277300234170ustar00rootroot00000000000000import asyncio import contextlib import io import os import socket from concurrent.futures import CancelledError from typing import Optional import aiohttp # lgtm [py/import-and-import-from] from aiohttp import ( ClientConnectionError, ClientConnectorError, ClientHttpProxyError, ClientProxyConnectionError, ClientSSLError, ServerDisconnectedError, ServerTimeoutError, ) from aiohttp.client import URL from botocore.httpsession import ( MAX_POOL_CONNECTIONS, ConnectionClosedError, ConnectTimeoutError, EndpointConnectionError, HTTPClientError, InvalidProxiesConfigError, LocationParseError, ProxyConfiguration, ProxyConnectionError, ReadTimeoutError, SSLError, _is_ipaddress, create_urllib3_context, ensure_boolean, get_cert_path, logger, mask_proxy_url, parse_url, urlparse, ) from multidict import CIMultiDict import aiobotocore.awsrequest from ._constants import DEFAULT_KEEPALIVE_TIMEOUT from ._endpoint_helpers import _IOBaseWrapper, _text class AIOHTTPSession: def __init__( self, verify: bool = True, proxies: dict[str, str] = None, # {scheme: url} timeout: float = None, max_pool_connections: int = MAX_POOL_CONNECTIONS, socket_options=None, client_cert=None, proxies_config=None, connector_args=None, ): self._exit_stack = contextlib.AsyncExitStack() # TODO: handle socket_options # keep track of sessions by proxy url (if any) self._sessions: dict[Optional[str], aiohttp.ClientSession] = {} self._verify = verify self._proxy_config = ProxyConfiguration( proxies=proxies, proxies_settings=proxies_config ) if isinstance(timeout, (list, tuple)): conn_timeout, read_timeout = timeout else: conn_timeout = read_timeout = timeout timeout = aiohttp.ClientTimeout( sock_connect=conn_timeout, sock_read=read_timeout ) self._cert_file = None self._key_file = None if isinstance(client_cert, str): self._cert_file = client_cert elif isinstance(client_cert, tuple): self._cert_file, self._key_file = client_cert self._timeout = timeout self._connector_args = connector_args if self._connector_args is None: self._connector_args = dict( keepalive_timeout=DEFAULT_KEEPALIVE_TIMEOUT ) self._max_pool_connections = max_pool_connections self._socket_options = socket_options if socket_options is None: self._socket_options = [] # aiohttp handles 100 continue so we shouldn't need AWSHTTP[S]ConnectionPool # it also pools by host so we don't need a manager, and can pass proxy via # request so don't need proxy manager async def __aenter__(self): assert not self._sessions return self async def __aexit__(self, exc_type, exc_val, exc_tb): self._sessions.clear() await self._exit_stack.aclose() def _get_ssl_context(self): return create_urllib3_context() def _setup_proxy_ssl_context(self, proxy_url): proxies_settings = self._proxy_config.settings proxy_ca_bundle = proxies_settings.get('proxy_ca_bundle') proxy_cert = proxies_settings.get('proxy_client_cert') if proxy_ca_bundle is None and proxy_cert is None: return None context = self._get_ssl_context() try: url = parse_url(proxy_url) # urllib3 disables this by default but we need it for proper # proxy tls negotiation when proxy_url is not an IP Address if not _is_ipaddress(url.host): context.check_hostname = True if proxy_ca_bundle is not None: context.load_verify_locations(cafile=proxy_ca_bundle) if isinstance(proxy_cert, tuple): context.load_cert_chain(proxy_cert[0], keyfile=proxy_cert[1]) elif isinstance(proxy_cert, str): context.load_cert_chain(proxy_cert) return context except (OSError, LocationParseError) as e: raise InvalidProxiesConfigError(error=e) def _chunked(self, headers): transfer_encoding = headers.get('Transfer-Encoding', '') if chunked := transfer_encoding.lower() == 'chunked': # aiohttp wants chunking as a param, and not a header del headers['Transfer-Encoding'] return chunked or None def _create_connector(self, proxy_url): ssl_context = None if bool(self._verify): if proxy_url: ssl_context = self._setup_proxy_ssl_context(proxy_url) # TODO: add support for # proxies_settings.get('proxy_use_forwarding_for_https') else: ssl_context = self._get_ssl_context() if ssl_context: if self._cert_file: ssl_context.load_cert_chain( self._cert_file, self._key_file, ) # inline self._setup_ssl_cert ca_certs = get_cert_path(self._verify) if ca_certs: ssl_context.load_verify_locations(ca_certs, None, None) return aiohttp.TCPConnector( limit=self._max_pool_connections, ssl=ssl_context or False, **self._connector_args, ) async def _get_session(self, proxy_url): if not (session := self._sessions.get(proxy_url)): connector = self._create_connector(proxy_url) self._sessions[proxy_url] = ( session ) = await self._exit_stack.enter_async_context( aiohttp.ClientSession( connector=connector, timeout=self._timeout, skip_auto_headers={'CONTENT-TYPE'}, auto_decompress=False, ), ) return session async def close(self): await self.__aexit__(None, None, None) async def send(self, request): try: proxy_url = self._proxy_config.proxy_url_for(request.url) proxy_headers = self._proxy_config.proxy_headers_for(request.url) url = request.url headers = request.headers data = request.body if ensure_boolean( os.environ.get('BOTO_EXPERIMENTAL__ADD_PROXY_HOST_HEADER', '') ): # This is currently an "experimental" feature which provides # no guarantees of backwards compatibility. It may be subject # to change or removal in any patch version. Anyone opting in # to this feature should strictly pin botocore. host = urlparse(request.url).hostname proxy_headers['host'] = host headers_ = CIMultiDict( (z[0], _text(z[1], encoding='utf-8')) for z in headers.items() ) # https://github.com/boto/botocore/issues/1255 headers_['Accept-Encoding'] = 'identity' if isinstance(data, io.IOBase): data = _IOBaseWrapper(data) url = URL(url, encoded=True) session = await self._get_session(proxy_url) response = await session.request( request.method, url=url, chunked=self._chunked(headers_), headers=headers_, data=data, proxy=proxy_url, proxy_headers=proxy_headers, ) # botocore converts keys to str, so make sure that they are in # the expected case. See detailed discussion here: # https://github.com/aio-libs/aiobotocore/pull/116 # aiohttp's CIMultiDict camel cases the headers :( headers = { k.decode('utf-8').lower(): v.decode('utf-8') for k, v in response.raw_headers } http_response = aiobotocore.awsrequest.AioAWSResponse( str(response.url), response.status, headers, response ) if not request.stream_output: # Cause the raw stream to be exhausted immediately. We do it # this way instead of using preload_content because # preload_content will never buffer chunked responses await http_response.content return http_response except ClientSSLError as e: raise SSLError(endpoint_url=request.url, error=e) except (ClientProxyConnectionError, ClientHttpProxyError) as e: raise ProxyConnectionError( proxy_url=mask_proxy_url(proxy_url), error=e ) except ( ServerDisconnectedError, aiohttp.ClientPayloadError, aiohttp.http_exceptions.BadStatusLine, ) as e: raise ConnectionClosedError( error=e, request=request, endpoint_url=request.url ) except ServerTimeoutError as e: if str(e).lower().startswith('connect'): raise ConnectTimeoutError(endpoint_url=request.url, error=e) else: raise ReadTimeoutError(endpoint_url=request.url, error=e) except ( ClientConnectorError, ClientConnectionError, socket.gaierror, ) as e: raise EndpointConnectionError(endpoint_url=request.url, error=e) except asyncio.TimeoutError as e: raise ReadTimeoutError(endpoint_url=request.url, error=e) except CancelledError: raise except Exception as e: message = 'Exception received when sending urllib3 HTTP request' logger.debug(message, exc_info=True) raise HTTPClientError(error=e) aio-libs-aiobotocore-e39c8b3/aiobotocore/httpxsession.py000066400000000000000000000234341510465277300236100ustar00rootroot00000000000000from __future__ import annotations import asyncio import io import os import socket import ssl from collections.abc import AsyncIterable, Iterable from concurrent.futures import CancelledError from typing import TYPE_CHECKING, Any, cast import botocore from botocore.awsrequest import AWSPreparedRequest from botocore.httpsession import ( MAX_POOL_CONNECTIONS, ConnectionClosedError, ConnectTimeoutError, EndpointConnectionError, HTTPClientError, ProxyConnectionError, ReadTimeoutError, create_urllib3_context, ensure_boolean, get_cert_path, logger, ) from multidict import CIMultiDict import aiobotocore.awsrequest from aiobotocore._endpoint_helpers import _text from ._constants import DEFAULT_KEEPALIVE_TIMEOUT try: import httpx except ImportError: httpx = None if TYPE_CHECKING: from ssl import SSLContext class HttpxSession: def __init__( self, verify: bool = True, proxies: dict[str, str] | None = None, # {scheme: url} timeout: float | list[float] | tuple[float, float] | None = None, max_pool_connections: int = MAX_POOL_CONNECTIONS, socket_options: list[Any] | None = None, client_cert: str | tuple[str, str] | None = None, proxies_config: dict[str, str] | None = None, connector_args: dict[str, Any] | None = None, ): if httpx is None: # pragma: no cover raise RuntimeError( "Using HttpxSession requires httpx to be installed" ) if proxies or proxies_config: raise NotImplementedError( "Proxy support not implemented with httpx as backend." ) if connector_args is None: self._connector_args: dict[str, Any] = { 'keepalive_timeout': DEFAULT_KEEPALIVE_TIMEOUT } else: self._connector_args = connector_args # TODO: neither this nor AIOHTTPSession handles socket_options self._session: httpx.AsyncClient | None = None conn_timeout: float | None read_timeout: float | None if isinstance(timeout, (list, tuple)): conn_timeout, read_timeout = timeout else: conn_timeout = read_timeout = timeout write_timeout = self._connector_args.get('write_timeout', 5) pool_timeout = self._connector_args.get('pool_timeout', 5) self._timeout = httpx.Timeout( connect=conn_timeout, read=read_timeout, write=write_timeout, pool=pool_timeout, ) self._cert_file = None self._key_file = None if isinstance(client_cert, str): self._cert_file = client_cert elif isinstance(client_cert, tuple): self._cert_file, self._key_file = client_cert elif client_cert is not None: raise TypeError(f'{client_cert} must be str or tuple[str,str]') if 'use_dns_cache' in self._connector_args: raise NotImplementedError( "DNS caching is not implemented by httpx. https://github.com/encode/httpx/discussions/2211" ) if 'force_close' in self._connector_args: raise NotImplementedError("Not supported with httpx as backend.") if 'resolver' in self._connector_args: raise NotImplementedError("Not supported with httpx as backend.") self._max_pool_connections = max_pool_connections self._socket_options = socket_options if socket_options is None: self._socket_options = [] # TODO [httpx]: clean up ssl_context: SSLContext | None = None self._verify: bool | str | SSLContext = verify if not verify: return if 'ssl_context' in self._connector_args: self._verify = cast( 'SSLContext', self._connector_args['ssl_context'] ) return ssl_context = self._get_ssl_context() # inline self._setup_ssl_cert ca_certs = get_cert_path(verify) if ca_certs: ssl_context.load_verify_locations(ca_certs, None, None) if ssl_context is not None: self._verify = ssl_context async def __aenter__(self): assert not self._session limits = httpx.Limits( max_connections=self._max_pool_connections, keepalive_expiry=self._connector_args['keepalive_timeout'], ) # TODO [httpx]: I put logic here to minimize diff / accidental downstream # consequences - but can probably put this logic in __init__ if self._cert_file and self._key_file is None: cert = self._cert_file elif self._cert_file: cert = (self._cert_file, self._key_file) else: cert = None self._session = httpx.AsyncClient( timeout=self._timeout, limits=limits, cert=cert ) return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self._session: await self._session.__aexit__(exc_type, exc_val, exc_tb) self._session = None self._connector = None def _get_ssl_context(self) -> SSLContext: ssl_context = create_urllib3_context() if self._cert_file: ssl_context.load_cert_chain(self._cert_file, self._key_file) return ssl_context async def close(self) -> None: await self.__aexit__(None, None, None) async def send( self, request: AWSPreparedRequest ) -> aiobotocore.awsrequest.HttpxAWSResponse: try: url = request.url headers = request.headers # currently no support for BOTO_EXPERIMENTAL__ADD_PROXY_HOST_HEADER if ensure_boolean( os.environ.get('BOTO_EXPERIMENTAL__ADD_PROXY_HOST_HEADER', '') ): raise NotImplementedError( 'httpx implementation of aiobotocore does not (currently) support proxies' ) headers_ = CIMultiDict( (z[0], _text(z[1], encoding='utf-8')) for z in headers.items() ) # https://github.com/boto/botocore/issues/1255 headers_['Accept-Encoding'] = 'identity' # content can also be https://github.com/ymyzk/tox-gh-actions content: AsyncIterable | bytes | bytearray | str | None = None async def to_async_iterable(stream: Iterable) -> AsyncIterable: if isinstance(stream, AsyncIterable): async for item in stream: yield item else: for item in stream: yield item await asyncio.sleep(0) # Yield control to event loop if isinstance( request.body, (AsyncIterable, io.BytesIO) ) and isinstance(request.body, Iterable): content = to_async_iterable(request.body) else: content = request.body # The target gets used as the HTTP target instead of the URL path # it does not get normalized or otherwise processed, which is important # since arbitrary dots and slashes are valid as key paths. # See test_basic_s3.test_non_normalized_key_paths # This way of using it is currently ~undocumented, but recommended in # https://github.com/encode/httpx/discussions/1805#discussioncomment-8975989 extensions = {"target": bytes(url, encoding='utf-8')} assert self._session is not None httpx_request = self._session.build_request( method=request.method, url=url, headers=headers, content=content, extensions=extensions, ) assert isinstance(httpx_request.stream, httpx.AsyncByteStream) # auth, follow_redirects response = await self._session.send(httpx_request, stream=True) response_headers = botocore.compat.HTTPHeaders.from_pairs( response.headers.items() ) http_response = aiobotocore.awsrequest.HttpxAWSResponse( str(response.url), response.status_code, response_headers, response, ) if not request.stream_output: # Cause the raw stream to be exhausted immediately. We do it # this way instead of using preload_content because # preload_content will never buffer chunked responses await http_response.content return http_response except httpx.ConnectError as e: raise EndpointConnectionError(endpoint_url=request.url, error=e) except (socket.gaierror,) as e: raise EndpointConnectionError(endpoint_url=request.url, error=e) except asyncio.TimeoutError as e: raise ReadTimeoutError(endpoint_url=request.url, error=e) except httpx.ReadTimeout as e: raise ReadTimeoutError(endpoint_url=request.url, error=e) except httpx.TimeoutException as e: raise ConnectTimeoutError(endpoint_url=request.url, error=e) except httpx.ProxyError as e: raise ProxyConnectionError(endpoint_url=request.url, error=e) except httpx.CloseError as e: raise ConnectionClosedError(endpoint_url=request.url, error=e) except ssl.SSLError: raise botocore.exceptions.SSLError except NotImplementedError: raise # Avoid turning it into HTTPClientError. except CancelledError: raise except Exception as e: message = 'Exception received when sending httpx HTTP request' logger.debug(message, exc_info=True) raise HTTPClientError(error=e) aio-libs-aiobotocore-e39c8b3/aiobotocore/paginate.py000066400000000000000000000201541510465277300226210ustar00rootroot00000000000000from functools import partial import aioitertools import jmespath from botocore.exceptions import PaginationError from botocore.paginate import PageIterator, Paginator from botocore.useragent import register_feature_id from botocore.utils import merge_dicts, set_value_from_jmespath from .context import with_current_context class AioPageIterator(PageIterator): def __aiter__(self): return self.__anext__() @with_current_context(partial(register_feature_id, 'PAGINATOR')) async def _make_request(self, current_kwargs): return await self._method(**current_kwargs) async def __anext__(self): current_kwargs = self._op_kwargs previous_next_token = None next_token = {key: None for key in self._input_token} if self._starting_token is not None: # If the starting token exists, populate the next_token with the # values inside it. This ensures that we have the service's # pagination token on hand if we need to truncate after the # first response. next_token = self._parse_starting_token()[0] # The number of items from result_key we've seen so far. total_items = 0 first_request = True primary_result_key = self.result_keys[0] starting_truncation = 0 self._inject_starting_params(current_kwargs) while True: response = await self._make_request(current_kwargs) parsed = self._extract_parsed_response(response) if first_request: # The first request is handled differently. We could # possibly have a resume/starting token that tells us where # to index into the retrieved page. if self._starting_token is not None: starting_truncation = self._handle_first_request( parsed, primary_result_key, starting_truncation ) first_request = False self._record_non_aggregate_key_values(parsed) else: # If this isn't the first request, we have already sliced into # the first request and had to make additional requests after. # We no longer need to add this to truncation. starting_truncation = 0 current_response = primary_result_key.search(parsed) if current_response is None: current_response = [] num_current_response = len(current_response) truncate_amount = 0 if self._max_items is not None: truncate_amount = ( total_items + num_current_response - self._max_items ) if truncate_amount > 0: self._truncate_response( parsed, primary_result_key, truncate_amount, starting_truncation, next_token, ) yield response break else: yield response total_items += num_current_response next_token = self._get_next_token(parsed) if all(t is None for t in next_token.values()): break if ( self._max_items is not None and total_items == self._max_items ): # We're on a page boundary so we can set the current # next token to be the resume token. self.resume_token = next_token break if ( previous_next_token is not None and previous_next_token == next_token ): message = ( f"The same next token was received twice: {next_token}" ) raise PaginationError(message=message) self._inject_token_into_kwargs(current_kwargs, next_token) previous_next_token = next_token async def search(self, expression): compiled = jmespath.compile(expression) async for page in self: results = compiled.search(page) if isinstance(results, list): for element in results: yield element # unfortunately yield from not avail from async f else: yield results def result_key_iters(self): teed_results = aioitertools.tee(self, len(self.result_keys)) return [ ResultKeyIterator(i, result_key) for i, result_key in zip(teed_results, self.result_keys) ] async def build_full_result(self): complete_result = {} async for response in self: page = response # We want to try to catch operation object pagination # and format correctly for those. They come in the form # of a tuple of two elements: (http_response, parsed_responsed). # We want the parsed_response as that is what the page iterator # uses. We can remove it though once operation objects are removed. if isinstance(response, tuple) and len(response) == 2: page = response[1] # We're incrementally building the full response page # by page. For each page in the response we need to # inject the necessary components from the page # into the complete_result. for result_expression in self.result_keys: # In order to incrementally update a result key # we need to search the existing value from complete_result, # then we need to search the _current_ page for the # current result key value. Then we append the current # value onto the existing value, and re-set that value # as the new value. result_value = result_expression.search(page) if result_value is None: continue existing_value = result_expression.search(complete_result) if existing_value is None: # Set the initial result set_value_from_jmespath( complete_result, result_expression.expression, result_value, ) continue # Now both result_value and existing_value contain something if isinstance(result_value, list): existing_value.extend(result_value) elif isinstance(result_value, (int, float, str)): # Modify the existing result with the sum or concatenation set_value_from_jmespath( complete_result, result_expression.expression, existing_value + result_value, ) merge_dicts(complete_result, self.non_aggregate_part) if self.resume_token is not None: complete_result['NextToken'] = self.resume_token return complete_result class AioPaginator(Paginator): PAGE_ITERATOR_CLS = AioPageIterator class ResultKeyIterator: """Iterates over the results of paginated responses. Each iterator is associated with a single result key. Iterating over this object will give you each element in the result key list. :param pages_iterator: An iterator that will give you pages of results (a ``PageIterator`` class). :param result_key: The JMESPath expression representing the result key. """ def __init__(self, pages_iterator, result_key): self._pages_iterator = pages_iterator self.result_key = result_key def __aiter__(self): return self.__anext__() async def __anext__(self): async for page in self._pages_iterator: results = self.result_key.search(page) if results is None: results = [] for result in results: yield result # yield from not avail from async func aio-libs-aiobotocore-e39c8b3/aiobotocore/parsers.py000066400000000000000000000150611510465277300225110ustar00rootroot00000000000000from botocore.parsers import ( LOG, BaseCBORParser, BaseEventStreamParser, BaseJSONParser, BaseRestParser, BaseRpcV2Parser, BaseXMLResponseParser, EC2QueryParser, EventStreamCBORParser, EventStreamJSONParser, EventStreamXMLParser, JSONParser, NoInitialResponseError, QueryParser, ResponseParser, ResponseParserError, ResponseParserFactory, RestJSONParser, RestXMLParser, RpcV2CBORParser, lowercase_dict, ) from ._helpers import resolve_awaitable from .eventstream import AioEventStream class AioResponseParserFactory(ResponseParserFactory): def create_parser(self, protocol_name): parser_cls = PROTOCOL_PARSERS[protocol_name] return parser_cls(**self._defaults) def create_parser(protocol): return AioResponseParserFactory().create_parser(protocol) class AioResponseParser(ResponseParser): async def parse(self, response, shape): LOG.debug('Response headers: %s', response['headers']) LOG.debug('Response body:\n%s', response['body']) if response['status_code'] >= 301: if self._is_generic_error_response(response): parsed = self._do_generic_error_parse(response) elif self._is_modeled_error_shape(shape): parsed = self._do_modeled_error_parse(response, shape) # We don't want to decorate the modeled fields with metadata return parsed else: parsed = self._do_error_parse(response, shape) else: parsed = await resolve_awaitable(self._do_parse(response, shape)) # We don't want to decorate event stream responses with metadata if shape and shape.serialization.get('eventstream'): return parsed # Add ResponseMetadata if it doesn't exist and inject the HTTP # status code and headers from the response. if isinstance(parsed, dict): response_metadata = parsed.get('ResponseMetadata', {}) response_metadata['HTTPStatusCode'] = response['status_code'] # Ensure that the http header keys are all lower cased. Older # versions of urllib3 (< 1.11) would unintentionally do this for us # (see urllib3#633). We need to do this conversion manually now. headers = response['headers'] response_metadata['HTTPHeaders'] = lowercase_dict(headers) parsed['ResponseMetadata'] = response_metadata self._add_checksum_response_metadata(response, response_metadata) return parsed def _create_event_stream(self, response, shape): parser = self._event_stream_parser name = response['context'].get('operation_name') return AioEventStream(response['body'], shape, parser, name) class AioBaseXMLResponseParser(BaseXMLResponseParser, AioResponseParser): pass class AioQueryParser(QueryParser, AioBaseXMLResponseParser): pass class AioEC2QueryParser(EC2QueryParser, AioQueryParser): pass class AioBaseJSONParser(BaseJSONParser, AioResponseParser): pass class AioBaseCBORParser(BaseCBORParser, AioResponseParser): pass class AioBaseEventStreamParser(BaseEventStreamParser, AioResponseParser): pass class AioEventStreamJSONParser( EventStreamJSONParser, AioBaseEventStreamParser, AioBaseJSONParser ): pass class AioEventStreamXMLParser( EventStreamXMLParser, AioBaseEventStreamParser, AioBaseXMLResponseParser ): pass class AioEventStreamCBORParser( EventStreamCBORParser, AioBaseEventStreamParser, AioBaseCBORParser ): pass class AioJSONParser(JSONParser, AioBaseJSONParser): EVENT_STREAM_PARSER_CLS = AioEventStreamJSONParser async def _do_parse(self, response, shape): parsed = {} if shape is not None: event_name = shape.event_stream_name if event_name: parsed = await self._handle_event_stream( response, shape, event_name ) else: parsed = self._handle_json_body(response['body'], shape) self._inject_response_metadata(parsed, response['headers']) return parsed async def _handle_event_stream(self, response, shape, event_name): event_stream_shape = shape.members[event_name] event_stream = self._create_event_stream(response, event_stream_shape) try: event = await event_stream.get_initial_response() except NoInitialResponseError: error_msg = 'First event was not of type initial-response' raise ResponseParserError(error_msg) parsed = self._handle_json_body(event.payload, shape) parsed[event_name] = event_stream return parsed class AioBaseRestParser(BaseRestParser, AioResponseParser): pass class AioBaseRpcV2Parser(BaseRpcV2Parser, AioResponseParser): async def _do_parse(self, response, shape): parsed = {} if shape is not None: event_stream_name = shape.event_stream_name if event_stream_name: parsed = await self._handle_event_stream( response, shape, event_stream_name ) else: parsed = {} self._parse_payload(response, shape, parsed) parsed['ResponseMetadata'] = self._populate_response_metadata( response ) return parsed class AioRestJSONParser(RestJSONParser, AioBaseRestParser, AioBaseJSONParser): EVENT_STREAM_PARSER_CLS = AioEventStreamJSONParser class AioRpcV2CBORParser( RpcV2CBORParser, AioBaseRpcV2Parser, AioBaseCBORParser ): EVENT_STREAM_PARSER_CLS = AioEventStreamCBORParser async def _handle_event_stream(self, response, shape, event_name): event_stream_shape = shape.members[event_name] event_stream = self._create_event_stream(response, event_stream_shape) try: event = await event_stream.get_initial_response() except NoInitialResponseError: error_msg = 'First event was not of type initial-response' raise ResponseParserError(error_msg) parsed = self._initial_body_parse(event.payload) parsed[event_name] = event_stream return parsed class AioRestXMLParser( RestXMLParser, AioBaseRestParser, AioBaseXMLResponseParser ): EVENT_STREAM_PARSER_CLS = AioEventStreamXMLParser PROTOCOL_PARSERS = { 'ec2': AioEC2QueryParser, 'query': AioQueryParser, 'json': AioJSONParser, 'rest-json': AioRestJSONParser, 'rest-xml': AioRestXMLParser, 'smithy-rpc-v2-cbor': AioRpcV2CBORParser, } aio-libs-aiobotocore-e39c8b3/aiobotocore/regions.py000066400000000000000000000102761510465277300225030ustar00rootroot00000000000000import copy import logging from botocore.exceptions import EndpointProviderError from botocore.regions import EndpointRulesetResolver from aiobotocore._helpers import resolve_awaitable LOG = logging.getLogger(__name__) class AioEndpointRulesetResolver(EndpointRulesetResolver): async def construct_endpoint( self, operation_model, call_args, request_context, ): """Invokes the provider with params defined in the service's ruleset""" if call_args is None: call_args = {} if request_context is None: request_context = {} provider_params = await self._get_provider_params( operation_model, call_args, request_context ) LOG.debug( 'Calling endpoint provider with parameters: %s', provider_params ) try: provider_result = self._provider.resolve_endpoint( **provider_params ) except EndpointProviderError as ex: botocore_exception = self.ruleset_error_to_botocore_exception( ex, provider_params ) if botocore_exception is None: raise else: raise botocore_exception from ex LOG.debug('Endpoint provider result: %s', provider_result.url) # The endpoint provider does not support non-secure transport. if ( not self._use_ssl and provider_result.url.startswith('https://') and 'Endpoint' not in provider_params ): provider_result = provider_result._replace( url=f'http://{provider_result.url[8:]}' ) # Multi-valued headers are not supported in botocore. Replace the list # of values returned for each header with just its first entry, # dropping any additionally entries. provider_result = provider_result._replace( headers={ key: val[0] for key, val in provider_result.headers.items() } ) return provider_result async def _get_provider_params( self, operation_model, call_args, request_context ): """Resolve a value for each parameter defined in the service's ruleset The resolution order for parameter values is: 1. Operation-specific static context values from the service definition 2. Operation-specific dynamic context values from API parameters 3. Client-specific context parameters 4. Built-in values such as region, FIPS usage, ... """ provider_params = {} # Builtin values can be customized for each operation by hooks # subscribing to the ``before-endpoint-resolution.*`` event. customized_builtins = await self._get_customized_builtins( operation_model, call_args, request_context ) for param_name, param_def in self._param_definitions.items(): param_val = self._resolve_param_from_context( param_name=param_name, operation_model=operation_model, call_args=call_args, ) if param_val is None and param_def.builtin is not None: param_val = await resolve_awaitable( self._resolve_param_as_builtin( builtin_name=param_def.builtin, builtins=customized_builtins, ) ) if param_val is not None: provider_params[param_name] = param_val self._register_endpoint_feature_ids(param_name, param_val) return provider_params async def _get_customized_builtins( self, operation_model, call_args, request_context ): service_id = self._service_model.service_id.hyphenize() customized_builtins = copy.copy(self._builtins) # Handlers are expected to modify the builtins dict in place. await self._event_emitter.emit( f'before-endpoint-resolution.{service_id}', builtins=customized_builtins, model=operation_model, params=call_args, context=request_context, ) return customized_builtins aio-libs-aiobotocore-e39c8b3/aiobotocore/response.py000066400000000000000000000160501510465277300226670ustar00rootroot00000000000000import asyncio import aiohttp import aiohttp.client_exceptions import wrapt from botocore.response import ( IncompleteReadError, ReadTimeoutError, ResponseStreamingError, ) from aiobotocore import parsers class AioReadTimeoutError(ReadTimeoutError, asyncio.TimeoutError): pass class StreamingBody(wrapt.ObjectProxy): """Wrapper class for an http response body. This provides a few additional conveniences that do not exist in the urllib3 model: * Auto validation of content length, if the amount of bytes we read does not match the content length, an exception is raised. """ _DEFAULT_CHUNK_SIZE = 1024 def __init__( self, raw_stream: aiohttp.ClientResponse, content_length: str ): super().__init__(raw_stream) self.__wrapped__: aiohttp.ClientResponse self._self_content_length = content_length self._self_amount_read = 0 # https://github.com/GrahamDumpleton/wrapt/issues/73 async def __aenter__(self): return await self.__wrapped__.__aenter__() async def __aexit__(self, exc_type, exc_val, exc_tb): return await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb) # NOTE: set_socket_timeout was only for when requests didn't support # read timeouts, so not needed def readable(self): return not self.at_eof() async def read(self, amt=None): """Read at most amt bytes from the stream. If the amt argument is omitted, read all data. """ # botocore to aiohttp mapping try: chunk = await self.__wrapped__.content.read( amt if amt is not None else -1 ) except asyncio.TimeoutError as e: raise AioReadTimeoutError( endpoint_url=self.__wrapped__.url, error=e ) except aiohttp.client_exceptions.ClientConnectionError as e: raise ResponseStreamingError(error=e) self._self_amount_read += len(chunk) if amt is None or (not chunk and amt > 0): # If the server sends empty contents or # we ask to read all of the contents, then we know # we need to verify the content length. self._verify_content_length() return chunk async def readinto(self, b: bytearray): """Read bytes into a pre-allocated, writable bytes-like object b, and return the number of bytes read.""" try: # AioHTTP ClientResponse does not support .readinto() chunk = await self.__wrapped__.content.read(len(b)) amount_read = len(chunk) b[:amount_read] = chunk except asyncio.TimeoutError as e: raise AioReadTimeoutError( endpoint_url=self.__wrapped__.url, error=e ) except aiohttp.client_exceptions.ClientConnectionError as e: raise ResponseStreamingError(error=e) self._self_amount_read += amount_read if amount_read == 0 and len(b) > 0: # If the server sends empty contents then we know we need to verify # the content length. self._verify_content_length() return amount_read async def readlines(self): # assuming this is not an iterator lines = [line async for line in self.iter_lines()] return lines def __aiter__(self): """Return an iterator to yield 1k chunks from the raw stream.""" return self.iter_chunks(self._DEFAULT_CHUNK_SIZE) async def __anext__(self): """Return the next 1k chunk from the raw stream.""" current_chunk = await self.read(self._DEFAULT_CHUNK_SIZE) if current_chunk: return current_chunk raise StopAsyncIteration anext = __anext__ async def iter_lines(self, chunk_size=_DEFAULT_CHUNK_SIZE, keepends=False): """Return an iterator to yield lines from the raw stream. This is achieved by reading chunk of bytes (of size chunk_size) at a time from the raw stream, and then yielding lines from there. """ pending = b'' async for chunk in self.iter_chunks(chunk_size): lines = (pending + chunk).splitlines(True) for line in lines[:-1]: yield line.splitlines(keepends)[0] pending = lines[-1] if pending: yield pending.splitlines(keepends)[0] async def iter_chunks(self, chunk_size=_DEFAULT_CHUNK_SIZE): """Return an iterator to yield chunks of chunk_size bytes from the raw stream. """ while True: current_chunk = await self.read(chunk_size) if current_chunk == b"": break yield current_chunk def _verify_content_length(self): # See: https://github.com/kennethreitz/requests/issues/1855 # Basically, our http library doesn't do this for us, so we have # to do this our self. if ( self._self_content_length is not None and self._self_amount_read != int(self._self_content_length) ): raise IncompleteReadError( actual_bytes=self._self_amount_read, expected_bytes=int(self._self_content_length), ) def tell(self): return self._self_amount_read # wraps httpx.Response class HttpxStreamingBody(wrapt.ObjectProxy): async def read(self, amt=None): if amt is not None: # We could do a fancy thing here and start doing calls to # aiter_bytes()/aiter_raw() and keep state raise ValueError( "httpx.Response.aread does not support reading a specific number of bytes" ) return await self.__wrapped__.aread() async def __aenter__(self): # use AsyncClient.stream somehow? # See "manual mode" at https://www.python-httpx.org/async/#streaming-responses return self async def __aexit__(self, exc_type, exc_val, exc_tb): # When support for anyio/trio is added this needs a shielded cancelscope to # avoid swallowing exceptions. # See https://github.com/python-trio/trio/issues/455 await self.__wrapped__.aclose() async def get_response(operation_model, http_response): protocol = operation_model.service_model.resolved_protocol response_dict = { 'headers': http_response.headers, 'status_code': http_response.status_code, } # TODO: Unfortunately, we have to have error logic here. # If it looks like an error, in the streaming response case we # need to actually grab the contents. if response_dict['status_code'] >= 300: response_dict['body'] = await http_response.content elif operation_model.has_streaming_output: response_dict['body'] = StreamingBody( http_response.raw, response_dict['headers'].get('content-length') ) else: response_dict['body'] = await http_response.content parser = parsers.create_parser(protocol) parsed = await parser.parse(response_dict, operation_model.output_shape) return http_response, parsed aio-libs-aiobotocore-e39c8b3/aiobotocore/retries/000077500000000000000000000000001510465277300221325ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/aiobotocore/retries/__init__.py000066400000000000000000000000001510465277300242310ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/aiobotocore/retries/adaptive.py000066400000000000000000000067661510465277300243200ustar00rootroot00000000000000"""An async reimplementation of the blocking elements from botocore.retries.adaptive.""" import asyncio import logging from botocore.retries import standard, throttling # The RateClocker from botocore uses a threading.Lock, but in a single-threaded asyncio # program, the lock will be acquired then released by the same coroutine without # blocking. from botocore.retries.adaptive import RateClocker from . import bucket logger = logging.getLogger(__name__) def register_retry_handler(client): clock = bucket.Clock() rate_adjustor = throttling.CubicCalculator( starting_max_rate=0, start_time=clock.current_time() ) token_bucket = bucket.AsyncTokenBucket(max_rate=1, clock=clock) rate_clocker = RateClocker(clock) throttling_detector = standard.ThrottlingErrorDetector( retry_event_adapter=standard.RetryEventAdapter(), ) limiter = AsyncClientRateLimiter( rate_adjustor=rate_adjustor, rate_clocker=rate_clocker, token_bucket=token_bucket, throttling_detector=throttling_detector, clock=clock, ) client.meta.events.register( 'before-send', limiter.on_sending_request, ) client.meta.events.register( 'needs-retry', limiter.on_receiving_response, ) return limiter class AsyncClientRateLimiter: """An async reimplementation of ClientRateLimiter.""" # Most of the code here comes directly from botocore. The main change is making the # callbacks async. # This doesn't inherit from the botocore ClientRateLimiter for two reasons: # * the interface is slightly changed (methods are now async) # * we rewrote the entirety of the class anyway _MAX_RATE_ADJUST_SCALE = 2.0 def __init__( self, rate_adjustor, rate_clocker, token_bucket, throttling_detector, clock, ): self._rate_adjustor = rate_adjustor self._rate_clocker = rate_clocker self._token_bucket = token_bucket self._throttling_detector = throttling_detector self._clock = clock self._enabled = False self._lock = asyncio.Lock() async def on_sending_request(self, request, **kwargs): if self._enabled: await self._token_bucket.acquire() # Hooked up to needs-retry. async def on_receiving_response(self, **kwargs): measured_rate = self._rate_clocker.record() timestamp = self._clock.current_time() async with self._lock: if not self._throttling_detector.is_throttling_error(**kwargs): new_rate = self._rate_adjustor.success_received(timestamp) else: if not self._enabled: rate_to_use = measured_rate else: rate_to_use = min( measured_rate, self._token_bucket.max_rate ) new_rate = self._rate_adjustor.error_received( rate_to_use, timestamp ) logger.debug( "Throttling response received, new send rate: %s " "measured rate: %s, token bucket capacity " "available: %s", new_rate, measured_rate, self._token_bucket.available_capacity, ) self._enabled = True await self._token_bucket.set_max_rate( min(new_rate, self._MAX_RATE_ADJUST_SCALE * measured_rate) ) aio-libs-aiobotocore-e39c8b3/aiobotocore/retries/bucket.py000066400000000000000000000103121510465277300237560ustar00rootroot00000000000000"""An async reimplementation of the blocking elements from botocore.retries.bucket.""" import asyncio from botocore.exceptions import CapacityNotAvailableError from botocore.retries.bucket import Clock as Clock # reexport # noqa class AsyncTokenBucket: """A reimplementation of TokenBucket that doesn't block.""" # Most of the code here is pulled straight up from botocore, with slight changes # to the interface to switch to async methods. # This class doesn't inherit from the botocore TokenBucket, as the interface is # different: the `max_rate` setter in the original class is replaced by the # async `set_max_rate`. # (a Python setter can't be async). _MIN_RATE = 0.5 def __init__(self, max_rate, clock, min_rate=_MIN_RATE): self._fill_rate = None self._max_capacity = None self._current_capacity = 0 self._clock = clock self._last_timestamp = None self._min_rate = min_rate self._set_max_rate(max_rate) # The main difference between this implementation and the botocore TokenBucket # implementation is replacing a threading.Condition by this asyncio.Condition. self._new_fill_rate_condition = asyncio.Condition() @property def max_rate(self): return self._fill_rate async def set_max_rate(self, value): async with self._new_fill_rate_condition: self._set_max_rate(value) self._new_fill_rate_condition.notify() def _set_max_rate(self, value): # Before we can change the rate we need to fill any pending # tokens we might have based on the current rate. If we don't # do this it means everything since the last recorded timestamp # will accumulate at the rate we're about to set which isn't # correct. self._refill() self._fill_rate = max(value, self._min_rate) if value >= 1: self._max_capacity = value else: self._max_capacity = 1 # If we're scaling down, we also can't have a capacity that's # more than our max_capacity. self._current_capacity = min( self._current_capacity, self._max_capacity ) @property def max_capacity(self): return self._max_capacity @property def available_capacity(self): return self._current_capacity async def acquire(self, amount=1, block=True): """Acquire token or return amount of time until next token available. If block is True, then this method will return when there's sufficient capacity to acquire the desired amount. This won't block the event loop. If block is False, then this method will return True if capacity was successfully acquired, False otherwise. """ async with self._new_fill_rate_condition: return await self._acquire(amount=amount, block=block) async def _acquire(self, amount, block): self._refill() if amount <= self._current_capacity: self._current_capacity -= amount return True else: if not block: raise CapacityNotAvailableError() # Not enough capacity. sleep_amount = self._sleep_amount(amount) while sleep_amount > 0: try: await asyncio.wait_for( self._new_fill_rate_condition.wait(), sleep_amount ) except asyncio.TimeoutError: pass self._refill() sleep_amount = self._sleep_amount(amount) self._current_capacity -= amount return True def _sleep_amount(self, amount): return (amount - self._current_capacity) / self._fill_rate def _refill(self): timestamp = self._clock.current_time() if self._last_timestamp is None: self._last_timestamp = timestamp return current_capacity = self._current_capacity fill_amount = (timestamp - self._last_timestamp) * self._fill_rate new_capacity = min(self._max_capacity, current_capacity + fill_amount) self._current_capacity = new_capacity self._last_timestamp = timestamp aio-libs-aiobotocore-e39c8b3/aiobotocore/retries/special.py000066400000000000000000000015351510465277300241300ustar00rootroot00000000000000from botocore.retries.special import RetryDDBChecksumError, crc32, logger class AioRetryDDBChecksumError(RetryDDBChecksumError): async def is_retryable(self, context): service_name = context.operation_model.service_model.service_name if service_name != self._SERVICE_NAME: return False if context.http_response is None: return False checksum = context.http_response.headers.get(self._CHECKSUM_HEADER) if checksum is None: return False actual_crc32 = crc32(await context.http_response.content) & 0xFFFFFFFF if actual_crc32 != int(checksum): logger.debug( "DynamoDB crc32 checksum does not match, " "expected: %s, actual: %s", checksum, actual_crc32, ) return True aio-libs-aiobotocore-e39c8b3/aiobotocore/retries/standard.py000066400000000000000000000073541510465277300243150ustar00rootroot00000000000000from botocore.retries.standard import ( DEFAULT_MAX_ATTEMPTS, ExponentialBackoff, MaxAttemptsChecker, ModeledRetryableChecker, OrRetryChecker, RetryEventAdapter, RetryHandler, RetryPolicy, RetryQuotaChecker, StandardRetryConditions, ThrottledRetryableChecker, TransientRetryableChecker, logger, quota, special, ) from .._helpers import async_any, resolve_awaitable from .special import AioRetryDDBChecksumError def register_retry_handler(client, max_attempts=DEFAULT_MAX_ATTEMPTS): retry_quota = RetryQuotaChecker(quota.RetryQuota()) service_id = client.meta.service_model.service_id service_event_name = service_id.hyphenize() client.meta.events.register( f'after-call.{service_event_name}', retry_quota.release_retry_quota ) handler = AioRetryHandler( retry_policy=AioRetryPolicy( retry_checker=AioStandardRetryConditions( max_attempts=max_attempts ), retry_backoff=ExponentialBackoff(), ), retry_event_adapter=RetryEventAdapter(), retry_quota=retry_quota, ) unique_id = f'retry-config-{service_event_name}' client.meta.events.register( f'needs-retry.{service_event_name}', handler.needs_retry, unique_id=unique_id, ) return handler class AioRetryHandler(RetryHandler): async def needs_retry(self, **kwargs): """Connect as a handler to the needs-retry event.""" retry_delay = None context = self._retry_event_adapter.create_retry_context(**kwargs) if await self._retry_policy.should_retry(context): # Before we can retry we need to ensure we have sufficient # capacity in our retry quota. if self._retry_quota.acquire_retry_quota(context): retry_delay = self._retry_policy.compute_retry_delay(context) logger.debug( "Retry needed, retrying request after delay of: %s", retry_delay, ) else: logger.debug( "Retry needed but retry quota reached, " "not retrying request." ) else: logger.debug("Not retrying request.") self._retry_event_adapter.adapt_retry_response_from_context(context) return retry_delay class AioRetryPolicy(RetryPolicy): async def should_retry(self, context): return await resolve_awaitable( self._retry_checker.is_retryable(context) ) class AioStandardRetryConditions(StandardRetryConditions): def __init__(self, max_attempts=DEFAULT_MAX_ATTEMPTS): # noqa: E501, lgtm [py/missing-call-to-init] # Note: This class is for convenience so you can have the # standard retry condition in a single class. self._max_attempts_checker = MaxAttemptsChecker(max_attempts) self._additional_checkers = AioOrRetryChecker( [ TransientRetryableChecker(), ThrottledRetryableChecker(), ModeledRetryableChecker(), AioOrRetryChecker( [ special.RetryIDPCommunicationError(), AioRetryDDBChecksumError(), ] ), ] ) async def is_retryable(self, context): return self._max_attempts_checker.is_retryable( context ) and await resolve_awaitable( self._additional_checkers.is_retryable(context) ) class AioOrRetryChecker(OrRetryChecker): async def is_retryable(self, context): return await async_any( checker.is_retryable(context) for checker in self._checkers ) aio-libs-aiobotocore-e39c8b3/aiobotocore/retryhandler.py000066400000000000000000000174331510465277300235420ustar00rootroot00000000000000from botocore.retryhandler import ( ChecksumError, CRC32Checker, ExceptionRaiser, HTTPStatusCodeChecker, MaxAttemptsDecorator, MultiChecker, RetryHandler, ServiceErrorCodeChecker, _extract_retryable_exception, crc32, create_retry_action_from_config, logger, ) from ._helpers import resolve_awaitable def create_retry_handler(config, operation_name=None): checker = create_checker_from_retry_config( config, operation_name=operation_name ) action = create_retry_action_from_config( config, operation_name=operation_name ) return AioRetryHandler(checker=checker, action=action) def create_checker_from_retry_config(config, operation_name=None): checkers = [] max_attempts = None retryable_exceptions = [] if '__default__' in config: policies = config['__default__'].get('policies', []) max_attempts = config['__default__']['max_attempts'] for key in policies: current_config = policies[key] checkers.append(_create_single_checker(current_config)) retry_exception = _extract_retryable_exception(current_config) if retry_exception is not None: retryable_exceptions.extend(retry_exception) if operation_name is not None and config.get(operation_name) is not None: operation_policies = config[operation_name]['policies'] for key in operation_policies: checkers.append(_create_single_checker(operation_policies[key])) retry_exception = _extract_retryable_exception( operation_policies[key] ) if retry_exception is not None: retryable_exceptions.extend(retry_exception) if len(checkers) == 1: # Don't need to use a MultiChecker return AioMaxAttemptsDecorator(checkers[0], max_attempts=max_attempts) else: multi_checker = AioMultiChecker(checkers) return AioMaxAttemptsDecorator( multi_checker, max_attempts=max_attempts, retryable_exceptions=tuple(retryable_exceptions), ) def _create_single_checker(config): if 'response' in config['applies_when']: return _create_single_response_checker( config['applies_when']['response'] ) elif 'socket_errors' in config['applies_when']: return ExceptionRaiser() def _create_single_response_checker(response): if 'service_error_code' in response: checker = ServiceErrorCodeChecker( status_code=response['http_status_code'], error_code=response['service_error_code'], ) elif 'http_status_code' in response: checker = HTTPStatusCodeChecker( status_code=response['http_status_code'] ) elif 'crc32body' in response: checker = AioCRC32Checker(header=response['crc32body']) else: # TODO: send a signal. raise ValueError("Unknown retry policy") return checker class AioRetryHandler(RetryHandler): async def _call(self, attempts, response, caught_exception, **kwargs): """Handler for a retry. Intended to be hooked up to an event handler (hence the **kwargs), this will process retries appropriately. """ checker_kwargs = { 'attempt_number': attempts, 'response': response, 'caught_exception': caught_exception, } if isinstance(self._checker, MaxAttemptsDecorator): retries_context = kwargs['request_dict']['context'].get('retries') checker_kwargs.update({'retries_context': retries_context}) if await resolve_awaitable(self._checker(**checker_kwargs)): result = self._action(attempts=attempts) logger.debug("Retry needed, action of: %s", result) return result logger.debug("No retry needed.") def __call__(self, *args, **kwargs): return self._call(*args, **kwargs) # return awaitable class AioMaxAttemptsDecorator(MaxAttemptsDecorator): async def _call( self, attempt_number, response, caught_exception, retries_context ): if retries_context: retries_context['max'] = max( retries_context.get('max', 0), self._max_attempts ) should_retry = await self._should_retry( attempt_number, response, caught_exception ) if should_retry: if attempt_number >= self._max_attempts: # explicitly set MaxAttemptsReached if response is not None and 'ResponseMetadata' in response[1]: response[1]['ResponseMetadata']['MaxAttemptsReached'] = ( True ) logger.debug( "Reached the maximum number of retry attempts: %s", attempt_number, ) return False else: return should_retry else: return False def __call__(self, *args, **kwargs): return self._call(*args, **kwargs) async def _should_retry(self, attempt_number, response, caught_exception): if self._retryable_exceptions and attempt_number < self._max_attempts: try: return await resolve_awaitable( self._checker(attempt_number, response, caught_exception) ) except self._retryable_exceptions as e: logger.debug( "retry needed, retryable exception caught: %s", e, exc_info=True, ) return True else: # If we've exceeded the max attempts we just let the exception # propagate if one has occurred. return await resolve_awaitable( self._checker(attempt_number, response, caught_exception) ) class AioMultiChecker(MultiChecker): async def _call(self, attempt_number, response, caught_exception): for checker in self._checkers: checker_response = await resolve_awaitable( checker(attempt_number, response, caught_exception) ) if checker_response: return checker_response return False def __call__(self, *args, **kwargs): return self._call(*args, **kwargs) class AioCRC32Checker(CRC32Checker): async def _call(self, attempt_number, response, caught_exception): if response is not None: return await self._check_response(attempt_number, response) elif caught_exception is not None: return self._check_caught_exception( attempt_number, caught_exception ) else: raise ValueError("Both response and caught_exception are None.") def __call__(self, *args, **kwargs): return self._call(*args, **kwargs) async def _check_response(self, attempt_number, response): http_response = response[0] expected_crc = http_response.headers.get(self._header_name) if expected_crc is None: logger.debug( "crc32 check skipped, the %s header is not " "in the http response.", self._header_name, ) else: actual_crc32 = crc32(await response[0].content) & 0xFFFFFFFF if not actual_crc32 == int(expected_crc): logger.debug( "retry needed: crc32 check failed, expected != actual: " "%s != %s", int(expected_crc), actual_crc32, ) raise ChecksumError( checksum_type='crc32', expected_checksum=int(expected_crc), actual_checksum=actual_crc32, ) aio-libs-aiobotocore-e39c8b3/aiobotocore/session.py000066400000000000000000000243671510465277300225260ustar00rootroot00000000000000from botocore import UNSIGNED, translate from botocore import __version__ as botocore_version from botocore.context import get_context from botocore.exceptions import PartialCredentialsError from botocore.session import ( EVENT_ALIASES, ServiceModel, UnknownServiceError, copy, logger, ) from botocore.session import Session as _SyncSession from botocore.useragent import register_feature_id from . import __version__, retryhandler from .client import AioBaseClient, AioClientCreator from .configprovider import AioSmartDefaultsConfigStoreFactory from .context import with_current_context from .credentials import AioCredentials, create_credential_resolver from .hooks import AioHierarchicalEmitter from .parsers import AioResponseParserFactory from .tokens import create_token_resolver from .utils import AioIMDSRegionProvider class ClientCreatorContext: def __init__(self, coro): self._coro = coro self._client = None async def __aenter__(self) -> AioBaseClient: self._client = await self._coro return await self._client.__aenter__() async def __aexit__(self, exc_type, exc_val, exc_tb): await self._client.__aexit__(exc_type, exc_val, exc_tb) class AioSession(_SyncSession): # noinspection PyMissingConstructor def __init__( self, session_vars=None, event_hooks=None, include_builtin_handlers=True, profile=None, ): if event_hooks is None: event_hooks = AioHierarchicalEmitter() super().__init__( session_vars, event_hooks, include_builtin_handlers, profile ) self._set_user_agent_for_session() def _set_user_agent_for_session(self): # Mimic approach taken by AWS's aws-cli project # https://github.com/aws/aws-cli/blob/b862122c76a3f280ff34e93c9dcafaf964e7bf9b/awscli/clidriver.py#L84 self.user_agent_name = 'aiobotocore' self.user_agent_version = __version__ self.user_agent_extra = f'botocore/{botocore_version}' def _create_token_resolver(self): return create_token_resolver(self) def _create_credential_resolver(self): return create_credential_resolver( self, region_name=self._last_client_region_used ) def _register_smart_defaults_factory(self): def create_smart_defaults_factory(): default_config_resolver = self._get_internal_component( 'default_config_resolver' ) imds_region_provider = AioIMDSRegionProvider(session=self) return AioSmartDefaultsConfigStoreFactory( default_config_resolver, imds_region_provider ) self._internal_components.lazy_register_component( 'smart_defaults_factory', create_smart_defaults_factory ) def _register_response_parser_factory(self): self._components.register_component( 'response_parser_factory', AioResponseParserFactory() ) def set_credentials( self, access_key, secret_key, token=None, account_id=None ): self._credentials = AioCredentials( access_key, secret_key, token, account_id=account_id ) async def get_credentials(self): if self._credentials is None: self._credentials = await self._components.get_component( 'credential_provider' ).load_credentials() return self._credentials async def get_service_model(self, service_name, api_version=None): service_description = await self.get_service_data( service_name, api_version ) return ServiceModel(service_description, service_name=service_name) async def get_service_data(self, service_name, api_version=None): """ Retrieve the fully merged data associated with a service. """ data_path = service_name service_data = self.get_component('data_loader').load_service_model( data_path, type_name='service-2', api_version=api_version ) service_id = EVENT_ALIASES.get(service_name, service_name) await self._events.emit( f'service-data-loaded.{service_id}', service_data=service_data, service_name=service_name, session=self, ) return service_data def create_client(self, *args, **kwargs): return ClientCreatorContext(self._create_client(*args, **kwargs)) @with_current_context() async def _create_client( self, service_name, region_name=None, api_version=None, use_ssl=True, verify=None, endpoint_url=None, aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None, config=None, aws_account_id=None, ): default_client_config = self.get_default_client_config() # If a config is provided and a default config is set, then # use the config resulting from merging the two. if config is not None and default_client_config is not None: config = default_client_config.merge(config) # If a config was not provided then use the default # client config from the session elif default_client_config is not None: config = default_client_config region_name = self._resolve_region_name(region_name, config) # Figure out the verify value base on the various # configuration options. if verify is None: verify = self.get_config_variable('ca_bundle') if api_version is None: api_version = self.get_config_variable('api_versions').get( service_name, None ) loader = self.get_component('data_loader') event_emitter = self.get_component('event_emitter') response_parser_factory = self.get_component('response_parser_factory') if config is not None and config.signature_version is UNSIGNED: credentials = None elif ( aws_access_key_id is not None and aws_secret_access_key is not None ): credentials = AioCredentials( access_key=aws_access_key_id, secret_key=aws_secret_access_key, token=aws_session_token, account_id=aws_account_id, ) elif self._missing_cred_vars(aws_access_key_id, aws_secret_access_key): raise PartialCredentialsError( provider='explicit', cred_var=self._missing_cred_vars( aws_access_key_id, aws_secret_access_key ), ) else: if ignored_credentials := self._get_ignored_credentials( aws_session_token, aws_account_id ): logger.debug( "Ignoring the following credential-related values which were set without " "an access key id and secret key on the session or client: %s", ignored_credentials, ) credentials = await self.get_credentials() if getattr(credentials, 'method', None) == 'explicit': register_feature_id('CREDENTIALS_CODE') auth_token = self.get_auth_token() endpoint_resolver = self._get_internal_component('endpoint_resolver') exceptions_factory = self._get_internal_component('exceptions_factory') config_store = copy.copy(self.get_component('config_store')) user_agent_creator = self.get_component('user_agent_creator') # Session configuration values for the user agent string are applied # just before each client creation because they may have been modified # at any time between session creation and client creation. user_agent_creator.set_session_config( session_user_agent_name=self.user_agent_name, session_user_agent_version=self.user_agent_version, session_user_agent_extra=self.user_agent_extra, ) defaults_mode = self._resolve_defaults_mode(config, config_store) if defaults_mode != 'legacy': smart_defaults_factory = self._get_internal_component( 'smart_defaults_factory' ) await smart_defaults_factory.merge_smart_defaults( config_store, defaults_mode, region_name ) self._add_configured_endpoint_provider( client_name=service_name, config_store=config_store, ) user_agent_creator.set_client_features(get_context().features) client_creator = AioClientCreator( loader, endpoint_resolver, self.user_agent(), event_emitter, retryhandler, translate, response_parser_factory, exceptions_factory, config_store, user_agent_creator=user_agent_creator, auth_token_resolver=self.get_auth_token, ) client = await client_creator.create_client( service_name=service_name, region_name=region_name, is_secure=use_ssl, endpoint_url=endpoint_url, verify=verify, credentials=credentials, scoped_config=self.get_scoped_config(), client_config=config, api_version=api_version, auth_token=auth_token, ) monitor = self._get_internal_component('monitor') if monitor is not None: monitor.register(client.meta.events) self._register_client_plugins(client) return client async def get_available_regions( self, service_name, partition_name='aws', allow_non_regional=False ): resolver = self._get_internal_component('endpoint_resolver') results = [] try: service_data = await self.get_service_data(service_name) endpoint_prefix = service_data['metadata'].get( 'endpointPrefix', service_name ) results = resolver.get_available_endpoints( endpoint_prefix, partition_name, allow_non_regional ) except UnknownServiceError: pass return results def get_session(env_vars=None): """ Return a new session object. """ return AioSession(env_vars) aio-libs-aiobotocore-e39c8b3/aiobotocore/signers.py000066400000000000000000000434101510465277300225030ustar00rootroot00000000000000import datetime import botocore import botocore.auth from botocore.compat import get_current_datetime from botocore.exceptions import ParamValidationError, UnknownClientMethodError from botocore.signers import ( RequestSigner, S3PostPresigner, UnknownSignatureVersionError, UnsupportedSignatureVersionError, _should_use_global_endpoint, create_request_object, prepare_request_dict, ) from botocore.tokens import FrozenAuthToken from botocore.utils import ArnParser class AioRequestSigner(RequestSigner): async def handler(self, operation_name=None, request=None, **kwargs): # This is typically hooked up to the "request-created" event # from a client's event emitter. When a new request is created # this method is invoked to sign the request. # Don't call this method directly. return await self.sign(operation_name, request) async def sign( self, operation_name, request, region_name=None, signing_type='standard', expires_in=None, signing_name=None, ): explicit_region_name = region_name if region_name is None: region_name = self._region_name if signing_name is None: signing_name = self._signing_name signature_version = await self._choose_signer( operation_name, signing_type, request.context ) # Allow mutating request before signing await self._event_emitter.emit( f'before-sign.{self._service_id.hyphenize()}.{operation_name}', request=request, signing_name=signing_name, region_name=self._region_name, signature_version=signature_version, request_signer=self, operation_name=operation_name, ) if signature_version != botocore.UNSIGNED: kwargs = { 'signing_name': signing_name, 'region_name': region_name, 'signature_version': signature_version, } if expires_in is not None: kwargs['expires'] = expires_in signing_context = request.context.get('signing', {}) if not explicit_region_name and signing_context.get('region'): kwargs['region_name'] = signing_context['region'] if signing_context.get('signing_name'): kwargs['signing_name'] = signing_context['signing_name'] if signing_context.get('request_credentials'): kwargs['request_credentials'] = signing_context[ 'request_credentials' ] if signing_context.get('identity_cache') is not None: self._resolve_identity_cache( kwargs, signing_context['identity_cache'], signing_context['cache_key'], ) try: auth = await self.get_auth_instance(**kwargs) except UnknownSignatureVersionError as e: if signing_type != 'standard': raise UnsupportedSignatureVersionError( signature_version=signature_version ) else: raise e auth.add_auth(request) async def _choose_signer(self, operation_name, signing_type, context): signing_type_suffix_map = { 'presign-post': '-presign-post', 'presign-url': '-query', } suffix = signing_type_suffix_map.get(signing_type, '') # operation specific signing context takes precedent over client-level # defaults signature_version = context.get('auth_type') or self._signature_version signing = context.get('signing', {}) signing_name = signing.get('signing_name', self._signing_name) region_name = signing.get('region', self._region_name) if ( signature_version is not botocore.UNSIGNED and not signature_version.endswith(suffix) ): signature_version += suffix handler, response = await self._event_emitter.emit_until_response( f'choose-signer.{self._service_id.hyphenize()}.{operation_name}', signing_name=signing_name, region_name=region_name, signature_version=signature_version, context=context, ) if response is not None: signature_version = response # The suffix needs to be checked again in case we get an improper # signature version from choose-signer. if ( signature_version is not botocore.UNSIGNED and not signature_version.endswith(suffix) ): signature_version += suffix return signature_version async def get_auth_instance( self, signing_name, region_name, signature_version=None, request_credentials=None, **kwargs, ): if signature_version is None: signature_version = self._signature_version cls = botocore.auth.AUTH_TYPE_MAPS.get(signature_version) if cls is None: raise UnknownSignatureVersionError( signature_version=signature_version ) if cls.REQUIRES_TOKEN is True: if self._auth_token and not isinstance( self._auth_token, FrozenAuthToken ): frozen_token = await self._auth_token.get_frozen_token() else: frozen_token = self._auth_token auth = cls(frozen_token) return auth credentials = request_credentials or self._credentials if getattr(cls, "REQUIRES_IDENTITY_CACHE", None) is True: cache = kwargs["identity_cache"] key = kwargs["cache_key"] credentials = await cache.get_credentials(key) del kwargs["cache_key"] frozen_credentials = None if credentials is not None: frozen_credentials = await credentials.get_frozen_credentials() kwargs['credentials'] = frozen_credentials if cls.REQUIRES_REGION: if self._region_name is None: raise botocore.exceptions.NoRegionError() kwargs['region_name'] = region_name kwargs['service_name'] = signing_name auth = cls(**kwargs) return auth # Alias get_auth for backwards compatibility. get_auth = get_auth_instance async def generate_presigned_url( self, request_dict, operation_name, expires_in=3600, region_name=None, signing_name=None, ): request = create_request_object(request_dict) await self.sign( operation_name, request, region_name, 'presign-url', expires_in, signing_name, ) request.prepare() return request.url def add_generate_db_auth_token(class_attributes, **kwargs): class_attributes['generate_db_auth_token'] = generate_db_auth_token def add_dsql_generate_db_auth_token_methods(class_attributes, **kwargs): class_attributes['generate_db_connect_auth_token'] = ( dsql_generate_db_connect_auth_token ) class_attributes['generate_db_connect_admin_auth_token'] = ( dsql_generate_db_connect_admin_auth_token ) async def generate_db_auth_token( self, DBHostname, Port, DBUsername, Region=None ): """Generates an auth token used to connect to a db with IAM credentials. :type DBHostname: str :param DBHostname: The hostname of the database to connect to. :type Port: int :param Port: The port number the database is listening on. :type DBUsername: str :param DBUsername: The username to log in as. :type Region: str :param Region: The region the database is in. If None, the client region will be used. :return: A presigned url which can be used as an auth token. """ region = Region if region is None: region = self.meta.region_name params = { 'Action': 'connect', 'DBUser': DBUsername, } request_dict = { 'url_path': '/', 'query_string': '', 'headers': {}, 'body': params, 'method': 'GET', } # RDS requires that the scheme not be set when sent over. This can cause # issues when signing because the Python url parsing libraries follow # RFC 1808 closely, which states that a netloc must be introduced by `//`. # Otherwise the url is presumed to be relative, and thus the whole # netloc would be treated as a path component. To work around this we # introduce https here and remove it once we're done processing it. scheme = 'https://' endpoint_url = f'{scheme}{DBHostname}:{Port}' prepare_request_dict(request_dict, endpoint_url) presigned_url = await self._request_signer.generate_presigned_url( operation_name='connect', request_dict=request_dict, region_name=region, expires_in=900, signing_name='rds-db', ) return presigned_url[len(scheme) :] async def _dsql_generate_db_auth_token( self, Hostname, Action, Region=None, ExpiresIn=900 ): """Generate a DSQL database token for an arbitrary action. :type Hostname: str :param Hostname: The DSQL endpoint host name. :type Action: str :param Action: Action to perform on the cluster (DbConnectAdmin or DbConnect). :type Region: str :param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used. :type ExpiresIn: int :param ExpiresIn: The token expiry duration in seconds (default is 900 seconds). :return: A presigned url which can be used as an auth token. """ possible_actions = ("DbConnect", "DbConnectAdmin") if Action not in possible_actions: raise ParamValidationError( report=f"Received {Action} for action but expected one of: {', '.join(possible_actions)}" ) if Region is None: Region = self.meta.region_name request_dict = { 'url_path': '/', 'query_string': '', 'headers': {}, 'body': { 'Action': Action, }, 'method': 'GET', } scheme = 'https://' endpoint_url = f'{scheme}{Hostname}' prepare_request_dict(request_dict, endpoint_url) presigned_url = await self._request_signer.generate_presigned_url( operation_name=Action, request_dict=request_dict, region_name=Region, expires_in=ExpiresIn, signing_name='dsql', ) return presigned_url[len(scheme) :] async def dsql_generate_db_connect_auth_token( self, Hostname, Region=None, ExpiresIn=900 ): """Generate a DSQL database token for the "DbConnect" action. :type Hostname: str :param Hostname: The DSQL endpoint host name. :type Region: str :param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used. :type ExpiresIn: int :param ExpiresIn: The token expiry duration in seconds (default is 900 seconds). :return: A presigned url which can be used as an auth token. """ return await _dsql_generate_db_auth_token( self, Hostname, "DbConnect", Region, ExpiresIn ) async def dsql_generate_db_connect_admin_auth_token( self, Hostname, Region=None, ExpiresIn=900 ): """Generate a DSQL database token for the "DbConnectAdmin" action. :type Hostname: str :param Hostname: The DSQL endpoint host name. :type Region: str :param Region: The AWS region where the DSQL Cluster is hosted. If None, the client region will be used. :type ExpiresIn: int :param ExpiresIn: The token expiry duration in seconds (default is 900 seconds). :return: A presigned url which can be used as an auth token. """ return await _dsql_generate_db_auth_token( self, Hostname, "DbConnectAdmin", Region, ExpiresIn ) class AioS3PostPresigner(S3PostPresigner): async def generate_presigned_post( self, request_dict, fields=None, conditions=None, expires_in=3600, region_name=None, ): if fields is None: fields = {} if conditions is None: conditions = [] # Create the policy for the post. policy = {} # Create an expiration date for the policy datetime_now = get_current_datetime() expire_date = datetime_now + datetime.timedelta(seconds=expires_in) policy['expiration'] = expire_date.strftime(botocore.auth.ISO8601) # Append all of the conditions that the user supplied. policy['conditions'] = [] for condition in conditions: policy['conditions'].append(condition) # Store the policy and the fields in the request for signing request = create_request_object(request_dict) request.context['s3-presign-post-fields'] = fields request.context['s3-presign-post-policy'] = policy await self._request_signer.sign( 'PutObject', request, region_name, 'presign-post' ) # Return the url and the fields for th form to post. return {'url': request.url, 'fields': fields} def add_generate_presigned_url(class_attributes, **kwargs): class_attributes['generate_presigned_url'] = generate_presigned_url async def generate_presigned_url( self, ClientMethod, Params=None, ExpiresIn=3600, HttpMethod=None ): """Generate a presigned url given a client, its method, and arguments :type ClientMethod: string :param ClientMethod: The client method to presign for :type Params: dict :param Params: The parameters normally passed to ``ClientMethod``. :type ExpiresIn: int :param ExpiresIn: The number of seconds the presigned url is valid for. By default it expires in an hour (3600 seconds) :type HttpMethod: string :param HttpMethod: The http method to use on the generated url. By default, the http method is whatever is used in the method's model. :returns: The presigned url """ client_method = ClientMethod params = Params if params is None: params = {} expires_in = ExpiresIn http_method = HttpMethod context = { 'is_presign_request': True, 'use_global_endpoint': _should_use_global_endpoint(self), } request_signer = self._request_signer try: operation_name = self._PY_TO_OP_NAME[client_method] except KeyError: raise UnknownClientMethodError(method_name=client_method) operation_model = self.meta.service_model.operation_model(operation_name) params = await self._emit_api_params( api_params=params, operation_model=operation_model, context=context, ) bucket_is_arn = ArnParser.is_arn(params.get('Bucket', '')) ( endpoint_url, additional_headers, properties, ) = await self._resolve_endpoint_ruleset( operation_model, params, context, ignore_signing_region=(not bucket_is_arn), ) request_dict = await self._convert_to_request_dict( api_params=params, operation_model=operation_model, endpoint_url=endpoint_url, context=context, headers=additional_headers, set_user_agent_header=False, ) # Switch out the http method if user specified it. if http_method is not None: request_dict['method'] = http_method # Generate the presigned url. return await request_signer.generate_presigned_url( request_dict=request_dict, expires_in=expires_in, operation_name=operation_name, ) def add_generate_presigned_post(class_attributes, **kwargs): class_attributes['generate_presigned_post'] = generate_presigned_post async def generate_presigned_post( self, Bucket, Key, Fields=None, Conditions=None, ExpiresIn=3600 ): bucket = Bucket key = Key fields = Fields conditions = Conditions expires_in = ExpiresIn if fields is None: fields = {} else: fields = fields.copy() if conditions is None: conditions = [] context = { 'is_presign_request': True, 'use_global_endpoint': _should_use_global_endpoint(self), } post_presigner = AioS3PostPresigner(self._request_signer) # We choose the CreateBucket operation model because its url gets # serialized to what a presign post requires. operation_model = self.meta.service_model.operation_model('CreateBucket') params = await self._emit_api_params( api_params={'Bucket': bucket}, operation_model=operation_model, context=context, ) bucket_is_arn = ArnParser.is_arn(params.get('Bucket', '')) ( endpoint_url, additional_headers, properties, ) = await self._resolve_endpoint_ruleset( operation_model, params, context, ignore_signing_region=(not bucket_is_arn), ) request_dict = await self._convert_to_request_dict( api_params=params, operation_model=operation_model, endpoint_url=endpoint_url, context=context, headers=additional_headers, set_user_agent_header=False, ) # Append that the bucket name to the list of conditions. conditions.append({'bucket': bucket}) # If the key ends with filename, the only constraint that can be # imposed is if it starts with the specified prefix. if key.endswith('${filename}'): conditions.append(["starts-with", '$key', key[: -len('${filename}')]]) else: conditions.append({'key': key}) # Add the key to the fields. fields['key'] = key return await post_presigner.generate_presigned_post( request_dict=request_dict, fields=fields, conditions=conditions, expires_in=expires_in, ) aio-libs-aiobotocore-e39c8b3/aiobotocore/stub.py000066400000000000000000000102531510465277300220050ustar00rootroot00000000000000from botocore.stub import Stubber from .awsrequest import AioAWSResponse class AioStubber(Stubber): def _add_response(self, method, service_response, expected_params): if not hasattr(self.client, method): raise ValueError( f"Client {self.client.meta.service_model.service_name} " f"does not have method: {method}" ) # pragma: no cover # Create a successful http response http_response = AioAWSResponse(None, 200, {}, None) operation_name = self.client.meta.method_to_api_mapping.get(method) self._validate_operation_response(operation_name, service_response) # Add the service_response to the queue for returning responses response = { 'operation_name': operation_name, 'response': (http_response, service_response), 'expected_params': expected_params, } self._queue.append(response) def add_client_error( self, method, service_error_code='', service_message='', http_status_code=400, service_error_meta=None, expected_params=None, response_meta=None, modeled_fields=None, ): """ Adds a ``ClientError`` to the response queue. :param method: The name of the service method to return the error on. :type method: str :param service_error_code: The service error code to return, e.g. ``NoSuchBucket`` :type service_error_code: str :param service_message: The service message to return, e.g. 'The specified bucket does not exist.' :type service_message: str :param http_status_code: The HTTP status code to return, e.g. 404, etc :type http_status_code: int :param service_error_meta: Additional keys to be added to the service Error :type service_error_meta: dict :param expected_params: A dictionary of the expected parameters to be called for the provided service response. The parameters match the names of keyword arguments passed to that client call. If any of the parameters differ a ``StubResponseError`` is thrown. You can use stub.ANY to indicate a particular parameter to ignore in validation. :param response_meta: Additional keys to be added to the response's ResponseMetadata :type response_meta: dict :param modeled_fields: Additional keys to be added to the response based on fields that are modeled for the particular error code. These keys will be validated against the particular error shape designated by the error code. :type modeled_fields: dict """ http_response = AioAWSResponse(None, http_status_code, {}, None) # We don't look to the model to build this because the caller would # need to know the details of what the HTTP body would need to # look like. parsed_response = { 'ResponseMetadata': {'HTTPStatusCode': http_status_code}, 'Error': {'Message': service_message, 'Code': service_error_code}, } if service_error_meta is not None: parsed_response['Error'].update(service_error_meta) if response_meta is not None: parsed_response['ResponseMetadata'].update(response_meta) if modeled_fields is not None: service_model = self.client.meta.service_model shape = service_model.shape_for_error_code(service_error_code) self._validate_response(shape, modeled_fields) parsed_response.update(modeled_fields) operation_name = self.client.meta.method_to_api_mapping.get(method) # Note that we do not allow for expected_params while # adding errors into the queue yet. response = { 'operation_name': operation_name, 'response': (http_response, parsed_response), 'expected_params': expected_params, } self._queue.append(response) aio-libs-aiobotocore-e39c8b3/aiobotocore/tokens.py000066400000000000000000000140661510465277300223410ustar00rootroot00000000000000import asyncio import logging from datetime import timedelta import dateutil.parser from botocore import UNSIGNED from botocore.compat import total_seconds from botocore.exceptions import ClientError, TokenRetrievalError from botocore.tokens import ( DeferredRefreshableToken, FrozenAuthToken, ScopedEnvTokenProvider, SSOTokenProvider, TokenProviderChain, _utc_now, ) from aiobotocore.config import AioConfig from aiobotocore.utils import create_nested_client logger = logging.getLogger(__name__) def create_token_resolver(session): providers = [ ScopedEnvTokenProvider(session), AioSSOTokenProvider(session), ] return TokenProviderChain(providers=providers) class AioDeferredRefreshableToken(DeferredRefreshableToken): def __init__(self, method, refresh_using, time_fetcher=_utc_now): # noqa: E501, lgtm [py/missing-call-to-init] self._time_fetcher = time_fetcher self._refresh_using = refresh_using self.method = method # The frozen token is protected by this lock self._refresh_lock = asyncio.Lock() self._frozen_token = None self._next_refresh = None async def get_frozen_token(self): await self._refresh() return self._frozen_token async def _refresh(self): # If we don't need to refresh just return refresh_type = self._should_refresh() if not refresh_type: return None # Block for refresh if we're in the mandatory refresh window block_for_refresh = refresh_type == "mandatory" if block_for_refresh or not self._refresh_lock.locked(): async with self._refresh_lock: await self._protected_refresh() async def _protected_refresh(self): # This should only be called after acquiring the refresh lock # Another task may have already refreshed, double check refresh refresh_type = self._should_refresh() if not refresh_type: return None try: now = self._time_fetcher() self._next_refresh = now + timedelta(seconds=self._attempt_timeout) self._frozen_token = await self._refresh_using() except Exception: logger.warning( "Refreshing token failed during the %s refresh period.", refresh_type, exc_info=True, ) if refresh_type == "mandatory": # This refresh was mandatory, error must be propagated back raise if self._is_expired(): # Fresh credentials should never be expired raise TokenRetrievalError( provider=self.method, error_msg="Token has expired and refresh failed", ) class AioSSOTokenProvider(SSOTokenProvider): async def _attempt_create_token(self, token): async with self._client as client: response = await client.create_token( grantType=self._GRANT_TYPE, clientId=token["clientId"], clientSecret=token["clientSecret"], refreshToken=token["refreshToken"], ) expires_in = timedelta(seconds=response["expiresIn"]) new_token = { "startUrl": self._sso_config["sso_start_url"], "region": self._sso_config["sso_region"], "accessToken": response["accessToken"], "expiresAt": self._now() + expires_in, # Cache the registration alongside the token "clientId": token["clientId"], "clientSecret": token["clientSecret"], "registrationExpiresAt": token["registrationExpiresAt"], } if "refreshToken" in response: new_token["refreshToken"] = response["refreshToken"] logger.info("SSO Token refresh succeeded") return new_token async def _refresh_access_token(self, token): keys = ( "refreshToken", "clientId", "clientSecret", "registrationExpiresAt", ) missing_keys = [k for k in keys if k not in token] if missing_keys: msg = f"Unable to refresh SSO token: missing keys: {missing_keys}" logger.info(msg) return None expiry = dateutil.parser.parse(token["registrationExpiresAt"]) if total_seconds(expiry - self._now()) <= 0: logger.info("SSO token registration expired at %s", expiry) return None try: return await self._attempt_create_token(token) except ClientError: logger.warning("SSO token refresh attempt failed", exc_info=True) return None async def _refresher(self): start_url = self._sso_config["sso_start_url"] session_name = self._sso_config["session_name"] logger.info("Loading cached SSO token for %s", session_name) token_dict = self._token_loader(start_url, session_name=session_name) expiration = dateutil.parser.parse(token_dict["expiresAt"]) logger.debug("Cached SSO token expires at %s", expiration) remaining = total_seconds(expiration - self._now()) if remaining < self._REFRESH_WINDOW: new_token_dict = await self._refresh_access_token(token_dict) if new_token_dict is not None: token_dict = new_token_dict expiration = token_dict["expiresAt"] self._token_loader.save_token( start_url, token_dict, session_name=session_name ) return FrozenAuthToken( token_dict["accessToken"], expiration=expiration ) @property def _client(self): config = AioConfig( region_name=self._sso_config["sso_region"], signature_version=UNSIGNED, ) return create_nested_client(self._session, "sso-oidc", config=config) def load_token(self, **kwargs): if self._sso_config is None: return None return AioDeferredRefreshableToken( self.METHOD, self._refresher, time_fetcher=self._now ) aio-libs-aiobotocore-e39c8b3/aiobotocore/utils.py000066400000000000000000000702311510465277300221720ustar00rootroot00000000000000import asyncio import contextlib import functools import inspect import json import logging import botocore.awsrequest from botocore.exceptions import ( InvalidIMDSEndpointError, MetadataRetrievalError, ) from botocore.utils import ( DEFAULT_METADATA_SERVICE_TIMEOUT, METADATA_BASE_URL, RETRYABLE_HTTP_ERRORS, ArnParser, BadIMDSRequestError, ClientError, ContainerMetadataFetcher, HTTPClientError, IdentityCache, IMDSFetcher, IMDSRegionProvider, InstanceMetadataFetcher, InstanceMetadataRegionFetcher, PluginContext, ReadTimeoutError, S3ExpressIdentityCache, S3ExpressIdentityResolver, S3RegionRedirector, S3RegionRedirectorv2, get_environ_proxies, os, reset_plugin_context, resolve_imds_endpoint_mode, set_plugin_context, ) import aiobotocore.httpsession logger = logging.getLogger(__name__) class _RefCountedSession(aiobotocore.httpsession.AIOHTTPSession): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__ref_count = 0 self.__lock = None @contextlib.asynccontextmanager async def acquire(self): if not self.__lock: self.__lock = asyncio.Lock() # ensure we have a session async with self.__lock: self.__ref_count += 1 try: if self.__ref_count == 1: await self.__aenter__() except BaseException: self.__ref_count -= 1 raise try: yield self finally: async with self.__lock: if self.__ref_count == 1: await self.__aexit__(None, None, None) self.__ref_count -= 1 class AioIMDSFetcher(IMDSFetcher): def __init__( self, timeout=DEFAULT_METADATA_SERVICE_TIMEOUT, # noqa: E501, lgtm [py/missing-call-to-init] num_attempts=1, base_url=METADATA_BASE_URL, env=None, user_agent=None, config=None, session=None, ): self._timeout = timeout self._num_attempts = num_attempts if config is None: config = {} self._base_url = self._select_base_url(base_url, config) self._config = config if env is None: env = os.environ.copy() self._disabled = ( env.get('AWS_EC2_METADATA_DISABLED', 'false').lower() == 'true' ) self._imds_v1_disabled = config.get('ec2_metadata_v1_disabled') self._user_agent = user_agent self._session = session or _RefCountedSession( timeout=self._timeout, proxies=get_environ_proxies(self._base_url), ) async def _fetch_metadata_token(self): self._assert_enabled() url = self._construct_url(self._TOKEN_PATH) headers = { 'x-aws-ec2-metadata-token-ttl-seconds': self._TOKEN_TTL, } self._add_user_agent(headers) request = botocore.awsrequest.AWSRequest( method='PUT', url=url, headers=headers ) async with self._session.acquire() as session: for i in range(self._num_attempts): try: response = await session.send(request.prepare()) if response.status_code == 200: return await response.text elif response.status_code in (404, 403, 405): return None elif response.status_code in (400,): raise BadIMDSRequestError(request) except ReadTimeoutError: return None except RETRYABLE_HTTP_ERRORS as e: logger.debug( "Caught retryable HTTP exception while making metadata " "service request to %s: %s", url, e, exc_info=True, ) except HTTPClientError as e: error = e.kwargs.get('error') if ( error and getattr(error, 'errno', None) == 8 or str(getattr(error, 'os_error', None)) == 'Domain name not found' ): # threaded vs async resolver raise InvalidIMDSEndpointError(endpoint=url, error=e) else: raise return None async def _get_request(self, url_path, retry_func, token=None): self._assert_enabled() if not token: self._assert_v1_enabled() if retry_func is None: retry_func = self._default_retry url = self._construct_url(url_path) headers = {} if token is not None: headers['x-aws-ec2-metadata-token'] = token self._add_user_agent(headers) async with self._session.acquire() as session: for i in range(self._num_attempts): try: request = botocore.awsrequest.AWSRequest( method='GET', url=url, headers=headers ) response = await session.send(request.prepare()) should_retry = retry_func(response) if inspect.isawaitable(should_retry): should_retry = await should_retry if not should_retry: return response except RETRYABLE_HTTP_ERRORS as e: logger.debug( "Caught retryable HTTP exception while making metadata " "service request to %s: %s", url, e, exc_info=True, ) raise self._RETRIES_EXCEEDED_ERROR_CLS() async def _default_retry(self, response): return await self._is_non_ok_response( response ) or await self._is_empty(response) async def _is_non_ok_response(self, response): if response.status_code != 200: await self._log_imds_response(response, 'non-200', log_body=True) return True return False async def _is_empty(self, response): if not await response.content: await self._log_imds_response(response, 'no body', log_body=True) return True return False async def _log_imds_response( self, response, reason_to_log, log_body=False ): statement = ( "Metadata service returned %s response " "with status code of %s for url: %s" ) logger_args = [reason_to_log, response.status_code, response.url] if log_body: statement += ", content body: %s" logger_args.append(await response.content) logger.debug(statement, *logger_args) class AioInstanceMetadataFetcher(AioIMDSFetcher, InstanceMetadataFetcher): async def retrieve_iam_role_credentials(self): try: token = await self._fetch_metadata_token() role_name = await self._get_iam_role(token) credentials = await self._get_credentials(role_name, token) if self._contains_all_credential_fields(credentials): credentials = { 'role_name': role_name, 'access_key': credentials['AccessKeyId'], 'secret_key': credentials['SecretAccessKey'], 'token': credentials['Token'], 'expiry_time': credentials['Expiration'], } self._evaluate_expiration(credentials) return credentials else: if 'Code' in credentials and 'Message' in credentials: logger.debug( 'Error response received when retrieving' 'credentials: %s.', credentials, ) return {} except self._RETRIES_EXCEEDED_ERROR_CLS: logger.debug( "Max number of attempts exceeded (%s) when " "attempting to retrieve data from metadata service.", self._num_attempts, ) except BadIMDSRequestError as e: logger.debug("Bad IMDS request: %s", e.request) return {} async def _get_iam_role(self, token=None): return await ( await self._get_request( url_path=self._URL_PATH, retry_func=self._needs_retry_for_role_name, token=token, ) ).text async def _get_credentials(self, role_name, token=None): r = await self._get_request( url_path=self._URL_PATH + role_name, retry_func=self._needs_retry_for_credentials, token=token, ) return json.loads(await r.text) async def _is_invalid_json(self, response): try: json.loads(await response.text) return False except ValueError: await self._log_imds_response(response, 'invalid json') return True async def _needs_retry_for_role_name(self, response): return await self._is_non_ok_response( response ) or await self._is_empty(response) async def _needs_retry_for_credentials(self, response): return ( await self._is_non_ok_response(response) or await self._is_empty(response) or await self._is_invalid_json(response) ) class AioIMDSRegionProvider(IMDSRegionProvider): async def provide(self): """Provide the region value from IMDS.""" instance_region = await self._get_instance_metadata_region() return instance_region async def _get_instance_metadata_region(self): fetcher = self._get_fetcher() region = await fetcher.retrieve_region() return region def _create_fetcher(self): metadata_timeout = self._session.get_config_variable( 'metadata_service_timeout' ) metadata_num_attempts = self._session.get_config_variable( 'metadata_service_num_attempts' ) imds_config = { 'ec2_metadata_service_endpoint': self._session.get_config_variable( 'ec2_metadata_service_endpoint' ), 'ec2_metadata_service_endpoint_mode': resolve_imds_endpoint_mode( self._session ), 'ec2_metadata_v1_disabled': self._session.get_config_variable( 'ec2_metadata_v1_disabled' ), } fetcher = AioInstanceMetadataRegionFetcher( timeout=metadata_timeout, num_attempts=metadata_num_attempts, env=self._environ, user_agent=self._session.user_agent(), config=imds_config, ) return fetcher class AioInstanceMetadataRegionFetcher( AioIMDSFetcher, InstanceMetadataRegionFetcher ): async def retrieve_region(self): try: region = await self._get_region() return region except self._RETRIES_EXCEEDED_ERROR_CLS: logger.debug( "Max number of attempts exceeded (%s) when " "attempting to retrieve data from metadata service.", self._num_attempts, ) return None async def _get_region(self): token = await self._fetch_metadata_token() response = await self._get_request( url_path=self._URL_PATH, retry_func=self._default_retry, token=token, ) availability_zone = await response.text region = availability_zone[:-1] return region class AioIdentityCache(IdentityCache): async def get_credentials(self, **kwargs): callback = self.build_refresh_callback(**kwargs) metadata = await callback() credential_entry = self._credential_cls.create_from_metadata( metadata=metadata, refresh_using=callback, method=self.METHOD, advisory_timeout=45, mandatory_timeout=10, ) return credential_entry class AioS3ExpressIdentityCache(AioIdentityCache, S3ExpressIdentityCache): @functools.lru_cache(maxsize=100) def _get_credentials(self, bucket): return asyncio.create_task(super().get_credentials(bucket=bucket)) async def get_credentials(self, bucket): # upstream uses `@functools.lru_cache(maxsize=100)` to cache credentials. # This is incompatible with async code. # We need to implement custom caching logic. return await self._get_credentials(bucket=bucket) def build_refresh_callback(self, bucket): async def refresher(): response = await self._client.create_session(Bucket=bucket) creds = response['Credentials'] expiration = self._serialize_if_needed( creds['Expiration'], iso=True ) return { "access_key": creds['AccessKeyId'], "secret_key": creds['SecretAccessKey'], "token": creds['SessionToken'], "expiry_time": expiration, } return refresher class AioS3ExpressIdentityResolver(S3ExpressIdentityResolver): def __init__(self, client, credential_cls, cache=None): super().__init__(client, credential_cls, cache) if cache is None: cache = AioS3ExpressIdentityCache(self._client, credential_cls) self._cache = cache class AioS3RegionRedirectorv2(S3RegionRedirectorv2): async def redirect_from_error( self, request_dict, response, operation, **kwargs, ): """ An S3 request sent to the wrong region will return an error that contains the endpoint the request should be sent to. This handler will add the redirect information to the signing context and then redirect the request. """ if response is None: # This could be none if there was a ConnectionError or other # transport error. return redirect_ctx = request_dict.get('context', {}).get('s3_redirect', {}) if ArnParser.is_arn(redirect_ctx.get('bucket')): logger.debug( 'S3 request was previously for an Accesspoint ARN, not ' 'redirecting.' ) return if redirect_ctx.get('redirected'): logger.debug( 'S3 request was previously redirected, not redirecting.' ) return error = response[1].get('Error', {}) error_code = error.get('Code') response_metadata = response[1].get('ResponseMetadata', {}) # We have to account for 400 responses because # if we sign a Head* request with the wrong region, # we'll get a 400 Bad Request but we won't get a # body saying it's an "AuthorizationHeaderMalformed". is_special_head_object = ( error_code in ('301', '400') and operation.name == 'HeadObject' ) is_special_head_bucket = ( error_code in ('301', '400') and operation.name == 'HeadBucket' and 'x-amz-bucket-region' in response_metadata.get('HTTPHeaders', {}) ) is_wrong_signing_region = ( error_code == 'AuthorizationHeaderMalformed' and 'Region' in error ) is_redirect_status = response[0] is not None and response[ 0 ].status_code in (301, 302, 307) is_permanent_redirect = error_code == 'PermanentRedirect' is_opt_in_region_redirect = ( error_code == 'IllegalLocationConstraintException' and operation.name != 'CreateBucket' ) if not any( [ is_special_head_object, is_wrong_signing_region, is_permanent_redirect, is_special_head_bucket, is_redirect_status, is_opt_in_region_redirect, ] ): return bucket = request_dict['context']['s3_redirect']['bucket'] client_region = request_dict['context'].get('client_region') new_region = await self.get_bucket_region(bucket, response) if new_region is None: logger.debug( "S3 client configured for region %s but the " "bucket %s is not in that region and the proper region " "could not be automatically determined.", client_region, bucket, ) return logger.debug( "S3 client configured for region %s but the bucket %s " "is in region %s; Please configure the proper region to " "avoid multiple unnecessary redirects and signing attempts.", client_region, bucket, new_region, ) # Adding the new region to _cache will make construct_endpoint() to # use the new region as value for the AWS::Region builtin parameter. self._cache[bucket] = new_region # Re-resolve endpoint with new region and modify request_dict with # the new URL, auth scheme, and signing context. ep_resolver = self._client._ruleset_resolver ep_info = await ep_resolver.construct_endpoint( operation_model=operation, call_args=request_dict['context']['s3_redirect']['params'], request_context=request_dict['context'], ) request_dict['url'] = self.set_request_url( request_dict['url'], ep_info.url ) request_dict['context']['s3_redirect']['redirected'] = True auth_schemes = ep_info.properties.get('authSchemes') if auth_schemes is not None: auth_info = ep_resolver.auth_schemes_to_signing_ctx(auth_schemes) auth_type, signing_context = auth_info request_dict['context']['auth_type'] = auth_type request_dict['context']['signing'] = { **request_dict['context'].get('signing', {}), **signing_context, } # Return 0 so it doesn't wait to retry return 0 async def get_bucket_region(self, bucket, response): """ There are multiple potential sources for the new region to redirect to, but they aren't all universally available for use. This will try to find region from response elements, but will fall back to calling HEAD on the bucket if all else fails. :param bucket: The bucket to find the region for. This is necessary if the region is not available in the error response. :param response: A response representing a service request that failed due to incorrect region configuration. """ # First try to source the region from the headers. service_response = response[1] response_headers = service_response['ResponseMetadata']['HTTPHeaders'] if 'x-amz-bucket-region' in response_headers: return response_headers['x-amz-bucket-region'] # Next, check the error body region = service_response.get('Error', {}).get('Region', None) if region is not None: return region # Finally, HEAD the bucket. No other choice sadly. try: # NOTE: we don't need to aenter/aexit as we have a ref to the base client response = await self._client.head_bucket(Bucket=bucket) headers = response['ResponseMetadata']['HTTPHeaders'] except ClientError as e: headers = e.response['ResponseMetadata']['HTTPHeaders'] region = headers.get('x-amz-bucket-region', None) return region class AioS3RegionRedirector(S3RegionRedirector): async def redirect_from_error( self, request_dict, response, operation, **kwargs ): if response is None: # This could be none if there was a ConnectionError or other # transport error. return if self._is_s3_accesspoint(request_dict.get('context', {})): logger.debug( 'S3 request was previously to an accesspoint, not redirecting.' ) return if request_dict.get('context', {}).get('s3_redirected'): logger.debug( 'S3 request was previously redirected, not redirecting.' ) return error = response[1].get('Error', {}) error_code = error.get('Code') response_metadata = response[1].get('ResponseMetadata', {}) # We have to account for 400 responses because # if we sign a Head* request with the wrong region, # we'll get a 400 Bad Request but we won't get a # body saying it's an "AuthorizationHeaderMalformed". is_special_head_object = ( error_code in ('301', '400') and operation.name == 'HeadObject' ) is_special_head_bucket = ( error_code in ('301', '400') and operation.name == 'HeadBucket' and 'x-amz-bucket-region' in response_metadata.get('HTTPHeaders', {}) ) is_wrong_signing_region = ( error_code == 'AuthorizationHeaderMalformed' and 'Region' in error ) is_redirect_status = response[0] is not None and response[ 0 ].status_code in (301, 302, 307) is_permanent_redirect = error_code == 'PermanentRedirect' if not any( [ is_special_head_object, is_wrong_signing_region, is_permanent_redirect, is_special_head_bucket, is_redirect_status, ] ): return bucket = request_dict['context']['signing']['bucket'] client_region = request_dict['context'].get('client_region') new_region = await self.get_bucket_region(bucket, response) if new_region is None: logger.debug( "S3 client configured for region %s but the bucket %s is not " "in that region and the proper region could not be " "automatically determined.", client_region, bucket, ) return logger.debug( "S3 client configured for region %s but the bucket %s is in region" " %s; Please configure the proper region to avoid multiple " "unnecessary redirects and signing attempts.", client_region, bucket, new_region, ) endpoint = self._endpoint_resolver.resolve('s3', new_region) endpoint = endpoint['endpoint_url'] signing_context = { 'region': new_region, 'bucket': bucket, 'endpoint': endpoint, } request_dict['context']['signing'] = signing_context self._cache[bucket] = signing_context self.set_request_url(request_dict, request_dict['context']) request_dict['context']['s3_redirected'] = True # Return 0 so it doesn't wait to retry return 0 async def get_bucket_region(self, bucket, response): # First try to source the region from the headers. service_response = response[1] response_headers = service_response['ResponseMetadata']['HTTPHeaders'] if 'x-amz-bucket-region' in response_headers: return response_headers['x-amz-bucket-region'] # Next, check the error body region = service_response.get('Error', {}).get('Region', None) if region is not None: return region # Finally, HEAD the bucket. No other choice sadly. try: # NOTE: we don't need to aenter/aexit as we have a ref to the base client response = await self._client.head_bucket(Bucket=bucket) headers = response['ResponseMetadata']['HTTPHeaders'] except ClientError as e: headers = e.response['ResponseMetadata']['HTTPHeaders'] region = headers.get('x-amz-bucket-region', None) return region class AioContainerMetadataFetcher(ContainerMetadataFetcher): def __init__(self, session=None, sleep=asyncio.sleep): # noqa: E501, lgtm [py/missing-call-to-init] if session is None: session = _RefCountedSession(timeout=self.TIMEOUT_SECONDS) self._session = session self._sleep = sleep async def retrieve_full_uri(self, full_url, headers=None): self._validate_allowed_url(full_url) return await self._retrieve_credentials(full_url, headers) async def retrieve_uri(self, relative_uri): """Retrieve JSON metadata from container metadata. :type relative_uri: str :param relative_uri: A relative URI, e.g "/foo/bar?id=123" :return: The parsed JSON response. """ full_url = self.full_url(relative_uri) return await self._retrieve_credentials(full_url) async def _retrieve_credentials(self, full_url, extra_headers=None): headers = {'Accept': 'application/json'} if extra_headers is not None: headers.update(extra_headers) attempts = 0 while True: try: return await self._get_response( full_url, headers, self.TIMEOUT_SECONDS ) except MetadataRetrievalError as e: logger.debug( "Received error when attempting to retrieve " "container metadata: %s", e, exc_info=True, ) await self._sleep(self.SLEEP_TIME) attempts += 1 if attempts >= self.RETRY_ATTEMPTS: raise async def _get_response(self, full_url, headers, timeout): try: async with self._session.acquire() as session: AWSRequest = botocore.awsrequest.AWSRequest request = AWSRequest( method='GET', url=full_url, headers=headers ) response = await session.send(request.prepare()) response_text = (await response.content).decode('utf-8') if response.status_code != 200: raise MetadataRetrievalError( error_msg=( f"Received non 200 response {response.status_code} " f"from container metadata: {response_text}" ) ) try: return json.loads(response_text) except ValueError: error_msg = "Unable to parse JSON returned from container metadata services" logger.debug('%s:%s', error_msg, response_text) raise MetadataRetrievalError(error_msg=error_msg) except RETRYABLE_HTTP_ERRORS as e: error_msg = ( "Received error when attempting to retrieve " f"container metadata: {e}" ) raise MetadataRetrievalError(error_msg=error_msg) @contextlib.asynccontextmanager async def create_nested_client(session, service_name, **kwargs): """Create a nested client with plugin context disabled. If a client is created from within a plugin based on the environment variable, an infinite loop could arise. Any clients created from within another client must use this method to prevent infinite loops. This is the async version of botocore.utils.create_nested_client that works with aiobotocore's async session. Usage: async with create_nested_client(session, 'sts', region_name='us-east-1') as client: response = await client.assume_role(...) """ # Set plugin context to disabled ctx = PluginContext(plugins="DISABLED") token = set_plugin_context(ctx) try: # Create client context async with session.create_client(service_name, **kwargs) as client: # Reset plugin context immediately after client creation, matching botocore behavior reset_plugin_context(token) token = None yield client finally: if token: reset_plugin_context(token) aio-libs-aiobotocore-e39c8b3/aiobotocore/waiter.py000066400000000000000000000122311510465277300223210ustar00rootroot00000000000000import asyncio from functools import partial # WaiterModel is required for client.py import from botocore.docs.docstring import WaiterDocstring from botocore.exceptions import ClientError from botocore.useragent import register_feature_id from botocore.utils import get_service_module_name from botocore.waiter import ( NormalizedOperationMethod as _NormalizedOperationMethod, ) from botocore.waiter import ( Waiter, WaiterError, WaiterModel, # noqa: F401 lgtm[py/unused-import] is_valid_waiter_error, logger, xform_name, ) from .context import with_current_context def create_waiter_with_client(waiter_name, waiter_model, client): """ :type waiter_name: str :param waiter_name: The name of the waiter. The name should match the name (including the casing) of the key name in the waiter model file (typically this is CamelCasing). :type waiter_model: botocore.waiter.WaiterModel :param waiter_model: The model for the waiter configuration. :type client: botocore.client.BaseClient :param client: The botocore client associated with the service. :rtype: botocore.waiter.Waiter :return: The waiter object. """ single_waiter_config = waiter_model.get_waiter(waiter_name) operation_name = xform_name(single_waiter_config.operation) operation_method = NormalizedOperationMethod( getattr(client, operation_name) ) # Create a new wait method that will serve as a proxy to the underlying # Waiter.wait method. This is needed to attach a docstring to the # method. async def wait(self, **kwargs): return await AIOWaiter.wait(self, **kwargs) wait.__doc__ = WaiterDocstring( waiter_name=waiter_name, event_emitter=client.meta.events, service_model=client.meta.service_model, service_waiter_model=waiter_model, include_signature=False, ) # Rename the waiter class based on the type of waiter. waiter_class_name = str( f'{get_service_module_name(client.meta.service_model)}.Waiter.{waiter_name}' ) # Create the new waiter class documented_waiter_cls = type( waiter_class_name, (AIOWaiter,), {'wait': wait} ) # Return an instance of the new waiter class. return documented_waiter_cls( waiter_name, single_waiter_config, operation_method ) class NormalizedOperationMethod(_NormalizedOperationMethod): async def __call__(self, **kwargs): try: return await self._client_method(**kwargs) except ClientError as e: return e.response class AIOWaiter(Waiter): @with_current_context(partial(register_feature_id, 'WAITER')) async def wait(self, **kwargs): acceptors = list(self.config.acceptors) current_state = 'waiting' # pop the invocation specific config config = kwargs.pop('WaiterConfig', {}) sleep_amount = config.get('Delay', self.config.delay) max_attempts = config.get('MaxAttempts', self.config.max_attempts) last_matched_acceptor = None num_attempts = 0 while True: response = await self._operation_method(**kwargs) num_attempts += 1 for acceptor in acceptors: if acceptor.matcher_func(response): last_matched_acceptor = acceptor current_state = acceptor.state break else: # If none of the acceptors matched, we should # transition to the failure state if an error # response was received. if is_valid_waiter_error(response): # Transition to a failure state, which we # can just handle here by raising an exception. raise WaiterError( name=self.name, reason='An error occurred ({}): {}'.format( response['Error'].get('Code', 'Unknown'), response['Error'].get('Message', 'Unknown'), ), last_response=response, ) if current_state == 'success': logger.debug( "Waiting complete, waiter matched the success state." ) return response if current_state == 'failure': reason = f'Waiter encountered a terminal failure state: {acceptor.explanation}' raise WaiterError( name=self.name, reason=reason, last_response=response, ) if num_attempts >= max_attempts: if last_matched_acceptor is None: reason = 'Max attempts exceeded' else: reason = ( f'Max attempts exceeded. Previously accepted state: ' f'{acceptor.explanation}' ) raise WaiterError( name=self.name, reason=reason, last_response=response, ) await asyncio.sleep(sleep_amount) aio-libs-aiobotocore-e39c8b3/docs/000077500000000000000000000000001510465277300171005ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/docs/Makefile000066400000000000000000000011421510465277300205360ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = aiombotocore SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) aio-libs-aiobotocore-e39c8b3/docs/conf.py000066400000000000000000000126471510465277300204110ustar00rootroot00000000000000#!/usr/bin/env python3 # # aiobotocore documentation build configuration file, created by # sphinx-quickstart on Sun Dec 11 17:08:38 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' import importlib.metadata import re try: _version_info = re.fullmatch( r"(?P\d+)" r"\.(?P\d+)" r"\.(?P\d+)" r"(?P.*)?", importlib.metadata.version('aiobotocore'), ).groupdict() except IndexError: raise RuntimeError('Unable to determine version.') # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'aiobotocore' copyright = '2021, Nikolay Novik and aio-libs contributors' author = 'Nikolay Novik' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '{major}.{minor}'.format(**_version_info) # The full version, including alpha/beta/rc tags. release = '{major}.{minor}.{patch}-{tag}'.format(**_version_info) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # aiobotocore_desc = 'Async client for aws services using botocore and aiohttp' html_theme_options = { 'description': aiobotocore_desc, 'github_user': 'aio-libs', 'github_repo': 'aiobotocore', 'github_button': True, 'github_type': 'star', 'github_banner': True, } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'aiobotocoredoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ( master_doc, 'aiobotocore.tex', 'aiobotocore Documentation', 'Nikolay Novik', 'manual', ), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'aiobotocore', 'aiobotocore Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, 'aiobotocore', 'aiobotocore Documentation', author, 'aiobotocore', aiobotocore_desc, 'Miscellaneous', ), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} default_role = 'any' aio-libs-aiobotocore-e39c8b3/docs/contributing.rst000066400000000000000000000000411510465277300223340ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst aio-libs-aiobotocore-e39c8b3/docs/examples.rst000066400000000000000000000012471510465277300214540ustar00rootroot00000000000000Examples ============================= Below is a list of examples from `aiobotocore/examples `_ Every example is a correct tiny python program. .. _aiobotocore-examples-simple: S3 -------- .. toctree:: :maxdepth: 2 examples/s3/basic_usage SQS --- .. toctree:: :maxdepth: 2 examples/sqs/queue examples/sqs/producer_consumer examples/dynamo_db/insertions DynamoDB -------- .. toctree:: :maxdepth: 2 examples/dynamo_db/insertions examples/dynamo_db/tables Cognito Identity Provider ------------------------- .. toctree:: :maxdepth: 2 examples/cognito_idp/forgot_password aio-libs-aiobotocore-e39c8b3/docs/examples/000077500000000000000000000000001510465277300207165ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/docs/examples/cognito_idp/000077500000000000000000000000001510465277300232145ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/docs/examples/cognito_idp/forgot_password.rst000066400000000000000000000004471510465277300271750ustar00rootroot00000000000000Forgot Password --------------- Amazon Cognito Identity Provider (IdP) allows you to manage user authentication and user management for your applications. We will demonstrate usage with the Cognito forgot password feature. .. literalinclude:: ../../../examples/cognito_idp_forgot_password.py aio-libs-aiobotocore-e39c8b3/docs/examples/dynamo_db/000077500000000000000000000000001510465277300226525ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/docs/examples/dynamo_db/insertions.rst000066400000000000000000000017551510465277300256110ustar00rootroot00000000000000 Batch Insertion +++++++++++++++ Now if you have a massive amount of data to insert into Dynamo, I would suggest using an EMR data pipeline (theres even an example for exactly this). But if you stubborn, here is an example of inserting lots of items into Dynamo (it's not really that complicated once you've read it). What the code does is generates items (e.g. item0, item1, item2...) and writes them to a table "test" against a primary partition key called "pk" (with 5 read and 5 write units, no auto-scaling). The ``batch_write_item`` method only takes a max of 25 items at a time, so the script computes 25 items, writes them, then does it all over again. After Dynamo has had enough, it will start throttling you and return any items that have not been written in the response. Once the script is being throttled, it will start sleeping for 5 seconds until the failed items have been successfully written, after that it will exit. .. literalinclude:: ../../../examples/dynamodb_batch_write.py aio-libs-aiobotocore-e39c8b3/docs/examples/dynamo_db/tables.rst000066400000000000000000000006331510465277300246600ustar00rootroot00000000000000Table Creation ++++++++++++++ When you create a DynamoDB table, it can take quite a while (especially if you add a few secondary index's). Instead of polling ``describe_table`` yourself, boto3 came up with "waiters" that will do all the polling for you. The following snippet shows how to wait for a DynamoDB table to be created in an async way. .. literalinclude:: ../../../examples/dynamodb_create_table.py aio-libs-aiobotocore-e39c8b3/docs/examples/s3/000077500000000000000000000000001510465277300212435ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/docs/examples/s3/basic_usage.rst000066400000000000000000000002121510465277300242350ustar00rootroot00000000000000Put, Get and Delete ------------------- Simple put, get, delete example for S3 service: .. literalinclude:: ../../../examples/simple.py aio-libs-aiobotocore-e39c8b3/docs/examples/sqs/000077500000000000000000000000001510465277300215245ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/docs/examples/sqs/producer_consumer.rst000066400000000000000000000006741510465277300260230ustar00rootroot00000000000000 Here is a quick and simple producer/consumer example. The producer will put messages on the queue with a delay of up to 4 seconds between each put. The consumer will read off any messages on the queue, waiting up to 2 seconds for messages to appear before returning. Producer +++++++++++++++++ .. literalinclude:: ../../../examples/sqs_queue_producer.py Consumer +++++++++++++++++ .. literalinclude:: ../../../examples/sqs_queue_consumer.py aio-libs-aiobotocore-e39c8b3/docs/examples/sqs/queue.rst000066400000000000000000000002641510465277300234040ustar00rootroot00000000000000Queue operations --- Queue Create ++++++++++++ This snippet creates a queue, lists the queues, then deletes the queue. .. literalinclude:: ../../../examples/sqs_queue_create.py aio-libs-aiobotocore-e39c8b3/docs/index.rst000066400000000000000000000071741510465277300207520ustar00rootroot00000000000000.. aiobotocore documentation master file, created by sphinx-quickstart on Sun Dec 11 17:08:38 2016. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. aiobotocore's documentation! ============================ .. image:: https://github.com/aio-libs/aiobotocore/actions/workflows/ci-cd.yml/badge.svg?branch=master :target: https://github.com/aio-libs/aiobotocore/actions/workflows/ci-cd.yml .. image:: https://results.pre-commit.ci/badge/github/aio-libs/aiobotocore/master.svg :target: https://results.pre-commit.ci/latest/github/aio-libs/aiobotocore/master .. image:: https://codecov.io/gh/aio-libs/aiobotocore/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiobotocore .. image:: https://img.shields.io/pypi/v/aiobotocore.svg :target: https://pypi.python.org/pypi/aiobotocore Async client for amazon services using botocore_ and aiohttp_/asyncio_. This library is a mostly full featured asynchronous version of botocore. Features -------- * Full async support for AWS services with botocore. * Library used in production with S3, SQS and Dynamo services Basic Example ------------- .. code:: python import asyncio from aiobotocore.session import get_session AWS_ACCESS_KEY_ID = "xxx" AWS_SECRET_ACCESS_KEY = "xxx" async def go(): bucket = 'dataintake' filename = 'dummy.bin' folder = 'aiobotocore' key = f'{folder}/{filename}' session = get_session() async with session.create_client('s3', region_name='us-west-2', aws_secret_access_key=AWS_SECRET_ACCESS_KEY, aws_access_key_id=AWS_ACCESS_KEY_ID) as client: # upload object to amazon s3 data = b'\x01'*1024 resp = await client.put_object(Bucket=bucket, Key=key, Body=data) print(resp) # getting s3 object properties of file we just uploaded resp = await client.get_object_acl(Bucket=bucket, Key=key) print(resp) # get object from s3 response = await client.get_object(Bucket=bucket, Key=key) # this will ensure the connection is correctly re-used/closed async with response['Body'] as stream: assert await stream.read() == data # list s3 objects using paginator paginator = client.get_paginator('list_objects') async for result in paginator.paginate(Bucket=bucket, Prefix=folder): for c in result.get('Contents', []): print(c) # delete object from s3 resp = await client.delete_object(Bucket=bucket, Key=key) print(resp) loop = asyncio.get_event_loop() loop.run_until_complete(go()) awscli ------ awscli depends on a single version of botocore, however aiobotocore only supports a specific range of botocore versions. To ensure you install the latest version of awscli that your specific combination or aiobotocore and botocore can support use:: pip install -U aiobotocore[awscli] Contents -------- .. toctree:: :maxdepth: 3 tutorial examples contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _PEP492: https://www.python.org/dev/peps/pep-0492/ .. _Python: https://www.python.org .. _asyncio: https://docs.python.org/3/library/asyncio.html .. _botocore: https://github.com/boto/botocore .. _aiohttp: https://github.com/KeepSafe/aiohttp aio-libs-aiobotocore-e39c8b3/docs/requirements.txt000066400000000000000000000000161510465277300223610ustar00rootroot00000000000000sphinx==8.3.0 aio-libs-aiobotocore-e39c8b3/docs/tutorial.rst000066400000000000000000000046241510465277300215030ustar00rootroot00000000000000Getting Started With aiobotocore ================================ Following tutorial based on `botocore tutorial `_. The ``aiobotocore`` package provides a low-level interface to Amazon services. It is responsible for: * Providing access to all available services * Providing access to all operations within a service * Marshaling all parameters for a particular operation in the correct format * Signing the request with the correct authentication signature * Receiving the response and returning the data in native Python data structures ``aiobotocore`` does not provide higher-level abstractions on top of these services, operations and responses. That is left to the application layer. The goal of ``aiobotocore`` is to handle all of the low-level details of making requests and getting results from a service. The ``aiobotocore`` package is mainly data-driven. Each service has a JSON description which specifies all of the operations the service supports, all of the parameters the operation accepts, all of the documentation related to the service, information about supported regions and endpoints, etc. Because this data can be updated quickly based on the canonical description of these services, it's much easier to keep ``aiobotocore`` current. Using Botocore ============== The first step in using aiobotocore is to create a ``Session`` object. ``Session`` objects then allow you to create individual clients:: session = aiobotocore.session.get_session() async with session.create_client('s3', region_name='us-west-2', aws_secret_access_key=AWS_SECRET_ACCESS_KEY, aws_access_key_id=AWS_ACCESS_KEY_ID) as client: Once you have that client created, each operation provided by the service is mapped to a method. Each method takes ``**kwargs`` that maps to the parameter names exposed by the service. For example, using the ``client`` object created above:: # upload object to amazon s3 data = b'\x01'*1024 resp = await client.put_object(Bucket=bucket, Key=key, Body=data) print(resp) # getting s3 object properties of file we just uploaded resp = await client.get_object_acl(Bucket=bucket, Key=key) print(resp) # delete object from s3 resp = await client.delete_object(Bucket=bucket, Key=key) print(resp) aio-libs-aiobotocore-e39c8b3/examples/000077500000000000000000000000001510465277300177665ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/examples/cognito_idp_forgot_password.py000066400000000000000000000013511510465277300261400ustar00rootroot00000000000000# Boto should get credentials from ~/.aws/credentials or the environment import asyncio from aiobotocore.session import get_session async def go(): session = get_session() async with session.create_client( 'cognito-idp', region_name='us-west-2', ) as client: # initiate forgot password resp = await client.forgot_password( ClientId='xxx', Username='xxx', ) print(resp) # confirm forgot password resp = await client.confirm_forgot_password( ClientId='xxx', Username='xxx', ConfirmationCode='xxx', Password='xxx', ) print(resp) if __name__ == '__main__': asyncio.run(go()) aio-libs-aiobotocore-e39c8b3/examples/dynamodb_batch_write.py000066400000000000000000000063241510465277300245150ustar00rootroot00000000000000# Boto should get credentials from ~/.aws/credentials or the environment import asyncio from aiobotocore.session import get_session def get_items(start_num, num_items): """ Generate a sequence of dynamo items :param start_num: Start index :type start_num: int :param num_items: Number of items :type num_items: int :return: List of dictionaries :rtype: list of dict """ result = [] for i in range(start_num, start_num + num_items): result.append({'pk': {'S': f'item{i}'}}) return result def create_batch_write_structure(table_name, start_num, num_items): """ Create item structure for passing to batch_write_item :param table_name: DynamoDB table name :type table_name: str :param start_num: Start index :type start_num: int :param num_items: Number of items :type num_items: int :return: dictionary of tables to write to :rtype: dict """ return { table_name: [ {'PutRequest': {'Item': item}} for item in get_items(start_num, num_items) ] } async def go(): session = get_session() async with session.create_client( 'dynamodb', region_name='us-west-2' ) as client: table_name = 'test' print('Writing to dynamo') start = 0 while True: # Loop adding 25 items to dynamo at a time request_items = create_batch_write_structure(table_name, start, 25) response = await client.batch_write_item( RequestItems=request_items ) if len(response['UnprocessedItems']) == 0: print('Wrote 25 items to dynamo') else: # Hit the provisioned write limit print('Hit write limit, backing off then retrying') await asyncio.sleep(5) # Items left over that haven't been inserted unprocessed_items = response['UnprocessedItems'] print('Resubmitting items') # Loop until unprocessed items are written while len(unprocessed_items) > 0: response = await client.batch_write_item( RequestItems=unprocessed_items ) # If any items are still left over, add them to the # list to be written unprocessed_items = response['UnprocessedItems'] # If there are items left over, we could do with # sleeping some more if len(unprocessed_items) > 0: print('Backing off for 5 seconds') await asyncio.sleep(5) # Inserted all the unprocessed items, exit loop print('Unprocessed items successfully inserted') break start += 25 # See if DynamoDB has the last item we inserted final_item = 'item' + str(start + 24) print(f'Item "{final_item}" should exist') response = await client.get_item( TableName=table_name, Key={'pk': {'S': final_item}} ) print(f'Response: {response["Item"]}') if __name__ == '__main__': asyncio.run(go()) aio-libs-aiobotocore-e39c8b3/examples/dynamodb_create_table.py000066400000000000000000000021221510465277300246240ustar00rootroot00000000000000# Boto should get credentials from ~/.aws/credentials or the environment import asyncio import uuid from aiobotocore.session import get_session async def go(): session = get_session() async with session.create_client( 'dynamodb', region_name='us-west-2' ) as client: # Create random table name table_name = f'aiobotocore-{uuid.uuid4()}' print('Requesting table creation...') await client.create_table( TableName=table_name, AttributeDefinitions=[ {'AttributeName': 'testKey', 'AttributeType': 'S'}, ], KeySchema=[ {'AttributeName': 'testKey', 'KeyType': 'HASH'}, ], ProvisionedThroughput={ 'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10, }, ) print("Waiting for table to be created...") waiter = client.get_waiter('table_exists') await waiter.wait(TableName=table_name) print(f"Table {table_name} created") if __name__ == '__main__': asyncio.run(go()) aio-libs-aiobotocore-e39c8b3/examples/simple.py000066400000000000000000000025521510465277300216350ustar00rootroot00000000000000import asyncio from aiobotocore.session import get_session AWS_ACCESS_KEY_ID = "xxx" AWS_SECRET_ACCESS_KEY = "xxx" async def go(): bucket = 'dataintake' filename = 'dummy.bin' folder = 'aiobotocore' key = f'{folder}/{filename}' session = get_session() async with session.create_client( 's3', region_name='us-west-2', aws_secret_access_key=AWS_SECRET_ACCESS_KEY, aws_access_key_id=AWS_ACCESS_KEY_ID, ) as client: # upload object to amazon s3 data = b'\x01' * 1024 resp = await client.put_object(Bucket=bucket, Key=key, Body=data) print(resp) # getting s3 object properties of file we just uploaded resp = await client.get_object_acl(Bucket=bucket, Key=key) print(resp) resp = await client.get_object(Bucket=bucket, Key=key) async with resp['Body'] as stream: await stream.read() # if you do not read the stream the connection cannot be re-used and will be dropped print(resp) """ This is to ensure the connection is returned to the pool as soon as possible. Otherwise the connection will be released after it is GC'd """ # delete object from s3 resp = await client.delete_object(Bucket=bucket, Key=key) print(resp) if __name__ == '__main__': asyncio.run(go()) aio-libs-aiobotocore-e39c8b3/examples/sqs_queue_consumer.py000066400000000000000000000034711510465277300242720ustar00rootroot00000000000000#!/usr/bin/env python3 """ aiobotocore SQS Consumer Example """ import asyncio import sys import botocore.exceptions from aiobotocore.session import get_session QUEUE_NAME = 'test_queue12' async def go(): # Boto should get credentials from ~/.aws/credentials or the environment session = get_session() async with session.create_client('sqs', region_name='us-west-2') as client: try: response = await client.get_queue_url(QueueName=QUEUE_NAME) except botocore.exceptions.ClientError as err: if ( err.response['Error']['Code'] == 'AWS.SimpleQueueService.NonExistentQueue' ): print(f"Queue {QUEUE_NAME} does not exist") sys.exit(1) else: raise queue_url = response['QueueUrl'] print('Pulling messages off the queue') while True: try: # This loop wont spin really fast as there is # essentially a sleep in the receive_message call response = await client.receive_message( QueueUrl=queue_url, WaitTimeSeconds=2, ) if 'Messages' in response: for msg in response['Messages']: print(f'Got msg "{msg["Body"]}"') # Need to remove msg from queue or else it'll reappear await client.delete_message( QueueUrl=queue_url, ReceiptHandle=msg['ReceiptHandle'], ) else: print('No messages in queue') except KeyboardInterrupt: break print('Finished') if __name__ == '__main__': asyncio.run(go()) aio-libs-aiobotocore-e39c8b3/examples/sqs_queue_create.py000066400000000000000000000013601510465277300236750ustar00rootroot00000000000000# Boto should get credentials from ~/.aws/credentials or the environment import asyncio from aiobotocore.session import get_session async def go(): session = get_session() async with session.create_client('sqs', region_name='us-west-2') as client: print('Creating test_queue1') response = await client.create_queue(QueueName='test_queue1') queue_url = response['QueueUrl'] response = await client.list_queues() print('Queue URLs:') for queue_name in response.get('QueueUrls', []): print(f' {queue_name}') print(f'Deleting queue {queue_url}') await client.delete_queue(QueueUrl=queue_url) print('Done') if __name__ == '__main__': asyncio.run(go()) aio-libs-aiobotocore-e39c8b3/examples/sqs_queue_producer.py000066400000000000000000000030141510465277300242530ustar00rootroot00000000000000#!/usr/bin/env python3 """ aiobotocore SQS Producer Example """ import asyncio import random import sys import botocore.exceptions from aiobotocore.session import get_session QUEUE_NAME = 'test_queue12' async def go(): # Boto should get credentials from ~/.aws/credentials or the environment session = get_session() async with session.create_client('sqs', region_name='us-west-2') as client: try: response = await client.get_queue_url(QueueName=QUEUE_NAME) except botocore.exceptions.ClientError as err: if ( err.response['Error']['Code'] == 'AWS.SimpleQueueService.NonExistentQueue' ): print(f"Queue {QUEUE_NAME} does not exist") sys.exit(1) else: raise queue_url = response['QueueUrl'] print('Putting messages on the queue') msg_no = 1 while True: try: msg_body = f'Message #{msg_no}' await client.send_message( QueueUrl=queue_url, MessageBody=msg_body ) msg_no += 1 print(f'Pushed "{msg_body}" to queue') await asyncio.sleep(random.randint(1, 4)) except KeyboardInterrupt: break print('Finished') def main(): try: loop = asyncio.get_event_loop() loop.run_until_complete(go()) except KeyboardInterrupt: pass if __name__ == '__main__': main() aio-libs-aiobotocore-e39c8b3/pyproject.toml000066400000000000000000000106761510465277300210760ustar00rootroot00000000000000[build-system] requires = ["setuptools>=77.0.0"] build-backend = "setuptools.build_meta" [project] name = "aiobotocore" description = "Async client for aws services using botocore and aiohttp" requires-python = ">=3.9" authors = [ { name = "Nikolay Novik", email = "nickolainovik@gmail.com" }, ] license = "Apache-2.0" classifiers = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Natural Language :: English', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14', 'Environment :: Web Environment', 'Framework :: AsyncIO', ] dynamic = ["version", "readme"] dependencies = [ "aiohttp >= 3.9.2, < 4.0.0", "aioitertools >= 0.5.1, < 1.0.0", "botocore >= 1.40.46, < 1.40.71", # NOTE: When updating, always keep `project.optional-dependencies` aligned "python-dateutil >= 2.1, < 3.0.0", "jmespath >= 0.7.1, < 2.0.0", "multidict >= 6.0.0, < 7.0.0", "wrapt >= 1.10.10, < 2.0.0", ] [project.optional-dependencies] awscli = [ "awscli >= 1.42.46, < 1.42.71", ] boto3 = [ "boto3 >= 1.40.46, < 1.40.71", ] httpx = [ "httpx >= 0.25.1, < 0.29" ] [project.urls] Repository = "https://github.com/aio-libs/aiobotocore" Documentation = "https://aiobotocore.aio-libs.org" [dependency-groups] botocore-dev = [ # keep in sync with https://github.com/boto/botocore/blob/develop/requirements-dev.txt # "wheel==0.43.0", # "behave==1.2.5", # "jsonschema==4.23.0", "coverage==7.2.7", # "setuptools==71.1.0;python_version>='3.12'", # "packaging==24.1;python_version>='3.12'", # Requirement for setuptools>=71 # Pytest specific deps "pytest==8.1.1", "pytest-cov==5.0.0", "pytest-xdist==3.5.0", # "atomicwrites>=1.0", # Windows requirement # "colorama>0.3.0", # Windows requirement ] dev = [ "anyio >= 4.11.0", "dill >= 0.3.3, < 0.5", # Used in test_patches.py "docker >= 7.1, < 8", "docutils >= 0.19, < 0.22", # Used in test_version.py "moto[server,s3,sqs,awslambda,dynamodb,cloudformation,sns,batch,ec2,rds] >= 5.0.11, < 6", "packaging >= 24.1, < 26", # Used in test_version.py "pip >= 24.3.1, < 26", # Used in test_version.py "pre-commit >= 3.5.0, < 5", "requests >= 2.32.3, < 3", # Used in test_version.py "time-machine >= 2.15.0, < 3", # Used in test_signers.py "tomli; python_version<'3.11'", # Used in test_version.py ] [tool.uv] default-groups = [ "botocore-dev", "dev", ] required-version = ">=0.8.4,<0.10" [tool.setuptools.dynamic] version = { attr = "aiobotocore.__version__" } readme = { file = ["README.rst", "CHANGES.rst"] } [tool.setuptools.packages.find] include = ["aiobotocore*"] [tool.pytest.ini_options] cache_dir = "/tmp/pytest_aiobotocore_cache" anyio_mode = "auto" markers = [ "localonly", "config_kwargs", "patch_attributes", ] [tool.ruff] exclude = [ ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".ipynb_checkpoints", ".mypy_cache", ".nox", ".pants.d", ".pyenv", ".pytest_cache", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", ".vscode", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "site-packages", "venv", ] # Format same as Black. line-length = 79 indent-width = 4 target-version = "py39" [tool.ruff.lint] # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or # McCabe complexity (`C901`) by default. select = [ "E4", # pycodestyle "E7", # pycodestyle "E9", # pycodestyle "F", # Pyflakes "I", # Import sorting "UP", # PyUpgrade "G", # Log formatting ] ignore = [] # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] unfixable = [] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.format] # Like Black, use double quotes for strings, spaces for indents # and trailing commas. quote-style = "preserve" indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" docstring-code-format = false docstring-code-line-length = "dynamic" [tool.coverage.report] exclude_also = [ "if TYPE_CHECKING", ] aio-libs-aiobotocore-e39c8b3/tests/000077500000000000000000000000001510465277300173125ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/__init__.py000066400000000000000000000000001510465277300214110ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/000077500000000000000000000000001510465277300223505ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/__init__.py000066400000000000000000000125131510465277300244630ustar00rootroot00000000000000# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import binascii import contextlib import os import random import shutil import tempfile import time from unittest import mock # noqa: F401 import botocore.loaders from botocore.compat import parse_qs, urlparse import aiobotocore.session from aiobotocore.awsrequest import AioAWSResponse _LOADER = botocore.loaders.Loader() def random_chars(num_chars): """Returns random hex characters. Useful for creating resources with random names. """ return binascii.hexlify(os.urandom(int(num_chars / 2))).decode('ascii') def create_session(**kwargs): # Create a Session object. By default, # the _LOADER object is used as the loader # so that we reused the same models across tests. session = aiobotocore.session.AioSession(**kwargs) session.register_component('data_loader', _LOADER) session.set_config_variable('credentials_file', 'noexist/foo/botocore') return session @contextlib.contextmanager def temporary_file(mode): """This is a cross platform temporary file creation. tempfile.NamedTemporary file on windows creates a secure temp file that can't be read by other processes and can't be opened a second time. For tests, we generally *want* them to be read multiple times. The test fixture writes the temp file contents, the test reads the temp file. """ temporary_directory = tempfile.mkdtemp() basename = f'tmpfile-{int(time.time())}-{random.randint(1, 1000)}' full_filename = os.path.join(temporary_directory, basename) open(full_filename, 'w').close() try: with open(full_filename, mode) as f: yield f finally: shutil.rmtree(temporary_directory) def _urlparse(url): if isinstance(url, bytes): # Not really necessary, but it helps to reduce noise on Python 2.x url = url.decode('utf8') return urlparse(url) def assert_url_equal(url1, url2): parts1 = _urlparse(url1) parts2 = _urlparse(url2) # Because the query string ordering isn't relevant, we have to parse # every single part manually and then handle the query string. assert parts1.scheme == parts2.scheme assert parts1.netloc == parts2.netloc assert parts1.path == parts2.path assert parts1.params == parts2.params assert parts1.fragment == parts2.fragment assert parts1.username == parts2.username assert parts1.password == parts2.password assert parts1.hostname == parts2.hostname assert parts1.port == parts2.port assert parse_qs(parts1.query) == parse_qs(parts2.query) class HTTPStubberException(Exception): pass class BaseHTTPStubber: class AsyncFileWrapper: def __init__(self, body: bytes): self._body = body async def read(self): return self._body def __init__(self, obj_with_event_emitter, strict=True): self.reset() self._strict = strict self._obj_with_event_emitter = obj_with_event_emitter def reset(self): self.requests = [] self.responses = [] def add_response( self, url='https://example.com', status=200, headers=None, body=b'' ): if headers is None: headers = {} response = AioAWSResponse( url, status, headers, self.AsyncFileWrapper(body) ) self.responses.append(response) @property def _events(self): raise NotImplementedError('_events') def start(self): self._events.register('before-send', self) def stop(self): self._events.unregister('before-send', self) def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_value, traceback): self.stop() def __call__(self, request, **kwargs): self.requests.append(request) if self.responses: response = self.responses.pop(0) if isinstance(response, Exception): raise response else: return response elif self._strict: raise HTTPStubberException('Insufficient responses') else: return None class ClientHTTPStubber(BaseHTTPStubber): @property def _events(self): return self._obj_with_event_emitter.meta.events class SessionHTTPStubber(BaseHTTPStubber): @property def _events(self): return self._obj_with_event_emitter.get_component('event_emitter') def patch_load_service_model( session, monkeypatch, service_model_json, ruleset_json ): def mock_load_service_model(service_name, type_name, api_version=None): if type_name == 'service-2': return service_model_json if type_name == 'endpoint-rule-set-1': return ruleset_json loader = session.get_component('data_loader') monkeypatch.setattr(loader, 'load_service_model', mock_load_service_model) aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/functional/000077500000000000000000000000001510465277300245125ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/functional/__init__.py000066400000000000000000000000001510465277300266110ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/functional/conftest.py000066400000000000000000000007121510465277300267110ustar00rootroot00000000000000import pytest from tests.botocore_tests import create_session @pytest.fixture() def patched_session(monkeypatch): monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'access_key') monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'secret_key') monkeypatch.setenv('AWS_CONFIG_FILE', 'no-exist-foo') monkeypatch.delenv('AWS_PROFILE', raising=False) monkeypatch.delenv('AWS_DEFAULT_REGION', raising=False) session = create_session() return session aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/functional/test_bearer_auth.py000066400000000000000000000163741510465277300304170ustar00rootroot00000000000000import botocore import pytest from botocore.config import Config from tests.botocore_tests import ClientHTTPStubber, patch_load_service_model from tests.botocore_tests.functional.test_useragent import ( get_captured_ua_strings, parse_registered_feature_ids, ) MOCK_SERVICE_MODEL = { "version": "2.0", "metadata": { "apiVersion": "2020-02-02", "endpointPrefix": "mock-bearer-service", "jsonVersion": "1.1", "protocol": "rest-json", "protocols": ["rest-json"], "serviceFullName": "Mock Bearer Service", "serviceId": "Bearer Service", "signatureVersion": "v4", "signingName": "bearer-service", "uid": "bearer-service-2020-02-02", "auth": ["aws.auth#sigv4", "smithy.api#httpBearerAuth"], }, "operations": { "MockOperation": { "name": "MockOperation", "http": {"method": "GET", "requestUri": "/"}, "input": {"shape": "MockOperationRequest"}, "documentation": "", }, }, "shapes": { "MockOpParam": { "type": "string", }, "MockOperationRequest": { "type": "structure", "required": ["MockOpParam"], "members": { "MockOpParam": { "shape": "MockOpParam", "documentation": "", "location": "uri", "locationName": "param", }, }, }, }, } MOCK_RULESET = { "version": "1.0", "parameters": {}, "rules": [ { "conditions": [], "endpoint": { "url": "https://bearer-service.us-west-2.amazonaws.com/" }, "type": "endpoint", }, ], } def mocked_supported_services(): return {"bearer-service"} @pytest.fixture(autouse=True) def patch_supported_services(monkeypatch): monkeypatch.setattr( "botocore.handlers.get_bearer_auth_supported_services", mocked_supported_services, ) @pytest.mark.parametrize( "env_vars", [ {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, { "AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token", "AWS_AUTH_SCHEME_PREFERENCE": "sigv4,httpBearerAuth", }, ], ids=["valid_token", "token_overrides_auth_scheme_preference"], ) async def test_service_uses_bearer_auth( monkeypatch, patched_session, env_vars ): for key, value in env_vars.items(): monkeypatch.setenv(key, value) patch_load_service_model( patched_session, monkeypatch, MOCK_SERVICE_MODEL, MOCK_RULESET ) async with patched_session.create_client( "bearer-service", region_name="us-west-2", ) as client: with ClientHTTPStubber(client) as http_stubber: http_stubber.add_response() await client.mock_operation(MockOpParam="mock-op-param-value") request = http_stubber.requests[0] auth_header = request.headers.get("Authorization") assert auth_header == b"Bearer bearer-service-token" @pytest.mark.parametrize( "env_vars, config", [ ({}, None), ({"AWS_BEARER_TOKEN_FOO_SERVICE": "foo-service-token"}, None), ( {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, Config(signature_version="v4"), ), ( {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, Config(auth_scheme_preference="sigv4"), ), ( {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, Config(signature_version=botocore.UNSIGNED), ), ], ids=[ "no_token_configured", "invalid_token", "signature_version_config_override", "auth_scheme_preference_config_override", "botocore_unsigned_config_override", ], ) async def test_service_does_not_use_bearer_auth( monkeypatch, patched_session, env_vars, config ): for key, value in env_vars.items(): monkeypatch.setenv(key, value) patch_load_service_model( patched_session, monkeypatch, MOCK_SERVICE_MODEL, MOCK_RULESET ) async with patched_session.create_client( "bearer-service", region_name="us-west-2", config=config, ) as client: with ClientHTTPStubber(client) as http_stubber: http_stubber.add_response() await client.mock_operation(MockOpParam="mock-op-param-value") request = http_stubber.requests[0] auth_header = request.headers.get("Authorization") assert not (auth_header and auth_header.startswith(b"Bearer")) @pytest.mark.parametrize( "env_vars", [ {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, { "AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token", "AWS_AUTH_SCHEME_PREFERENCE": "sigv4,httpBearerAuth", }, ], ids=["valid_token", "token_overrides_auth_scheme_preference"], ) async def test_user_agent_has_bearer_service_env_vars_feature_id( monkeypatch, patched_session, env_vars ): for key, value in env_vars.items(): monkeypatch.setenv(key, value) patch_load_service_model( patched_session, monkeypatch, MOCK_SERVICE_MODEL, MOCK_RULESET ) async with patched_session.create_client( "bearer-service", region_name="us-west-2", ) as client: with ClientHTTPStubber(client) as http_stubber: http_stubber.add_response() await client.mock_operation(MockOpParam="mock-op-param-value") ua_string = get_captured_ua_strings(http_stubber)[0] feature_ids = parse_registered_feature_ids(ua_string) assert "3" in feature_ids @pytest.mark.parametrize( "env_vars, config", [ ({"AWS_BEARER_TOKEN_FOO_SERVICE": "foo-service-token"}, None), ( {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, Config(signature_version="v4"), ), ( {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, Config(auth_scheme_preference="sigv4"), ), ( {"AWS_BEARER_TOKEN_BEARER_SERVICE": "bearer-service-token"}, Config(signature_version=botocore.UNSIGNED), ), ], ids=[ "invalid_token", "signature_version_config_override", "auth_scheme_preference_config_override", "botocore_unsigned_config_override", ], ) async def test_user_agent_does_not_have_bearer_service_env_vars_feature_id( monkeypatch, patched_session, env_vars, config ): for key, value in env_vars.items(): monkeypatch.setenv(key, value) patch_load_service_model( patched_session, monkeypatch, MOCK_SERVICE_MODEL, MOCK_RULESET ) async with patched_session.create_client( "bearer-service", region_name="us-west-2", config=config, ) as client: with ClientHTTPStubber(client) as http_stubber: http_stubber.add_response() await client.mock_operation(MockOpParam="mock-op-param-value") ua_string = get_captured_ua_strings(http_stubber)[0] feature_ids = parse_registered_feature_ids(ua_string) assert "3" not in feature_ids aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/functional/test_credentials.py000066400000000000000000000315651510465277300304320ustar00rootroot00000000000000# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import os from datetime import datetime, timedelta, timezone from unittest.mock import patch import pytest from aiobotocore.session import AioSession from tests.botocore_tests import ( ClientHTTPStubber, SessionHTTPStubber, ) from tests.botocore_tests.functional.test_useragent import ( get_captured_ua_strings, parse_registered_feature_ids, ) @pytest.mark.parametrize( "creds_env_var,creds_file_content,patches,expected_feature_id", [ ( 'AWS_SHARED_CREDENTIALS_FILE', '[default]\naws_access_key_id = FAKEACCESSKEY\naws_secret_access_key = FAKESECRET', [ patch( "aiobotocore.credentials.AioAssumeRoleProvider.load", return_value=None, ), patch( "aiobotocore.credentials.AioEnvProvider.load", return_value=None, ), ], 'n', ), ( 'AWS_CONFIG_FILE', '[default]\naws_access_key_id = FAKEACCESSKEY\naws_secret_access_key = FAKESECRET', [ patch( "aiobotocore.credentials.AioAssumeRoleProvider.load", return_value=None, ), patch( "aiobotocore.credentials.AioEnvProvider.load", return_value=None, ), patch( "aiobotocore.credentials.AioSharedCredentialProvider.load", return_value=None, ), ], 'n', ), ], ) async def test_user_agent_has_file_based_feature_ids( creds_env_var, creds_file_content, patches, expected_feature_id, tmp_path, monkeypatch, ): credentials_file = tmp_path / "creds" credentials_file.write_text(creds_file_content) monkeypatch.setenv(creds_env_var, str(credentials_file)) for patch_obj in patches: patch_obj.start() try: session = AioSession() async with session.create_client( "s3", region_name="us-east-1" ) as client: await _assert_feature_ids_in_ua(client, expected_feature_id) finally: for patch_obj in patches: patch_obj.stop() async def _assert_feature_ids_in_ua(client, expected_feature_ids): """Helper to test feature IDs appear in user agent for multiple calls.""" with ClientHTTPStubber(client, strict=True) as http_stubber: http_stubber.add_response() http_stubber.add_response() await client.list_buckets() await client.list_buckets() ua_strings = get_captured_ua_strings(http_stubber) for ua_string in ua_strings: feature_list = parse_registered_feature_ids(ua_string) for expected_id in expected_feature_ids: assert expected_id in feature_list @patch("aiobotocore.credentials.AioCachedCredentialFetcher._load_from_cache") @patch("aiobotocore.credentials.AioSSOProvider._load_sso_config") @patch( "aiobotocore.credentials.AioAssumeRoleWithWebIdentityProvider.load", return_value=None, ) @patch("aiobotocore.credentials.AioAssumeRoleProvider.load", return_value=None) @patch("aiobotocore.credentials.AioEnvProvider.load", return_value=None) async def test_user_agent_has_sso_legacy_credentials_feature_id( _unused_mock_env_load, _unused_mock_shared_load, _unused_mock_config_load, mock_load_sso_config, mock_load_sso_credentials, monkeypatch, patched_session, ): fake_fetcher_kwargs = { 'sso_start_url': "https://test.awsapps.com/start", 'sso_region': "us-east-1", 'sso_role_name': "Administrator", 'sso_account_id': "1234567890", } fake_response = { "ProviderType": "sso", "Credentials": { "role_name": "FAKEROLE", "AccessKeyId": "FAKEACCESSKEY", "SecretAccessKey": "FAKESECRET", "SessionToken": "FAKETOKEN", "Expiration": "2099-01-01T00:00:00Z", }, } mock_load_sso_config.return_value = fake_fetcher_kwargs async with patched_session.create_client( "s3", region_name="us-east-1" ) as client_one: mock_load_sso_credentials.return_value = fake_response await _assert_feature_ids_in_ua(client_one, ['t', 'u']) @patch("aiobotocore.credentials.AioCachedCredentialFetcher._load_from_cache") @patch("aiobotocore.credentials.AioSSOProvider._load_sso_config") @patch( "aiobotocore.credentials.AioAssumeRoleWithWebIdentityProvider.load", return_value=None, ) @patch("aiobotocore.credentials.AioAssumeRoleProvider.load", return_value=None) @patch("aiobotocore.credentials.AioEnvProvider.load", return_value=None) async def test_user_agent_has_sso_credentials_feature_id( _unused_mock_env_load, _unused_mock_shared_load, _unused_mock_config_load, mock_load_sso_config, mock_load_sso_credentials, monkeypatch, patched_session, ): fake_fetcher_kwargs = { 'sso_session': 'sample_test', 'sso_start_url': "https://test.awsapps.com/start", 'sso_region': "us-east-1", 'sso_role_name': "Administrator", 'sso_account_id': "1234567890", } fake_response = { "ProviderType": "sso", "Credentials": { "role_name": "FAKEROLE", "AccessKeyId": "FAKEACCESSKEY", "SecretAccessKey": "FAKESECRET", "SessionToken": "FAKETOKEN", "Expiration": "2099-01-01T00:00:00Z", }, } mock_load_sso_config.return_value = fake_fetcher_kwargs async with patched_session.create_client( "s3", region_name="us-east-1" ) as client_one: mock_load_sso_credentials.return_value = fake_response await _assert_feature_ids_in_ua(client_one, ['r', 's']) @pytest.mark.parametrize( "config_content,env_vars,expected_source_features,expected_provider_feature", [ # Test Case 1: Assume Role with source profile ( '''[profile assume-role-test] role_arn = arn:aws:iam::123456789012:role/test-role source_profile = base [profile base] aws_access_key_id = FAKEACCESSKEY aws_secret_access_key = FAKESECRET''', {}, [ 'n', # CREDENTIALS_PROFILE 'o', # CREDENTIALS_PROFILE_SOURCE_PROFILE ], 'i', # CREDENTIALS_STS_ASSUME_ROLE ), # Test Case 2: Assume Role with named provider ( '''[profile assume-role-test] role_arn = arn:aws:iam::123456789012:role/test-role credential_source = Environment''', { 'AWS_ACCESS_KEY_ID': 'FAKEACCESSKEY', 'AWS_SECRET_ACCESS_KEY': 'FAKESECRET', }, [ 'g', # CREDENTIALS_ENV_VARS 'p', # CREDENTIALS_PROFILE_NAMED_PROVIDER ], 'i', # CREDENTIALS_STS_ASSUME_ROLE ), ], ) async def test_user_agent_has_assume_role_feature_ids( config_content, env_vars, expected_source_features, expected_provider_feature, tmp_path, ): session = _create_assume_role_session(config_content, tmp_path) # Set env vars if needed with patch.dict(os.environ, env_vars, clear=True): with SessionHTTPStubber(session) as stubber: async with session.create_client( 's3', region_name='us-east-1' ) as s3: _add_assume_role_http_response( stubber, with_web_identity=False ) stubber.add_response() stubber.add_response() await s3.list_buckets() await s3.list_buckets() ua_strings = get_captured_ua_strings(stubber) _assert_deferred_credential_feature_ids( ua_strings, expected_source_features, expected_provider_feature ) @pytest.mark.parametrize( "config_content,env_vars,expected_source_features,expected_provider_feature", [ # Test Case 1: Assume Role with Web Identity through config profile ( '''[profile assume-role-test] role_arn = arn:aws:iam::123456789012:role/test-role web_identity_token_file = {token_file}''', {}, ['q'], # CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN 'k', # CREDENTIALS_STS_ASSUME_ROLE_WEB_ID ), # Test Case 2: Assume Role with Web Identity through env vars ( '', { 'AWS_ROLE_ARN': 'arn:aws:iam::123456789012:role/test-role', 'AWS_WEB_IDENTITY_TOKEN_FILE': '{token_file}', 'AWS_ROLE_SESSION_NAME': 'test-session', }, ['h'], # CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN 'k', # CREDENTIALS_STS_ASSUME_ROLE_WEB_ID ), ], ) async def test_user_agent_has_assume_role_with_web_identity_feature_ids( config_content, env_vars, expected_source_features, expected_provider_feature, tmp_path, ): token_file = tmp_path / 'token.jwt' token_file.write_text('fake-jwt-token') if 'AWS_WEB_IDENTITY_TOKEN_FILE' in env_vars: env_vars['AWS_WEB_IDENTITY_TOKEN_FILE'] = str(token_file) elif config_content and 'web_identity_token_file' in config_content: config_content = config_content.replace( '{token_file}', str(token_file) ) session = _create_assume_role_session(config_content, tmp_path) # Set env vars if needed with patch.dict(os.environ, env_vars, clear=True): with SessionHTTPStubber(session) as stubber: async with session.create_client( 's3', region_name='us-east-1' ) as s3: _add_assume_role_http_response(stubber, with_web_identity=True) stubber.add_response() stubber.add_response() await s3.list_buckets() await s3.list_buckets() ua_strings = get_captured_ua_strings(stubber) _assert_deferred_credential_feature_ids( ua_strings, expected_source_features, expected_provider_feature ) def _create_assume_role_session(config_content, tmp_path): if config_content: config_file = tmp_path / 'config' config_file.write_text(config_content) session = AioSession(profile='assume-role-test') session.set_config_variable('config_file', str(config_file)) else: session = AioSession() return session def _add_assume_role_http_response(stubber, with_web_identity): """Add HTTP response for AssumeRole or AssumeRoleWithWebIdentity call with proper credentials""" expiration = (datetime.now(timezone.utc) + timedelta(hours=1)).strftime( '%Y-%m-%dT%H:%M:%SZ' ) method_name = ( 'AssumeRoleWithWebIdentity' if with_web_identity else 'AssumeRole' ) body = ( f'<{method_name}Response>' f' <{method_name}Result>' ' ' ' arn:aws:sts::123456789012:user' ' AKID:test-session-123' ' ' ' ' f' FAKEASSUMEROLEKEY' f' FAKEASSUMEROLSECRET' ' FAKETOKEN' f' {expiration}' ' ' f' ' f'' ) stubber.add_response(body=body.encode('utf-8')) def _assert_deferred_credential_feature_ids( ua_strings, expected_source_features, expected_provider_feature, ): """Helper to assert feature IDs for deferred credential provider tests""" assert len(ua_strings) == 3 # Request to fetch credentials should only register feature ids for the credential source credential_source_feature_list = parse_registered_feature_ids( ua_strings[0] ) for feature in expected_source_features: assert feature in credential_source_feature_list assert expected_provider_feature not in credential_source_feature_list # Original operation request should register feature ids for both the credential source and the provider for i in [1, 2]: operation_feature_list = parse_registered_feature_ids(ua_strings[i]) for feature in expected_source_features: assert feature in operation_feature_list assert expected_provider_feature in operation_feature_list aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/functional/test_useragent.py000066400000000000000000000104261510465277300301230ustar00rootroot00000000000000import anyio from aiobotocore.session import AioSession from ...mock_server import AIOServer from .. import ClientHTTPStubber def get_captured_ua_strings(stubber): """Get captured request-level user agent strings from stubber. :type stubber: tests.BaseHTTPStubber """ return [req.headers['User-Agent'].decode() for req in stubber.requests] def parse_registered_feature_ids(ua_string): """Parse registered feature ids in user agent string. :type ua_string: str :rtype: list[str] """ ua_fields = ua_string.split(' ') feature_field = [field for field in ua_fields if field.startswith('m/')][0] return feature_field[2:].split(',') async def test_user_agent_has_registered_feature_id(): session = AioSession() async with ( AIOServer() as server, session.create_client( 's3', endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): with ClientHTTPStubber(s3_client) as stub_client: stub_client.add_response() paginator = s3_client.get_paginator('list_buckets') # The `paginate()` method registers `'PAGINATOR': 'C'` async for _ in paginator.paginate(): pass ua_string = get_captured_ua_strings(stub_client)[0] feature_list = parse_registered_feature_ids(ua_string) assert 'C' in feature_list async def test_registered_feature_ids_dont_bleed_between_requests(): session = AioSession() async with ( AIOServer() as server, session.create_client( 's3', endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): with ClientHTTPStubber(s3_client) as stub_client: stub_client.add_response() waiter = s3_client.get_waiter('bucket_exists') # The `wait()` method registers `'WAITER': 'B'` await waiter.wait(Bucket='mybucket') stub_client.add_response() paginator = s3_client.get_paginator('list_buckets') # The `paginate()` method registers `'PAGINATOR': 'C'` async for _ in paginator.paginate(): pass ua_strings = get_captured_ua_strings(stub_client) waiter_feature_list = parse_registered_feature_ids(ua_strings[0]) assert 'B' in waiter_feature_list paginator_feature_list = parse_registered_feature_ids(ua_strings[1]) assert 'C' in paginator_feature_list assert 'B' not in paginator_feature_list # This tests context's bleeding across tasks instead async def test_registered_feature_ids_dont_bleed_across_threads(): session = AioSession() async with ( AIOServer() as server, session.create_client( 's3', endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): waiter_features = [] paginator_features = [] async def wait(): with ClientHTTPStubber(s3_client) as stub_client: stub_client.add_response() waiter = s3_client.get_waiter('bucket_exists') # The `wait()` method registers `'WAITER': 'B'` await waiter.wait(Bucket='mybucket') ua_string = get_captured_ua_strings(stub_client)[0] waiter_features.extend(parse_registered_feature_ids(ua_string)) async def paginate(): with ClientHTTPStubber(s3_client) as stub_client: stub_client.add_response() paginator = s3_client.get_paginator('list_buckets') # The `paginate()` method registers `'PAGINATOR': 'C'` async for _ in paginator.paginate(): pass ua_string = get_captured_ua_strings(stub_client)[0] paginator_features.extend(parse_registered_feature_ids(ua_string)) async with anyio.create_task_group() as tg: tg.start_soon(wait) tg.start_soon(paginate) assert 'B' in waiter_features assert 'C' not in waiter_features assert 'C' in paginator_features assert 'B' not in paginator_features aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/helpers.py000066400000000000000000000035201510465277300243640ustar00rootroot00000000000000from contextlib import AsyncExitStack, asynccontextmanager from botocore.stub import Stubber import aiobotocore.session class StubbedSession(aiobotocore.session.AioSession): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._cached_clients = {} self._client_stubs = {} @asynccontextmanager async def create_client(self, service_name, *args, **kwargs): async with AsyncExitStack() as es: es: AsyncExitStack if service_name not in self._cached_clients: client = await es.enter_async_context( self._create_stubbed_client(service_name, *args, **kwargs) ) self._cached_clients[service_name] = client yield self._cached_clients[service_name] @asynccontextmanager async def _create_stubbed_client(self, service_name, *args, **kwargs): async with AsyncExitStack() as es: es: AsyncExitStack client = await es.enter_async_context( super().create_client(service_name, *args, **kwargs) ) stubber = Stubber(client) self._client_stubs[service_name] = stubber yield client @asynccontextmanager async def stub(self, service_name, *args, **kwargs): async with AsyncExitStack() as es: es: AsyncExitStack if service_name not in self._client_stubs: await es.enter_async_context( self.create_client(service_name, *args, **kwargs) ) yield self._client_stubs[service_name] def activate_stubs(self): for stub in self._client_stubs.values(): stub.activate() def verify_stubs(self): for stub in self._client_stubs.values(): stub.assert_no_pending_responses() aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/000077500000000000000000000000001510465277300233275ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/__init__.py000066400000000000000000000031341510465277300254410ustar00rootroot00000000000000# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/ # Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import copy class BaseResponseTest: def assert_response_with_subset_metadata( self, actual_response, expected_response ): """ Compares two parsed service responses. For ResponseMetadata, it will only assert that the expected is a proper subset of the actual. This is useful so that when new keys are added to the metadata, tests don't break. """ actual = copy.copy(actual_response) expected = copy.copy(expected_response) actual_metadata = actual.pop('ResponseMetadata', {}) expected_metadata = expected.pop('ResponseMetadata', {}) assert actual == expected self.assert_dict_is_proper_subset(actual_metadata, expected_metadata) def assert_dict_is_proper_subset(self, superset, subset): """ Asserts that a dictionary is a proper subset of another. """ assert all( (k in superset and superset[k] == v) for k, v in subset.items() ) aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/cfg/000077500000000000000000000000001510465277300240665ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/cfg/foo_config000066400000000000000000000002321510465277300261160ustar00rootroot00000000000000[default] aws_access_key_id = foo aws_secret_access_key = bar foo_region = us-west-1 [profile "foo"] aws_access_key_id = fie aws_secret_access_key = baz aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/000077500000000000000000000000001510465277300253535ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/000077500000000000000000000000001510465277300266175ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/output/000077500000000000000000000000001510465277300301575ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/output/ec2.json000066400000000000000000000332621510465277300315310ustar00rootroot00000000000000[ { "description": "Scalar members", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Str": { "shape": "StringType" }, "Num": { "shape": "IntegerType", "locationName": "FooNum" }, "FalseBool": { "shape": "BooleanType" }, "TrueBool": { "shape": "BooleanType" }, "Float": { "shape": "FloatType" }, "Double": { "shape": "DoubleType" }, "Long": { "shape": "LongType" }, "Char": { "shape": "CharType" } } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BooleanType": { "type": "boolean" }, "FloatType": { "type": "float" }, "DoubleType": { "type": "double" }, "LongType": { "type": "long" }, "CharType": { "type": "character" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Str": "myname", "Num": 123, "FalseBool": false, "TrueBool": true, "Float": 1.2, "Double": 1.3, "Long": 200, "Char": "a" }, "response": { "status_code": 200, "headers": {}, "body": "myname123falsetrue1.21.3200arequest-id" } } ] }, { "description": "Blob", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Blob": { "shape": "BlobType" } } }, "BlobType": { "type": "blob" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Blob": "value" }, "response": { "status_code": 200, "headers": {}, "body": "dmFsdWU=requestid" } } ] }, { "description": "Lists", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape" } } }, "ListShape": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123requestid" } } ] }, { "description": "List with custom member name", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape" } } }, "ListShape": { "type": "list", "member": { "shape": "StringType", "locationName": "item" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123requestid" } } ] }, { "description": "Flattened List", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListType", "flattened": true } } }, "ListType": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123requestid" } } ] }, { "description": "Normal map", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "MapType" } } }, "MapType": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "StructureType" } }, "StructureType": { "type": "structure", "members": { "foo": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": { "foo": "bar" }, "baz": { "foo": "bam" } } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbamrequestid" } } ] }, { "description": "Flattened map", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "MapType", "flattened": true } } }, "MapType": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar", "baz": "bam" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbamrequestid" } } ] }, { "description": "Named map", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "MapType", "flattened": true } } }, "MapType": { "type": "map", "key": { "shape": "StringType", "locationName": "foo" }, "value": { "shape": "StringType", "locationName": "bar" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar", "baz": "bam" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbamrequestid" } } ] }, { "description": "Empty string", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Foo": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Foo": "" }, "response": { "status_code": 200, "headers": {}, "body": "requestid" } } ] }, { "description": "Timestamp members", "metadata": { "protocol": "ec2" }, "shapes": { "OutputShape": { "type": "structure", "members": { "TimeArg": { "shape": "TimestampType" }, "TimeCustom": { "timestampFormat": "rfc822", "shape": "TimestampType" }, "TimeFormat": { "shape": "TimestampFormatType" }, "StructMember": { "shape": "TimeContainer" } } }, "TimeContainer": { "type": "structure", "members": { "foo": { "shape": "TimestampType" }, "bar": { "shape": "TimestampFormatType" } } }, "TimestampFormatType": { "timestampFormat": "unixTimestamp", "type": "timestamp" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "TimeArg": 1398796238, "TimeCustom": 1398796238, "TimeFormat": 1398796238, "StructMember": { "foo": 1398796238, "bar": 1398796238 } }, "response": { "status_code": 200, "headers": {}, "body": "2014-04-29T18:30:38+00:0013987962382014-04-29T18:30:38+00:00Tue, 29 Apr 2014 18:30:38 GMT1398796238requestid" } } ] }, { "description": "Modeled exceptions", "metadata": { "protocol": "ec2" }, "shapes": { "ExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" }, "Message": { "shape": "StringType" } } }, "OtherExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" } } }, "StatusShape": { "type": "integer" }, "StringType": { "type": "string" } }, "cases": [ { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "ExceptionShapemymessagemybody" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody" }, "errorCode": "OtherExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "OtherExceptionShapemymessagemybody" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": {}, "errorCode": "UndefinedShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "UndefinedShapemymessagemybody" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/output/json.json000066400000000000000000000456321510465277300320350ustar00rootroot00000000000000[ { "description": "Scalar members", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Str": { "shape": "StringType" }, "Num": { "shape": "IntegerType" }, "FalseBool": { "shape": "BooleanType" }, "TrueBool": { "shape": "BooleanType" }, "Float": { "shape": "FloatType" }, "Double": { "shape": "DoubleType" }, "Long": { "shape": "LongType" }, "Char": { "shape": "CharType" } } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BooleanType": { "type": "boolean" }, "FloatType": { "type": "float" }, "DoubleType": { "type": "double" }, "LongType": { "type": "long" }, "CharType": { "type": "character" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Str": "myname", "Num": 123, "FalseBool": false, "TrueBool": true, "Float": 1.2, "Double": 1.3, "Long": 200, "Char": "a" }, "response": { "status_code": 200, "headers": {}, "body": "{\"Str\": \"myname\", \"Num\": 123, \"FalseBool\": false, \"TrueBool\": true, \"Float\": 1.2, \"Double\": 1.3, \"Long\": 200, \"Char\": \"a\"}" } } ] }, { "description": "Blob members", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "BlobMember": { "shape": "BlobType" }, "StructMember": { "shape": "BlobContainer" } } }, "BlobType": { "type": "blob" }, "BlobContainer": { "type": "structure", "members": { "foo": { "shape": "BlobType" } } } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "BlobMember": "hi!", "StructMember": { "foo": "there!" } }, "response": { "status_code": 200, "headers": {}, "body": "{\"BlobMember\": \"aGkh\", \"StructMember\": {\"foo\": \"dGhlcmUh\"}}" } } ] }, { "description": "Timestamp members", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "TimeArg": { "shape": "TimestampType" }, "TimeCustom": { "timestampFormat": "rfc822", "shape": "TimestampType" }, "TimeFormat": { "shape": "TimestampFormatType" }, "StructMember": { "shape": "TimeContainer" } } }, "TimeContainer": { "type": "structure", "members": { "foo": { "shape": "TimestampType" }, "bar": { "shape": "TimestampFormatType" } } }, "TimestampFormatType": { "timestampFormat": "iso8601", "type": "timestamp" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "TimeArg": 1398796238, "TimeCustom": 1398796238, "TimeFormat": 1398796238, "StructMember": { "foo": 1398796238, "bar": 1398796238 } }, "response": { "status_code": 200, "headers": {}, "body": "{\"TimeArg\": 1398796238, \"TimeCustom\": \"Tue, 29 Apr 2014 18:30:38 GMT\", \"TimeFormat\": \"2014-04-29T18:30:38+00:00\", \"StructMember\": {\"foo\": 1398796238, \"bar\": \"2014-04-29T18:30:38+00:00\"}}" } } ] }, { "description": "Lists", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListType" }, "ListMemberMap": { "shape": "ListTypeMap" }, "ListMemberStruct": { "shape": "ListTypeStruct" } } }, "ListType": { "type": "list", "member": { "shape": "StringType" } }, "ListTypeMap": { "type": "list", "member": { "shape": "MapType" } }, "ListTypeStruct": { "type": "list", "member": { "shape": "StructType" } }, "StringType": { "type": "string" }, "StructType": { "type": "structure", "members": { } }, "MapType": { "type": "string", "key": { "shape": "StringType" }, "value": { "shape": "StringType" } } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["a", "b"] }, "response": { "status_code": 200, "headers": {}, "body": "{\"ListMember\": [\"a\", \"b\"]}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["a", null], "ListMemberMap": [{}, null, null, {}], "ListMemberStruct": [{}, null, null, {}] }, "response": { "status_code": 200, "headers": {}, "body": "{\"ListMember\": [\"a\", null], \"ListMemberMap\": [{}, null, null, {}], \"ListMemberStruct\": [{}, null, null, {}]}" } } ] }, { "description": "Maps", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "MapMember": { "shape": "MapType" } } }, "MapType": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "NumberList" } }, "StringType": { "type": "string" }, "NumberList": { "type": "list", "member": { "shape": "IntegerType" } }, "IntegerType": { "type": "integer" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "MapMember": { "a": [1, 2], "b": [3, 4] } }, "response": { "status_code": 200, "headers": {}, "body": "{\"MapMember\": {\"a\": [1, 2], \"b\": [3, 4]}}" } } ] }, { "description": "Ignores extra data", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "StrType": { "shape": "StrType" } } }, "StrType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": {}, "response": { "status_code": 200, "headers": {}, "body": "{\"foo\": \"bar\"}" } } ] }, { "description": "RPC JSON Event Stream", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Payload": {"shape": "EventStream"}, "InitialResponse": {"shape": "StringType"} } }, "EventStream": { "type": "structure", "eventstream": true, "members": { "TypeA": {"shape": "TypeAEvent"}, "TypeB": {"shape": "TypeBEvent"} } }, "TypeAEvent": { "type": "structure", "event": true, "members": { "Payload": { "shape": "BlobType", "eventpayload": true } } }, "TypeBEvent": { "type": "structure", "event": true, "members": { "Details": { "shape": "Details", "eventpayload": true } } }, "Details": { "type": "structure", "members": { "StringField": {"shape": "StringType"}, "IntegerField": {"shape": "IntegerType"} } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BlobType": { "type": "blob" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "InitialResponse": "sometext", "Payload": [ { "TypeA": {"Payload": "somebytes"} }, { "TypeB": { "Details": { "StringField": "somestring", "IntegerField": 123 } } } ] }, "response": { "status_code": 200, "headers": {}, "body": "AAAAfgAAAE/Fo93GDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcAEGluaXRpYWwtcmVzcG9uc2UNOmNvbnRlbnQtdHlwZQcACXRleHQvanNvbnsiSW5pdGlhbFJlc3BvbnNlIjogInNvbWV0ZXh0In32mCSDAAAAbAAAAFPLgkVrDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVBDTpjb250ZW50LXR5cGUHABhhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW1zb21lYnl0ZXMesj2HAAAAhgAAAEQqNR/SDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVCDTpjb250ZW50LXR5cGUHAAl0ZXh0L2pzb257IlN0cmluZ0ZpZWxkIjogInNvbWVzdHJpbmciLCAiSW50ZWdlckZpZWxkIjogMTIzfffGN30=" } } ] }, { "description": "Modeled exceptions", "metadata": { "protocol": "json" }, "shapes": { "ExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" }, "Code": { "shape": "StringType" }, "Message": { "shape": "StringType" } } }, "OtherExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody", "Code": "OtherExceptionShape", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "{ \"__type\": \"ExceptionShape\", \"Code\": \"OtherExceptionShape\", \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody" }, "errorCode": "OtherExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "{ \"__type\": \"OtherExceptionShape\", \"Code\": \"ExceptionShape\", \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": {}, "errorCode": "UndefinedShape", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "{ \"__type\": \"UndefinedShape\", \"Code\": \"ExceptionShape\", \"BodyMember\": \"mybody\"}" } } ] }, { "description": "Modeled exceptions with jsonVersion 1.0", "metadata": { "protocol": "json", "jsonVersion": "1.0" }, "shapes": { "ExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" }, "Message": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "{ \"__type\": \"FooPrefix#ExceptionShape\", \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } } ] }, { "description": "Serialize document with standalone primitive type in a JSON response", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "inlineDocument": { "shape": "DocumentType" } } }, "DocumentType": { "type": "structure", "document": true } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "inlineDocument": "foo" }, "response": { "status_code": 200, "headers": {}, "body": "{\"inlineDocument\": \"foo\"}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "inlineDocument": 123 }, "response": { "status_code": 200, "headers": {}, "body": "{\"inlineDocument\": 123}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "inlineDocument": 1.2 }, "response": { "status_code": 200, "headers": {}, "body": "{\"inlineDocument\": 1.2}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "inlineDocument": true }, "response": { "status_code": 200, "headers": {}, "body": "{\"inlineDocument\": true}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "inlineDocument": "" }, "response": { "status_code": 200, "headers": {}, "body": "{\"inlineDocument\": \"\"}" } } ] }, { "description": "Serialize inline document in a JSON response", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "inlineDocument": { "shape": "DocumentType" } } }, "DocumentType": { "type": "structure", "document": true } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "inlineDocument": {"foo": "bar"} }, "response": { "status_code": 200, "headers": {}, "body": "{\"inlineDocument\": {\"foo\": \"bar\"}}" } } ] }, { "description": "Serialize aggregate documents in a JSON response", "metadata": { "protocol": "json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "parentDocument": { "shape": "DocumentType" } } }, "DocumentType": { "type": "structure", "document": true } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "parentDocument": { "str": "test", "num": 123, "float": 1.2, "bool": true, "null": "", "document": {"foo": false}, "list": ["myname", 321, 1.3, true, "", {"nested": true}, [200, ""]] } }, "response": { "status_code": 200, "headers": {}, "body": "{\"parentDocument\": {\"str\": \"test\", \"num\": 123, \"float\": 1.2, \"bool\": true, \"null\": \"\", \"document\": {\"foo\": false}, \"list\": [\"myname\", 321, 1.3, true, \"\", {\"nested\": true}, [200, \"\"]]}}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "parentDocument": [ "test", 123, 1.2, true, "", {"str": "myname", "num": 321, "float": 1.3, "bool": true, "null": "", "document": {"nested": true}, "list": [200, ""]}, ["foo", false] ] }, "response": { "status_code": 200, "headers": {}, "body": "{\"parentDocument\": [\"test\", 123, 1.2, true, \"\", {\"str\": \"myname\", \"num\": 321, \"float\": 1.3, \"bool\": true, \"null\": \"\", \"document\": {\"nested\": true}, \"list\": [200, \"\"]}, [\"foo\", false]]}" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/output/query.json000066400000000000000000000557041510465277300322320ustar00rootroot00000000000000[ { "description": "Scalar members", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Str": { "shape": "StringType" }, "Num": { "shape": "IntegerType", "locationName": "FooNum" }, "FalseBool": { "shape": "BooleanType" }, "TrueBool": { "shape": "BooleanType" }, "Float": { "shape": "FloatType" }, "Double": { "shape": "DoubleType" }, "Long": { "shape": "LongType" }, "Char": { "shape": "CharType" }, "Timestamp": { "shape": "TimestampType" } } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BooleanType": { "type": "boolean" }, "FloatType": { "type": "float" }, "DoubleType": { "type": "double" }, "LongType": { "type": "long" }, "CharType": { "type": "character" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Str": "myname", "Num": 123, "FalseBool": false, "TrueBool": true, "Float": 1.2, "Double": 1.3, "Long": 200, "Char": "a", "Timestamp": 1422172800 }, "response": { "status_code": 200, "headers": {}, "body": "myname123falsetrue1.21.3200a2015-01-25T08:00:00Zrequest-id" } } ] }, { "description": "Not all members in response", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Str": { "shape": "StringType" }, "Num": { "shape": "IntegerType" } } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Str": "myname" }, "response": { "status_code": 200, "headers": {}, "body": "mynamerequest-id" } } ] }, { "description": "Blob", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Blob": { "shape": "BlobType" } } }, "BlobType": { "type": "blob" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Blob": "value" }, "response": { "status_code": 200, "headers": {}, "body": "dmFsdWU=requestid" } } ] }, { "description": "Lists", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape" } } }, "ListShape": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123requestid" } } ] }, { "description": "List with custom member name", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape" } } }, "ListShape": { "type": "list", "member": { "shape": "StringType", "locationName": "item" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123requestid" } } ] }, { "description": "Flattened List", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListType" } } }, "ListType": { "type": "list", "flattened": true, "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123requestid" } } ] }, { "description": "Flattened single element list", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListType" } } }, "ListType": { "type": "list", "flattened": true, "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc"] }, "response": { "status_code": 200, "headers": {}, "body": "abcrequestid" } } ] }, { "description": "List of structures", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "List": { "shape": "ListOfStructs" } } }, "ListOfStructs": { "type": "list", "member": { "shape": "StructureShape" } }, "StructureShape": { "type": "structure", "members": { "Foo": { "shape": "StringShape" }, "Bar": { "shape": "StringShape" }, "Baz": { "shape": "StringShape" } } }, "StringShape": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "List": [{"Foo": "firstfoo", "Bar": "firstbar", "Baz": "firstbaz"}, {"Foo": "secondfoo", "Bar": "secondbar", "Baz": "secondbaz"}] }, "response": { "status_code": 200, "headers": {}, "body": "firstfoofirstbarfirstbazsecondfoosecondbarsecondbazrequestid" } } ] }, { "description": "Flattened list of structures", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "resultWrapper": "OperationNameResult", "members": { "List": { "shape": "ListOfStructs" } } }, "ListOfStructs": { "type": "list", "flattened": true, "member": { "shape": "StructureShape" } }, "StructureShape": { "type": "structure", "members": { "Foo": { "shape": "StringShape" }, "Bar": { "shape": "StringShape" }, "Baz": { "shape": "StringShape" } } }, "StringShape": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "List": [{"Foo": "firstfoo", "Bar": "firstbar", "Baz": "firstbaz"}, {"Foo": "secondfoo", "Bar": "secondbar", "Baz": "secondbaz"}] }, "response": { "status_code": 200, "headers": {}, "body": "firstfoofirstbarfirstbazsecondfoosecondbarsecondbazrequestid" } } ] }, { "description": "Flattened list with location name", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "List": { "shape": "ListType" } } }, "ListType": { "type": "list", "flattened": true, "member": { "shape": "StringShape", "locationName": "NamedList" } }, "StringShape": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "List": ["a", "b"] }, "response": { "status_code": 200, "headers": {}, "body": "abrequestid" } } ] }, { "description": "Normal map", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "StringMap" } } }, "StringMap": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "StructType" } }, "StringType": { "type": "string" }, "StructType": { "type": "structure", "members": { "foo": { "shape": "StringType" } } } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": { "foo": "bar" }, "baz": { "foo": "bam" } } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbamrequestid" } } ] }, { "description": "Flattened map", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "StringMap", "flattened": true } } }, "StringMap": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar", "baz": "bam" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbamrequestid" } } ] }, { "description": "Flattened map in shape definition", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "StringMap", "locationName": "Attribute" } } }, "StringMap": { "type": "map", "key": { "shape": "StringType", "locationName": "Name" }, "value": { "shape": "StringType", "locationName": "Value" }, "flattened": true, "locationName": "Attribute" }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarrequestid" } } ] }, { "description": "Named map", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "MapType" } } }, "MapType": { "type": "map", "flattened": true, "key": { "locationName": "foo", "shape": "StringType" }, "value": { "locationName": "bar", "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar", "baz": "bam" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbamrequestid" } } ] }, { "description": "Empty string", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Foo": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Foo": "" }, "response": { "status_code": 200, "headers": {}, "body": "requestid" } } ] }, { "description": "Timestamp members", "metadata": { "protocol": "query" }, "shapes": { "OutputShape": { "type": "structure", "members": { "TimeArg": { "shape": "TimestampType" }, "TimeCustom": { "timestampFormat": "rfc822", "shape": "TimestampType" }, "TimeFormat": { "shape": "TimestampFormatType" }, "StructMember": { "shape": "TimeContainer" } } }, "TimeContainer": { "type": "structure", "members": { "foo": { "shape": "TimestampType" }, "bar": { "shape": "TimestampFormatType" } } }, "TimestampFormatType": { "timestampFormat": "unixTimestamp", "type": "timestamp" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "resultWrapper": "OperationNameResult", "shape": "OutputShape" }, "name": "OperationName" }, "result": { "TimeArg": 1398796238, "TimeCustom": 1398796238, "TimeFormat": 1398796238, "StructMember": { "foo": 1398796238, "bar": 1398796238 } }, "response": { "status_code": 200, "headers": {}, "body": "2014-04-29T18:30:38+00:0013987962382014-04-29T18:30:38+00:00Tue, 29 Apr 2014 18:30:38 GMT1398796238requestid" } } ] }, { "description": "Modeled exceptions", "metadata": { "protocol": "query" }, "shapes": { "ExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" }, "Message": { "shape": "StringType" } } }, "OtherExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "SomeTypeExceptionShapemymessagemybody" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody" }, "errorCode": "OtherExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "SomeTypeOtherExceptionShapemymessagemybody" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": {}, "errorCode": "UndefinedShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": {}, "body": "SomeTypeUndefinedShapemymessagemybody" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/output/rest-json.json000066400000000000000000000716521510465277300330110ustar00rootroot00000000000000[ { "description": "Scalar members", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ImaHeader": { "shape": "HeaderShape" }, "ImaHeaderLocation": { "shape": "HeaderShape", "locationName": "X-Foo" }, "Status": { "shape": "StatusShape", "location": "statusCode" }, "Str": { "shape": "StringType" }, "Num": { "shape": "IntegerType" }, "FalseBool": { "shape": "BooleanType" }, "TrueBool": { "shape": "BooleanType" }, "Float": { "shape": "FloatType" }, "Double": { "shape": "DoubleType" }, "Long": { "shape": "LongType" }, "Char": { "shape": "CharType" } } }, "HeaderShape": { "type": "string", "location": "header" }, "StatusShape": { "type": "integer" }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BooleanType": { "type": "boolean" }, "FloatType": { "type": "float" }, "DoubleType": { "type": "double" }, "LongType": { "type": "long" }, "CharType": { "type": "character" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Status": 200, "Str": "myname", "Num": 123, "FalseBool": false, "TrueBool": true, "Float": 1.2, "Double": 1.3, "Long": 200, "Char": "a" }, "response": { "status_code": 200, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "{\"Str\": \"myname\", \"Num\": 123, \"FalseBool\": false, \"TrueBool\": true, \"Float\": 1.2, \"Double\": 1.3, \"Long\": 200, \"Char\": \"a\"}" } } ] }, { "description": "Blob members", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "BlobMember": { "shape": "BlobType" }, "StructMember": { "shape": "BlobContainer" } } }, "BlobType": { "type": "blob" }, "BlobContainer": { "type": "structure", "members": { "foo": { "shape": "BlobType" } } } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "BlobMember": "hi!", "StructMember": { "foo": "there!" } }, "response": { "status_code": 200, "headers": {}, "body": "{\"BlobMember\": \"aGkh\", \"StructMember\": {\"foo\": \"dGhlcmUh\"}}" } } ] }, { "description": "Timestamp members", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "TimeArg": { "shape": "TimestampType" }, "TimeArgInHeader": { "shape": "TimestampType", "location": "header", "locationName": "x-amz-timearg" }, "TimeCustom": { "timestampFormat": "rfc822", "shape": "TimestampType" }, "TimeCustomInHeader": { "timestampFormat": "unixTimestamp", "shape": "TimestampType", "location": "header", "locationName": "x-amz-timecustom" }, "TimeFormat": { "shape": "TimestampFormatType" }, "TimeFormatInHeader": { "shape": "TimestampFormatType", "location": "header", "locationName": "x-amz-timeformat" }, "StructMember": { "shape": "TimeContainer" } } }, "TimeContainer": { "type": "structure", "members": { "foo": { "shape": "TimestampType" }, "bar": { "shape": "TimestampFormatType" } } }, "TimestampFormatType": { "timestampFormat": "iso8601", "type": "timestamp" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "TimeArg": 1398796238, "TimeArgInHeader": 1398796238, "TimeCustom": 1398796238, "TimeCustomInHeader": 1398796238, "TimeFormat": 1398796238, "TimeFormatInHeader": 1398796238, "StructMember": { "foo": 1398796238, "bar": 1398796238 } }, "response": { "status_code": 200, "headers": { "x-amz-timearg": "Tue, 29 Apr 2014 18:30:38 GMT", "x-amz-timecustom": "1398796238", "x-amz-timeformat": "2014-04-29T18:30:38+00:00" }, "body": "{\"TimeArg\": 1398796238, \"TimeCustom\": \"Tue, 29 Apr 2014 18:30:38 GMT\", \"TimeFormat\": \"2014-04-29T18:30:38+00:00\", \"StructMember\": {\"foo\": 1398796238, \"bar\": \"2014-04-29T18:30:38+00:00\"}}" } } ] }, { "description": "Lists", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListType" } } }, "ListType": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["a", "b"] }, "response": { "status_code": 200, "headers": {}, "body": "{\"ListMember\": [\"a\", \"b\"]}" } } ] }, { "description": "Lists with structure member", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListType" } } }, "ListType": { "type": "list", "member": { "shape": "SingleStruct" } }, "StringType": { "type": "string" }, "SingleStruct": { "type": "structure", "members": { "Foo": { "shape": "StringType" } } } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": [{"Foo": "a"}, {"Foo": "b"}] }, "response": { "status_code": 200, "headers": {}, "body": "{\"ListMember\": [{\"Foo\": \"a\"}, {\"Foo\": \"b\"}]}" } } ] }, { "description": "Maps", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "MapMember": { "shape": "MapType" } } }, "MapType": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "ListType" } }, "ListType": { "type": "list", "member": { "shape": "IntegerType" } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "MapMember": { "a": [1, 2], "b": [3, 4] } }, "response": { "status_code": 200, "headers": {}, "body": "{\"MapMember\": {\"a\": [1, 2], \"b\": [3, 4]}}" } } ] }, { "description": "Complex Map Values", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "MapMember": { "shape": "MapType" } } }, "MapType": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "TimeType" } }, "TimeType": { "type": "timestamp" }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "MapMember": { "a": 1398796238, "b": 1398796238 } }, "response": { "status_code": 200, "headers": {}, "body": "{\"MapMember\": {\"a\": 1398796238, \"b\": 1398796238}}" } } ] }, { "description": "Ignores extra data", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "StrType": { "shape": "StrType" } } }, "StrType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": {}, "response": { "status_code": 200, "headers": {}, "body": "{\"foo\": \"bar\"}" } } ] }, { "description": "Ignores undefined output", "metadata": { "protocol": "rest-json" }, "shapes": {}, "cases": [ { "given": { "name": "OperationName" }, "result": {}, "response": { "status_code": 200, "headers": {}, "body": "OK" } } ] }, { "description": "Supports header maps", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "AllHeaders": { "shape": "HeaderMap", "location": "headers" }, "PrefixedHeaders": { "shape": "HeaderMap", "location": "headers", "locationName": "X-" } } }, "HeaderMap": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "AllHeaders": { "Content-Length": "10", "x-Foo": "bar", "X-bam": "boo" }, "PrefixedHeaders": { "Foo": "bar", "bam": "boo" } }, "response": { "status_code": 200, "headers": { "Content-Length": "10", "x-Foo": "bar", "X-bam": "boo" }, "body": "{}" } } ] }, { "description": "JSON payload", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "payload": "Data", "members": { "Header": { "shape": "StringType", "location": "header", "locationName": "X-Foo" }, "Data": { "shape": "BodyStructure" } } }, "BodyStructure": { "type": "structure", "members": { "Foo": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Header": "baz", "Data": { "Foo": "abc" } }, "response": { "status_code": 200, "headers": { "X-Foo": "baz" }, "body": "{\"Foo\": \"abc\"}" } } ] }, { "description": "Streaming payload", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "payload": "Stream", "members": { "Stream": { "shape": "Stream" } } }, "Stream": { "type": "blob" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Stream": "abc" }, "response": { "status_code": 200, "headers": {}, "body": "abc" } } ] }, { "description": "JSON value trait", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Attr": { "shape": "StringType", "jsonvalue": true, "location": "header", "locationName": "X-Amz-Foo" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Attr": {"Foo":"Bar"} }, "response": { "status_code": 200, "headers": {"X-Amz-Foo": "eyJGb28iOiJCYXIifQ=="}, "body": "" } } ] }, { "description": "Modeled exceptions", "metadata": { "protocol": "rest-json" }, "shapes": { "ExceptionShape": { "exception": true, "type": "structure", "members": { "ImaHeader": { "shape": "HeaderShape" }, "ImaHeaderLocation": { "shape": "HeaderShape", "locationName": "X-Foo" }, "Status": { "shape": "StatusShape", "location": "statusCode" }, "BodyMember": { "shape": "StringType" }, "Message": { "shape": "StringType" } } }, "OtherExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" } } }, "HeaderShape": { "type": "string", "location": "header" }, "StatusShape": { "type": "integer" }, "StringType": { "type": "string" } }, "cases": [ { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Status": 400, "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Errortype": "ExceptionShape" }, "body": "{\"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Status": 400, "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "{ \"code\": \"ExceptionShape\", \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Status": 400, "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Errortype": "ExceptionShape" }, "body": "{ \"code\": \"OtherExceptionShape\", \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody" }, "errorCode": "OtherExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Errortype": "OtherExceptionShape" }, "body": "{ \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody" }, "errorCode": "OtherExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "{ \"code\": \"OtherExceptionShape\", \"BodyMember\": \"mybody\", \"Message\": \"mymessage\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": {}, "errorCode": "UndefinedShape", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Errortype": "UndefinedShape" }, "body": "{ \"BodyMember\": \"mybody\"}" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": {}, "errorCode": "UndefinedShape", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "{ \"code\": \"UndefinedShape\", \"BodyMember\": \"mybody\"}" } } ] }, { "description": "Serializes document with standalone primitive as part of the JSON response payload with no escaping.", "metadata": { "protocol": "rest-json", "apiVersion": "2014-01-01" }, "shapes": { "OutputShape": { "type": "structure", "members": { "documentValue": { "shape": "DocumentType" } } }, "DocumentType": { "type": "structure", "document": true } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": "foo" }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": \"foo\"}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": 123 }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": 123}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": 1.2 }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": 1.2}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": true }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": true}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": "" }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": \"\"}" } } ] }, { "description": "Serializes inline documents as part of the JSON response payload with no escaping.", "metadata": { "protocol": "rest-json", "apiVersion": "2014-01-01" }, "shapes": { "OutputShape": { "type": "structure", "members": { "documentValue": { "shape": "DocumentType" } } }, "DocumentType": { "type": "structure", "document": true } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": {"foo": "bar"} }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": {\"foo\": \"bar\"}}" } } ] }, { "description": "Serializes aggregate documents as part of the JSON response payload with no escaping.", "metadata": { "protocol": "rest-json", "apiVersion": "2014-01-01" }, "shapes": { "OutputShape": { "type": "structure", "members": { "documentValue": { "shape": "DocumentType" } } }, "DocumentType": { "type": "structure", "document": true } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": { "str": "test", "num": 123, "float": 1.2, "bool": true, "null": "", "document": {"foo": false}, "list": ["myname", 321, 1.3, true, "", {"nested": true}, [200, ""]] } }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": {\"str\": \"test\", \"num\": 123, \"float\": 1.2, \"bool\": true, \"null\": \"\", \"document\": {\"foo\": false}, \"list\": [\"myname\", 321, 1.3, true, \"\", {\"nested\": true}, [200, \"\"]]}}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "documentValue": [ "test", 123, 1.2, true, "", {"str": "myname", "num": 321, "float": 1.3, "bool": true, "null": "", "document": {"nested": true}, "list": [200, ""]}, ["foo", false] ] }, "response": { "status_code": 200, "headers": {}, "body": "{\"documentValue\": [\"test\", 123, 1.2, true, \"\", {\"str\": \"myname\", \"num\": 321, \"float\": 1.3, \"bool\": true, \"null\": \"\", \"document\": {\"nested\": true}, \"list\": [200, \"\"]}, [\"foo\", false]]}" } } ] }, { "description": "Tagged Unions", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "UnionMember": { "shape": "UnionType" } } }, "UnionType": { "type": "structure", "members": { "S":{"shape":"StringType"}, "L": {"shape": "ListType"} }, "union": true }, "ListType": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "UnionMember": {"S": "mystring"} }, "response": { "status_code": 200, "headers": {}, "body": "{\"UnionMember\": {\"S\": \"mystring\"}}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "UnionMember": {"L": ["a", "b"]} }, "response": { "status_code": 200, "headers": {}, "body": "{\"UnionMember\": {\"L\": [\"a\", \"b\"]}}" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "UnionMember": {"SDK_UNKNOWN_MEMBER": {"name": "SomeUnknownMember"}} }, "response": { "status_code": 200, "headers": {}, "body": "{\"UnionMember\": {\"SomeUnknownMember\": \"foo\"}}" } } ] }, { "description": "List in header", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape", "location": "header", "locationName": "x-amz-list-member" } } }, "ListShape": { "type": "list", "member": { "shape": "EnumType" } }, "EnumType": { "type": "string", "enum": ["one", "two", "three"] } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["one", "two", "three"] }, "response": { "status_code": 200, "headers": { "x-amz-list-member": " one,two , three " }, "body": "" } } ] }, { "description": "Number in header", "metadata": { "protocol": "rest-json" }, "shapes": { "OutputShape": { "type": "structure", "members": { "IntegerMember": { "shape": "IntegerShape", "location": "header", "locationName": "x-amz-integer-member" }, "LongMember": { "shape": "LongShape", "location": "header", "locationName": "x-amz-long-member" } } }, "IntegerShape": { "type": "integer" }, "LongShape": { "type": "long" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "IntegerMember": 123, "LongMember": 200 }, "response": { "status_code": 200, "headers": { "x-amz-integer-member": "123", "x-amz-long-member": "200" }, "body": "" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/legacy/output/rest-xml.json000066400000000000000000000744441510465277300326420ustar00rootroot00000000000000[ { "description": "Scalar members", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ImaHeader": { "shape": "HeaderShape" }, "ImaHeaderLocation": { "shape": "HeaderShape", "locationName": "X-Foo" }, "Str": { "shape": "StringType" }, "Num": { "shape": "IntegerType", "locationName": "FooNum" }, "FalseBool": { "shape": "BooleanType" }, "TrueBool": { "shape": "BooleanType" }, "Float": { "shape": "FloatType" }, "Double": { "shape": "DoubleType" }, "Long": { "shape": "LongType" }, "Char": { "shape": "CharType" }, "Timestamp": { "shape": "TimestampType" } } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BooleanType": { "type": "boolean" }, "FloatType": { "type": "float" }, "DoubleType": { "type": "double" }, "LongType": { "type": "long" }, "CharType": { "type": "character" }, "HeaderShape": { "type": "string", "location": "header" }, "StatusShape": { "type": "integer", "location": "statusCode" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Str": "myname", "Num": 123, "FalseBool": false, "TrueBool": true, "Float": 1.2, "Double": 1.3, "Long": 200, "Char": "a", "Timestamp": 1422172800 }, "response": { "status_code": 200, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "myname123falsetrue1.21.3200a2015-01-25T08:00:00Z" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Str": "", "Num": 123, "FalseBool": false, "TrueBool": true, "Float": 1.2, "Double": 1.3, "Long": 200, "Char": "a", "Timestamp": 1422172800 }, "response": { "status_code": 200, "headers": { "ImaHeader": "test", "X-Foo": "abc" }, "body": "123falsetrue1.21.3200a2015-01-25T08:00:00Z" } } ] }, { "description": "Blob", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Blob": { "shape": "BlobType" } } }, "BlobType": { "type": "blob" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Blob": "value" }, "response": { "status_code": 200, "headers": {}, "body": "dmFsdWU=" } } ] }, { "description": "Lists", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape" } } }, "ListShape": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123" } } ] }, { "description": "List with custom member name", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape" } } }, "ListShape": { "type": "list", "member": { "shape": "StringType", "locationName": "item" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123" } } ] }, { "description": "Flattened List", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "StringList", "flattened": true } } }, "StringList": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["abc", "123"] }, "response": { "status_code": 200, "headers": {}, "body": "abc123" } } ] }, { "description": "Normal map", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "StringMap" } } }, "StringMap": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "SingleStructure" } }, "SingleStructure": { "type": "structure", "members": { "foo": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": { "foo": "bar" }, "baz": { "foo": "bam" } } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbam" } } ] }, { "description": "Flattened map", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "StringMap", "flattened": true } } }, "StringMap": { "type": "map", "key": { "shape": "StringType" }, "value": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar", "baz": "bam" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbam" } } ] }, { "description": "Named map", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Map": { "shape": "StringMap" } } }, "StringMap": { "type": "map", "key": { "shape": "StringType", "locationName": "foo" }, "value": { "shape": "StringType", "locationName": "bar" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Map": { "qux": "bar", "baz": "bam" } }, "response": { "status_code": 200, "headers": {}, "body": "quxbarbazbam" } } ] }, { "description": "XML payload", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "payload": "Data", "members": { "Header": { "shape": "StringType", "location": "header", "locationName": "X-Foo" }, "Data": { "shape": "SingleStructure" } } }, "StringType": { "type": "string" }, "SingleStructure": { "type": "structure", "members": { "Foo": { "shape": "StringType" } } } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Header": "baz", "Data": { "Foo": "abc" } }, "response": { "status_code": 200, "headers": { "X-Foo": "baz" }, "body": "abc" } } ] }, { "description": "Streaming payload", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "payload": "Stream", "members": { "Stream": { "shape": "BlobStream" } } }, "BlobStream": { "type": "blob" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Stream": "abc" }, "response": { "status_code": 200, "headers": {}, "body": "abc" } } ] }, { "description": "Scalar members in headers", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Str": { "locationName": "x-str", "shape": "StringHeaderType" }, "Integer": { "locationName": "x-int", "shape": "IntegerHeaderType" }, "TrueBool": { "locationName": "x-true-bool", "shape": "BooleanHeaderType" }, "FalseBool": { "locationName": "x-false-bool", "shape": "BooleanHeaderType" }, "Float": { "locationName": "x-float", "shape": "FloatHeaderType" }, "Double": { "locationName": "x-double", "shape": "DoubleHeaderType" }, "Long": { "locationName": "x-long", "shape": "LongHeaderType" }, "Char": { "locationName": "x-char", "shape": "CharHeaderType" }, "Timestamp": { "locationName": "x-timestamp", "shape": "TimestampHeaderType" } } }, "StringHeaderType": { "location": "header", "type": "string" }, "IntegerHeaderType": { "location": "header", "type": "integer" }, "BooleanHeaderType": { "location": "header", "type": "boolean" }, "FloatHeaderType": { "location": "header", "type": "float" }, "DoubleHeaderType": { "location": "header", "type": "double" }, "LongHeaderType": { "location": "header", "type": "long" }, "CharHeaderType": { "location": "header", "type": "character" }, "TimestampHeaderType": { "location": "header", "type": "timestamp" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Str": "string", "Integer": 1, "TrueBool": true, "FalseBool": false, "Float": 1.5, "Double": 1.5, "Long": 100, "Char": "a", "Timestamp": 1422172800 }, "response": { "status_code": 200, "headers": { "x-str": "string", "x-int": "1", "x-true-bool": "true", "x-false-bool": "false", "x-float": "1.5", "x-double": "1.5", "x-long": "100", "x-char": "a", "x-timestamp": "Sun, 25 Jan 2015 08:00:00 GMT" }, "body": "" } } ] }, { "description": "Empty string", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Foo": { "shape": "StringType" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Foo": "" }, "response": { "status_code": 200, "headers": {}, "body": "requestid" } } ] }, { "description": "JSON value trait", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Attr": { "shape": "StringType", "jsonvalue": true, "location": "header", "locationName": "X-Amz-Foo" } } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Attr": {"Foo":"Bar"} }, "response": { "status_code": 200, "headers": {"X-Amz-Foo": "eyJGb28iOiJCYXIifQ=="}, "body": "" } } ] }, { "description": "Timestamp members", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "TimeArg": { "shape": "TimestampType" }, "TimeArgInHeader": { "shape": "TimestampType", "location": "header", "locationName": "x-amz-timearg" }, "TimeCustom": { "timestampFormat": "rfc822", "shape": "TimestampType" }, "TimeCustomInHeader": { "timestampFormat": "unixTimestamp", "shape": "TimestampType", "location": "header", "locationName": "x-amz-timecustom" }, "TimeFormat": { "shape": "TimestampFormatType" }, "TimeFormatInHeader": { "shape": "TimestampFormatType", "location": "header", "locationName": "x-amz-timeformat" }, "StructMember": { "shape": "TimeContainer" } } }, "TimeContainer": { "type": "structure", "members": { "foo": { "shape": "TimestampType" }, "bar": { "shape": "TimestampFormatType" } } }, "TimestampFormatType": { "timestampFormat": "unixTimestamp", "type": "timestamp" }, "TimestampType": { "type": "timestamp" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "TimeArg": 1398796238, "TimeArgInHeader": 1398796238, "TimeCustom": 1398796238, "TimeCustomInHeader": 1398796238, "TimeFormat": 1398796238, "TimeFormatInHeader": 1398796238, "StructMember": { "foo": 1398796238, "bar": 1398796238 } }, "response": { "status_code": 200, "headers": { "x-amz-timearg": "Tue, 29 Apr 2014 18:30:38 GMT", "x-amz-timecustom": "1398796238", "x-amz-timeformat": "1398796238" }, "body": "2014-04-29T18:30:38+00:0013987962382014-04-29T18:30:38+00:00Tue, 29 Apr 2014 18:30:38 GMT1398796238requestid" } } ] }, { "description": "REST XML Event Stream", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "Payload": {"shape": "EventStream"} }, "payload": "Payload" }, "EventStream": { "type": "structure", "eventstream": true, "members": { "TypeA": {"shape": "TypeAEvent"}, "TypeB": {"shape": "TypeBEvent"}, "TypeC": {"shape": "TypeCEvent"} } }, "TypeAEvent": { "type": "structure", "event": true, "members": { "Payload": { "shape": "BlobType", "eventpayload": true } } }, "TypeBEvent": { "type": "structure", "event": true, "members": { "Details": { "shape": "Details", "eventpayload": true } } }, "TypeCEvent": { "type": "structure", "event": true, "members": { "Details": { "shape": "Details", "eventpayload": true }, "Boolean": { "shape": "BooleanType", "eventheader": true }, "Integer": { "shape": "IntegerType", "eventheader": true }, "Blob": { "shape": "BlobType", "eventheader": true }, "String": { "shape": "StringType", "eventheader": true }, "Timestamp": { "shape": "TimestampType", "eventheader": true } } }, "Details": { "type": "structure", "members": { "StringField": {"shape": "StringType"}, "IntegerField": {"shape": "IntegerType"} } }, "StringType": { "type": "string" }, "IntegerType": { "type": "integer" }, "BooleanType": { "type": "boolean" }, "TimestampType": { "type": "timestamp" }, "BlobType": { "type": "blob" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Payload": [ { "TypeA": {"Payload": "somebytes"} }, { "TypeB": { "Details": { "StringField": "somestring", "IntegerField": 123 } } } ] }, "response": { "status_code": 200, "headers": {}, "body": "AAAAbAAAAFPLgkVrDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVBDTpjb250ZW50LXR5cGUHABhhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW1zb21lYnl0ZXMesj2HAAAAsAAAAEOaMMdXDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVCDTpjb250ZW50LXR5cGUHAAh0ZXh0L3htbDxUeXBlQiB4bWxucz0iIj48U3RyaW5nRmllbGQ+c29tZXN0cmluZzwvU3RyaW5nRmllbGQ+PEludGVnZXJGaWVsZD4xMjM8L0ludGVnZXJGaWVsZD48L1R5cGVCPiwthPo=" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Payload": [ { "TypeC": { "Boolean": true, "Integer": 123, "Blob": "someblob", "String": "somestring", "Timestamp": 1422172800, "Details": { "StringField": "somestring", "IntegerField": 123 } } } ] }, "response": { "status_code": 200, "headers": {}, "body": "AAABAQAAAJBjEbY4DTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVDDTpjb250ZW50LXR5cGUHAAh0ZXh0L3htbAdCb29sZWFuAAdJbnRlZ2VyBAAAAHsEQmxvYgYACHNvbWVibG9iBlN0cmluZwcACnNvbWVzdHJpbmcJVGltZXN0YW1wCAAAAUsgGsQAPERldGFpbHMgeG1sbnM9IiI+PFN0cmluZ0ZpZWxkPnNvbWVzdHJpbmc8L1N0cmluZ0ZpZWxkPjxJbnRlZ2VyRmllbGQ+MTIzPC9JbnRlZ2VyRmllbGQ+PC9EZXRhaWxzPhGUvKo=" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "Payload": [] }, "response": { "status_code": 200, "headers": {}, "body": "" } } ] }, { "description": "Modeled exceptions", "metadata": { "protocol": "rest-xml" }, "shapes": { "ExceptionShape": { "exception": true, "type": "structure", "members": { "ImaHeader": { "shape": "HeaderShape" }, "ImaHeaderLocation": { "shape": "HeaderShape", "locationName": "X-Foo" }, "Status": { "shape": "StatusShape", "location": "statusCode" }, "BodyMember": { "shape": "StringType" }, "Message": { "shape": "StringType" } } }, "OtherExceptionShape": { "exception": true, "type": "structure", "members": { "BodyMember": { "shape": "StringType" } } }, "HeaderShape": { "type": "string", "location": "header" }, "StatusShape": { "type": "integer" }, "StringType": { "type": "string" } }, "cases": [ { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "ImaHeader": "test", "ImaHeaderLocation": "abc", "Status": 400, "BodyMember": "mybody", "Message": "mymessage" }, "errorCode": "ExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Requestid": "foo-id" }, "body": "SomeTypeExceptionShapemymessagemybody" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": { "BodyMember": "mybody" }, "errorCode": "OtherExceptionShape", "errorMessage": "mymessage", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Requestid": "foo-id" }, "body": "SomeTypeOtherExceptionShapemymessagemybody" } }, { "given": { "errors": [ {"shape": "ExceptionShape"} ], "name": "OperationName" }, "error": {}, "errorCode": "UndefinedShape", "response": { "status_code": 400, "headers": { "ImaHeader": "test", "X-Foo": "abc", "X-Amzn-Requestid": "foo-id" }, "body": "SomeTypeUndefinedShapemybody" } } ] }, { "description": "Unions", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "UnionMember": { "shape": "UnionType" } } }, "UnionType": { "type": "structure", "members": { "S":{"shape":"StringType"}, "L": {"shape": "ListType"} }, "union": true }, "ListType": { "type": "list", "member": { "shape": "StringType" } }, "StringType": { "type": "string" } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "UnionMember": {"S": "string value"} }, "response": { "status_code": 200, "headers": {}, "body": "string value" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "UnionMember": {"L": ["a", "b"]} }, "response": { "status_code": 200, "headers": {}, "body": "ab" } }, { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "UnionMember": {"SDK_UNKNOWN_MEMBER": {"name": "SomeUnknownMember"}} }, "response": { "status_code": 200, "headers": {}, "body": "foo" } } ] }, { "description": "List in header", "metadata": { "protocol": "rest-xml" }, "shapes": { "OutputShape": { "type": "structure", "members": { "ListMember": { "shape": "ListShape", "location": "header", "locationName": "x-amz-list-member" } } }, "ListShape": { "type": "list", "member": { "shape": "EnumType" } }, "EnumType": { "type": "string", "enum": ["one", "two", "three"] } }, "cases": [ { "given": { "output": { "shape": "OutputShape" }, "name": "OperationName" }, "result": { "ListMember": ["one", "two", "three"] }, "response": { "status_code": 200, "headers": { "x-amz-list-member": " one,two , three " }, "body": "" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/000077500000000000000000000000001510465277300267135ustar00rootroot00000000000000aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/ec2.json000066400000000000000000002073471510465277300302740ustar00rootroot00000000000000[ { "description": "Test cases for DatetimeOffsets operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "DatetimeOffsetsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "Ec2QueryDateTimeWithNegativeOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 2019-12-16T22:48:18-01:00\n requestid\n\n" } }, { "id": "Ec2QueryDateTimeWithPositiveOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 2019-12-17T00:48:18+01:00\n requestid\n\n" } } ] }, { "description": "Test cases for EmptyInputAndEmptyOutput operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "EmptyInputAndEmptyOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "Ec2QueryEmptyInputAndEmptyOutput", "given": { "name": "EmptyInputAndEmptyOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "EmptyInputAndEmptyOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response members.

While this should be rare, code generators must support this.

" }, "description": "Empty output", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n requestid\n\n" } } ] }, { "description": "Test cases for FractionalSeconds operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "FractionalSecondsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "Ec2QueryDateTimeWithFractionalSeconds", "given": { "name": "FractionalSeconds", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FractionalSecondsOutput" } }, "description": "Ensures that clients can correctly parse datetime timestamps with fractional seconds", "result": { "datetime": 9.46845296123E8 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 2000-01-02T20:34:56.123Z\n requestid\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "GreetingWithErrorsOutput": { "type": "structure", "members": { "greeting": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "Ec2GreetingWithErrors", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "GreetingWithErrorsOutput" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
" }, "description": "Ensures that operations with errors successfully know how to deserialize the successful response", "result": { "greeting": "Hello" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n Hello\n requestid\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "Ec2InvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
", "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple XML errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n \n InvalidGreeting\n Hi\n \n \n foo-id\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "ComplexError": { "type": "structure", "members": { "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String" } } } }, "cases": [ { "id": "Ec2ComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
", "errors": [ { "shape": "ComplexError" } ] }, "errorCode": "ComplexError", "error": { "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 400, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n \n ComplexError\n Hi\n Top level\n \n bar\n \n \n \n foo-id\n\n" } } ] }, { "description": "Test cases for IgnoresWrappingXmlName operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "IgnoresWrappingXmlNameOutput": { "type": "structure", "members": { "foo": { "shape": "String" } }, "locationName": "IgnoreMe" }, "String": { "type": "string" } }, "cases": [ { "id": "Ec2IgnoresWrappingXmlName", "given": { "name": "IgnoresWrappingXmlName", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "IgnoresWrappingXmlNameOutput" }, "documentation": "

The xmlName trait on the output structure is ignored in AWS Query.

The wrapping element is always operation name + "Response".

" }, "description": "The xmlName trait on the output structure is ignored in the ec2 protocol", "result": { "foo": "bar" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n bar\n requestid\n\n" } } ] }, { "description": "Test cases for NoInputAndOutput operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "NoInputAndOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "Ec2QueryNoInputAndOutput", "given": { "name": "NoInputAndOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "NoInputAndOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request payload or response members.

While this should be rare, code generators must support this.

" }, "description": "Empty output", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n requestid\n\n" } } ] }, { "description": "Test cases for RecursiveXmlShapes operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "RecursiveXmlShapesOutput": { "type": "structure", "members": { "nested": { "shape": "RecursiveXmlShapesOutputNested1" } } }, "RecursiveXmlShapesOutputNested1": { "type": "structure", "members": { "foo": { "shape": "String" }, "nested": { "shape": "RecursiveXmlShapesOutputNested2" } } }, "String": { "type": "string" }, "RecursiveXmlShapesOutputNested2": { "type": "structure", "members": { "bar": { "shape": "String" }, "recursiveMember": { "shape": "RecursiveXmlShapesOutputNested1" } } } }, "cases": [ { "id": "Ec2RecursiveShapes", "given": { "name": "RecursiveXmlShapes", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RecursiveXmlShapesOutput" }, "documentation": "

Recursive shapes

" }, "description": "Serializes recursive structures", "result": { "nested": { "foo": "Foo1", "nested": { "bar": "Bar1", "recursiveMember": { "foo": "Foo2", "nested": { "bar": "Bar2" } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n Foo1\n \n Bar1\n \n Foo2\n \n Bar2\n \n \n \n \n requestid\n\n" } } ] }, { "description": "Test cases for SimpleScalarXmlProperties operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "SimpleScalarXmlPropertiesOutput": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "emptyStringValue": { "shape": "String" }, "trueBooleanValue": { "shape": "Boolean" }, "falseBooleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "shortValue": { "shape": "Integer" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double", "locationName": "DoubleDribble" } } }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "Ec2SimpleScalarProperties", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Serializes simple scalar properties", "result": { "stringValue": "string", "emptyStringValue": "", "trueBooleanValue": true, "falseBooleanValue": false, "byteValue": 1, "shortValue": 2, "integerValue": 3, "longValue": 4, "floatValue": 5.5, "doubleValue": 6.5 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n string\n \n true\n false\n 1\n 2\n 3\n 4\n 5.5\n 6.5\n requestid\n\n" } }, { "id": "Ec2QuerySupportsNaNFloatOutputs", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Supports handling NaN float values.", "result": { "floatValue": "NaN", "doubleValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n NaN\n NaN\n\n" } }, { "id": "Ec2QuerySupportsInfinityFloatOutputs", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Supports handling Infinity float values.", "result": { "floatValue": "Infinity", "doubleValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n Infinity\n Infinity\n\n" } }, { "id": "Ec2QuerySupportsNegativeInfinityFloatOutputs", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Supports handling -Infinity float values.", "result": { "floatValue": "-Infinity", "doubleValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n -Infinity\n -Infinity\n\n" } } ] }, { "description": "Test cases for XmlBlobs operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlBlobsOutput": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "Ec2XmlBlobs", "given": { "name": "XmlBlobs", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlBlobsOutput" }, "documentation": "

Blobs are base64 encoded

" }, "description": "Blobs are base64 encoded", "result": { "data": "value" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n dmFsdWU=\n requestid\n\n" } } ] }, { "description": "Test cases for XmlEmptyBlobs operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlBlobsOutput": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "Ec2XmlEmptyBlobs", "given": { "name": "XmlEmptyBlobs", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlBlobsOutput" } }, "description": "Empty blobs are deserialized as empty string", "result": { "data": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n requestid\n\n" } }, { "id": "Ec2XmlEmptySelfClosedBlobs", "given": { "name": "XmlEmptyBlobs", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlBlobsOutput" } }, "description": "Empty self closed blobs are deserialized as empty string", "result": { "data": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n requestid\n\n" } } ] }, { "description": "Test cases for XmlEmptyLists operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlListsOutput": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "renamedListMembers": { "shape": "RenamedListMembers", "locationName": "renamed" }, "flattenedList": { "shape": "RenamedListMembers", "flattened": true }, "flattenedList2": { "shape": "RenamedListMembers", "flattened": true, "locationName": "customName" }, "flattenedListWithMemberNamespace": { "shape": "ListWithMemberNamespace", "flattened": true }, "flattenedListWithNamespace": { "shape": "ListWithNamespace", "flattened": true }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "RenamedListMembers": { "type": "list", "member": { "shape": "String", "locationName": "item" } }, "ListWithMemberNamespace": { "type": "list", "member": { "shape": "String", "xmlNamespace": "https://xml-member.example.com" }, "xmlNamespace": "https://xml-list.example.com" }, "ListWithNamespace": { "type": "list", "member": { "shape": "String" }, "xmlNamespace": "https://xml-list.example.com" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember", "locationName": "item" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "Ec2XmlEmptyLists", "given": { "name": "XmlEmptyLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlListsOutput" } }, "description": "Deserializes empty XML lists", "result": { "stringList": [], "stringSet": [] }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n\n" } } ] }, { "description": "Test cases for XmlEnums operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlEnumsOutput": { "type": "structure", "members": { "fooEnum1": { "shape": "FooEnum" }, "fooEnum2": { "shape": "FooEnum" }, "fooEnum3": { "shape": "FooEnum" }, "fooEnumList": { "shape": "FooEnumList" }, "fooEnumSet": { "shape": "FooEnumSet" }, "fooEnumMap": { "shape": "FooEnumMap" } } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumSet": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "Ec2XmlEnums", "given": { "name": "XmlEnums", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlEnumsOutput" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

" }, "description": "Serializes simple scalar properties", "result": { "fooEnum1": "Foo", "fooEnum2": "0", "fooEnum3": "1", "fooEnumList": [ "Foo", "0" ], "fooEnumSet": [ "Foo", "0" ], "fooEnumMap": { "hi": "Foo", "zero": "0" } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n Foo\n 0\n 1\n \n Foo\n 0\n \n \n Foo\n 0\n \n \n \n hi\n Foo\n \n \n zero\n 0\n \n \n requestid\n\n" } } ] }, { "description": "Test cases for XmlIntEnums operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlIntEnumsOutput": { "type": "structure", "members": { "intEnum1": { "shape": "IntegerEnum" }, "intEnum2": { "shape": "IntegerEnum" }, "intEnum3": { "shape": "IntegerEnum" }, "intEnumList": { "shape": "IntegerEnumList" }, "intEnumSet": { "shape": "IntegerEnumSet" }, "intEnumMap": { "shape": "IntegerEnumMap" } } }, "IntegerEnum": { "type": "integer", "box": true }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumSet": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "IntegerEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "Ec2XmlIntEnums", "given": { "name": "XmlIntEnums", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlIntEnumsOutput" }, "documentation": "

This example serializes intEnums as top level properties, in lists, sets, and maps.

" }, "description": "Serializes simple scalar properties", "result": { "intEnum1": 1, "intEnum2": 2, "intEnum3": 3, "intEnumList": [ 1, 2 ], "intEnumSet": [ 1, 2 ], "intEnumMap": { "a": 1, "b": 2 } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 1\n 2\n 3\n \n 1\n 2\n \n \n 1\n 2\n \n \n \n a\n 1\n \n \n b\n 2\n \n \n requestid\n\n" } } ] }, { "description": "Test cases for XmlLists operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlListsOutput": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "renamedListMembers": { "shape": "RenamedListMembers", "locationName": "renamed" }, "flattenedList": { "shape": "RenamedListMembers", "flattened": true }, "flattenedList2": { "shape": "RenamedListMembers", "flattened": true, "locationName": "customName" }, "flattenedListWithMemberNamespace": { "shape": "ListWithMemberNamespace", "flattened": true }, "flattenedListWithNamespace": { "shape": "ListWithNamespace", "flattened": true }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "RenamedListMembers": { "type": "list", "member": { "shape": "String", "locationName": "item" } }, "ListWithMemberNamespace": { "type": "list", "member": { "shape": "String", "xmlNamespace": "https://xml-member.example.com" }, "xmlNamespace": "https://xml-list.example.com" }, "ListWithNamespace": { "type": "list", "member": { "shape": "String" }, "xmlNamespace": "https://xml-list.example.com" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember", "locationName": "item" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "Ec2XmlLists", "given": { "name": "XmlLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlListsOutput" }, "documentation": "

This test case serializes XML lists for the following cases for both input and output:

  1. Normal XML lists.
  2. Normal XML sets.
  3. XML lists of lists.
  4. XML lists with @xmlName on its members
  5. Flattened XML lists.
  6. Flattened XML lists with @xmlName.
  7. Flattened XML lists with @xmlNamespace.
  8. Lists of structures.
" }, "description": "Tests for XML list serialization", "result": { "stringList": [ "foo", "bar" ], "stringSet": [ "foo", "bar" ], "integerList": [ 1, 2 ], "booleanList": [ true, false ], "timestampList": [ 1398796238, 1398796238 ], "enumList": [ "Foo", "0" ], "intEnumList": [ 1, 2 ], "nestedStringList": [ [ "foo", "bar" ], [ "baz", "qux" ] ], "renamedListMembers": [ "foo", "bar" ], "flattenedList": [ "hi", "bye" ], "flattenedList2": [ "yep", "nope" ], "flattenedListWithMemberNamespace": [ "a", "b" ], "flattenedListWithNamespace": [ "a", "b" ], "structureList": [ { "a": "1", "b": "2" }, { "a": "3", "b": "4" } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n foo\n bar\n \n \n foo\n bar\n \n \n 1\n 2\n \n \n true\n false\n \n \n 2014-04-29T18:30:38Z\n 2014-04-29T18:30:38Z\n \n \n Foo\n 0\n \n \n 1\n 2\n \n \n \n foo\n bar\n \n \n baz\n qux\n \n \n \n foo\n bar\n \n hi\n bye\n yep\n nope\n a\n b\n a\n b\n \n \n 1\n 2\n \n \n 3\n 4\n \n \n requestid\n\n" } } ] }, { "description": "Test cases for XmlNamespaces operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlNamespacesOutput": { "type": "structure", "members": { "nested": { "shape": "XmlNamespaceNested" } }, "xmlNamespace": "http://foo.com" }, "XmlNamespaceNested": { "type": "structure", "members": { "foo": { "shape": "String", "xmlNamespace": { "prefix": "baz", "uri": "http://baz.com" } }, "values": { "shape": "XmlNamespacedList", "xmlNamespace": "http://qux.com" } }, "xmlNamespace": "http://foo.com" }, "String": { "type": "string" }, "XmlNamespacedList": { "type": "list", "member": { "shape": "String", "xmlNamespace": "http://bux.com" } } }, "cases": [ { "id": "Ec2XmlNamespaces", "given": { "name": "XmlNamespaces", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlNamespacesOutput" } }, "description": "Serializes XML namespaces", "result": { "nested": { "foo": "Foo", "values": [ "Bar", "Baz" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n \n Foo\n \n Bar\n Baz\n \n \n requestid\n\n" } } ] }, { "description": "Test cases for XmlTimestamps operation", "metadata": { "protocol": "ec2", "protocols": [ "ec2" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlTimestampsOutput": { "type": "structure", "members": { "normal": { "shape": "Timestamp" }, "dateTime": { "shape": "SyntheticTimestamp_date_time" }, "dateTimeOnTarget": { "shape": "DateTime" }, "epochSeconds": { "shape": "SyntheticTimestamp_epoch_seconds" }, "epochSecondsOnTarget": { "shape": "EpochSeconds" }, "httpDate": { "shape": "SyntheticTimestamp_http_date" }, "httpDateOnTarget": { "shape": "HttpDate" } } }, "Timestamp": { "type": "timestamp" }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "EpochSeconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "HttpDate": { "type": "timestamp", "timestampFormat": "rfc822" } }, "cases": [ { "id": "Ec2XmlTimestamps", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Tests how normal timestamps are serialized", "result": { "normal": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 2014-04-29T18:30:38Z\n requestid\n\n" } }, { "id": "Ec2XmlTimestampsWithDateTimeFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time works like normal timestamps", "result": { "dateTime": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 2014-04-29T18:30:38Z\n requestid\n\n" } }, { "id": "Ec2XmlTimestampsWithDateTimeOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time on the target shape works like normal timestamps", "result": { "dateTimeOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 2014-04-29T18:30:38Z\n requestid\n\n" } }, { "id": "Ec2XmlTimestampsWithEpochSecondsFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds works", "result": { "epochSeconds": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 1398796238\n requestid\n\n" } }, { "id": "Ec2XmlTimestampsWithEpochSecondsOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds on the target shape works", "result": { "epochSecondsOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n 1398796238\n requestid\n\n" } }, { "id": "Ec2XmlTimestampsWithHttpDateFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date works", "result": { "httpDate": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n Tue, 29 Apr 2014 18:30:38 GMT\n requestid\n\n" } }, { "id": "Ec2XmlTimestampsWithHttpDateOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date on the target shape works", "result": { "httpDateOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml;charset=UTF-8" }, "body": "\n Tue, 29 Apr 2014 18:30:38 GMT\n requestid\n\n" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/json.json000066400000000000000000002510711510465277300305650ustar00rootroot00000000000000[ { "description": "Test cases for DatetimeOffsets operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "DatetimeOffsetsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "AwsJson11DateTimeWithNegativeOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": " {\n \"datetime\": \"2019-12-16T22:48:18-01:00\"\n }\n" } }, { "id": "AwsJson11DateTimeWithPositiveOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": " {\n \"datetime\": \"2019-12-17T00:48:18+01:00\"\n }\n" } } ] }, { "description": "Test cases for EmptyOperation operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": {}, "cases": [ { "id": "handles_empty_output_shape", "given": { "name": "EmptyOperation", "http": { "method": "POST", "requestUri": "/" } }, "description": "When no output is defined, the service is expected to return\nan empty payload, however, client must ignore a JSON payload\nif one is returned. This ensures that if output is added later,\nthen it will not break the client.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{}" } }, { "id": "handles_unexpected_json_output", "given": { "name": "EmptyOperation", "http": { "method": "POST", "requestUri": "/" } }, "description": "This client-only test builds on handles_empty_output_shape,\nby including unexpected fields in the JSON. A client\nneeds to ignore JSON output that is empty or that contains\nJSON object data.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"foo\": true\n}" } }, { "id": "json_1_1_service_responds_with_no_payload", "given": { "name": "EmptyOperation", "http": { "method": "POST", "requestUri": "/" } }, "description": "When no output is defined, the service is expected to return\nan empty payload. Despite the lack of a payload, the service\nis expected to always send a Content-Type header. Clients must\nhandle cases where a service returns a JSON object and where\na service returns no JSON at all.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "" } } ] }, { "description": "Test cases for FractionalSeconds operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "FractionalSecondsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "AwsJson11DateTimeWithFractionalSeconds", "given": { "name": "FractionalSeconds", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FractionalSecondsOutput" } }, "description": "Ensures that clients can correctly parse datetime timestamps with fractional seconds", "result": { "datetime": 9.46845296123E8 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": " {\n \"datetime\": \"2000-01-02T20:34:56.123Z\"\n }\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "AwsJson11InvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple JSON errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"__type\": \"InvalidGreeting\",\n \"Message\": \"Hi\"\n}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "ComplexError": { "type": "structure", "members": { "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String" } } } }, "cases": [ { "id": "AwsJson11ComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "description": "Parses a complex error with no message member", "errorCode": "ComplexError", "error": { "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 400, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"__type\": \"ComplexError\",\n \"TopLevel\": \"Top level\",\n \"Nested\": {\n \"Foo\": \"bar\"\n }\n}" } }, { "id": "AwsJson11EmptyComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "errorCode": "ComplexError", "error": {}, "response": { "status_code": 400, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"__type\": \"ComplexError\"\n}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "FooError": { "type": "structure", "members": {}, "documentation": "

This error has test cases that test some of the dark corners of Amazon service framework history. It should only be implemented by clients.

", "exception": true, "fault": true } }, "cases": [ { "id": "AwsJson11FooErrorUsingXAmznErrorType", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Serializes the X-Amzn-ErrorType header. For an example service, see Amazon EKS.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "FooError" } } }, { "id": "AwsJson11FooErrorUsingXAmznErrorTypeWithUri", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some X-Amzn-Errortype headers contain URLs. Clients need to split the URL on ':' and take only the first half of the string. For example, 'ValidationException:http://internal.amazon.com/coral/com.amazon.coral.validate/'\nis to be interpreted as 'ValidationException'.\n\nFor an example service see Amazon Polly.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" } } }, { "id": "AwsJson11FooErrorUsingXAmznErrorTypeWithUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "X-Amzn-Errortype might contain a URL and a namespace. Client should extract only the shape name. This is a pathalogical case that might not actually happen in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" } } }, { "id": "AwsJson11FooErrorUsingCode", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "This example uses the 'code' property in the output rather than X-Amzn-Errortype. Some services do this though it's preferable to send the X-Amzn-Errortype. Client implementations must first check for the X-Amzn-Errortype and then check for a top-level 'code' property.\n\nFor example service see Amazon S3 Glacier.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"code\": \"FooError\"\n}" } }, { "id": "AwsJson11FooErrorUsingCodeAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using code, and it might contain a namespace. Clients should just take the last part of the string after '#'.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"code\": \"aws.protocoltests.restjson#FooError\"\n}" } }, { "id": "AwsJson11FooErrorUsingCodeUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using code, and it might contain a namespace. It also might contain a URI. Clients should just take the last part of the string after '#' and before \":\". This is a pathalogical case that might not occur in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"code\": \"aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/\"\n}" } }, { "id": "AwsJson11FooErrorWithDunderType", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"__type\": \"FooError\"\n}" } }, { "id": "AwsJson11FooErrorWithDunderTypeAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type, and it might contain a namespace. Clients should just take the last part of the string after '#'.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"__type\": \"aws.protocoltests.restjson#FooError\"\n}" } }, { "id": "AwsJson11FooErrorWithDunderTypeUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type, and it might contain a namespace. It also might contain a URI. Clients should just take the last part of the string after '#' and before \":\". This is a pathalogical case that might not occur in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"__type\": \"aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/\"\n}" } } ] }, { "description": "Test cases for JsonEnums operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "JsonEnumsInputOutput": { "type": "structure", "members": { "fooEnum1": { "shape": "FooEnum" }, "fooEnum2": { "shape": "FooEnum" }, "fooEnum3": { "shape": "FooEnum" }, "fooEnumList": { "shape": "FooEnumList" }, "fooEnumSet": { "shape": "FooEnumSet" }, "fooEnumMap": { "shape": "FooEnumMap" } } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumSet": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "AwsJson11Enums", "given": { "name": "JsonEnums", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonEnumsInputOutput" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "fooEnum1": "Foo", "fooEnum2": "0", "fooEnum3": "1", "fooEnumList": [ "Foo", "0" ], "fooEnumSet": [ "Foo", "0" ], "fooEnumMap": { "hi": "Foo", "zero": "0" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"fooEnum1\": \"Foo\",\n \"fooEnum2\": \"0\",\n \"fooEnum3\": \"1\",\n \"fooEnumList\": [\n \"Foo\",\n \"0\"\n ],\n \"fooEnumSet\": [\n \"Foo\",\n \"0\"\n ],\n \"fooEnumMap\": {\n \"hi\": \"Foo\",\n \"zero\": \"0\"\n }\n}" } } ] }, { "description": "Test cases for JsonIntEnums operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "JsonIntEnumsInputOutput": { "type": "structure", "members": { "intEnum1": { "shape": "IntegerEnum" }, "intEnum2": { "shape": "IntegerEnum" }, "intEnum3": { "shape": "IntegerEnum" }, "intEnumList": { "shape": "IntegerEnumList" }, "intEnumSet": { "shape": "IntegerEnumSet" }, "intEnumMap": { "shape": "IntegerEnumMap" } } }, "IntegerEnum": { "type": "integer", "box": true }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumSet": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "IntegerEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "AwsJson11IntEnums", "given": { "name": "JsonIntEnums", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonIntEnumsInputOutput" }, "documentation": "

This example serializes intEnums as top level properties, in lists, sets, and maps.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "intEnum1": 1, "intEnum2": 2, "intEnum3": 3, "intEnumList": [ 1, 2 ], "intEnumSet": [ 1, 2 ], "intEnumMap": { "a": 1, "b": 2 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1", "X-Amz-Target": "JsonProtocol.JsonIntEnums" }, "body": "{\n \"intEnum1\": 1,\n \"intEnum2\": 2,\n \"intEnum3\": 3,\n \"intEnumList\": [\n 1,\n 2\n ],\n \"intEnumSet\": [\n 1,\n 2\n ],\n \"intEnumMap\": {\n \"a\": 1,\n \"b\": 2\n }\n}" } } ] }, { "description": "Test cases for JsonUnions operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "UnionInputOutput": { "type": "structure", "members": { "contents": { "shape": "MyUnion" } }, "documentation": "

A shared structure that contains a single union member.

" }, "MyUnion": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "booleanValue": { "shape": "Boolean" }, "numberValue": { "shape": "Integer" }, "blobValue": { "shape": "Blob" }, "timestampValue": { "shape": "Timestamp" }, "enumValue": { "shape": "FooEnum" }, "listValue": { "shape": "StringList" }, "mapValue": { "shape": "StringMap" }, "structureValue": { "shape": "GreetingStruct" } }, "documentation": "

A union with a representative set of types for members.

", "union": true }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Blob": { "type": "blob" }, "Timestamp": { "type": "timestamp" }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } } }, "cases": [ { "id": "AwsJson11DeserializeStringUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a string union value", "result": { "contents": { "stringValue": "foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"stringValue\": \"foo\"\n }\n}" } }, { "id": "AwsJson11DeserializeBooleanUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a boolean union value", "result": { "contents": { "booleanValue": true } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"booleanValue\": true\n }\n}" } }, { "id": "AwsJson11DeserializeNumberUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a number union value", "result": { "contents": { "numberValue": 1 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"numberValue\": 1\n }\n}" } }, { "id": "AwsJson11DeserializeBlobUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a blob union value", "result": { "contents": { "blobValue": "foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"blobValue\": \"Zm9v\"\n }\n}" } }, { "id": "AwsJson11DeserializeTimestampUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a timestamp union value", "result": { "contents": { "timestampValue": 1398796238 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"timestampValue\": 1398796238\n }\n}" } }, { "id": "AwsJson11DeserializeEnumUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes an enum union value", "result": { "contents": { "enumValue": "Foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"enumValue\": \"Foo\"\n }\n}" } }, { "id": "AwsJson11DeserializeListUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a list union value", "result": { "contents": { "listValue": [ "foo", "bar" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"listValue\": [\"foo\", \"bar\"]\n }\n}" } }, { "id": "AwsJson11DeserializeMapUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a map union value", "result": { "contents": { "mapValue": { "foo": "bar", "spam": "eggs" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"mapValue\": {\n \"foo\": \"bar\",\n \"spam\": \"eggs\"\n }\n }\n}" } }, { "id": "AwsJson11DeserializeStructureUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a structure union value", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } }, { "id": "AwsJson11DeserializeIgnoreType", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Ignores an unrecognized __type property", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"contents\": {\n \"__type\": \"aws.protocoltests.json10#MyUnion\",\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } } ] }, { "description": "Test cases for KitchenSinkOperation operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "KitchenSink": { "type": "structure", "members": { "Blob": { "shape": "Blob" }, "Boolean": { "shape": "Boolean" }, "Double": { "shape": "Double" }, "EmptyStruct": { "shape": "EmptyStruct" }, "Float": { "shape": "Float" }, "HttpdateTimestamp": { "shape": "SyntheticTimestamp_http_date" }, "Integer": { "shape": "Integer" }, "Iso8601Timestamp": { "shape": "SyntheticTimestamp_date_time" }, "JsonValue": { "shape": "JsonValue", "jsonvalue": true }, "ListOfLists": { "shape": "ListOfListOfStrings" }, "ListOfMapsOfStrings": { "shape": "ListOfMapsOfStrings" }, "ListOfStrings": { "shape": "ListOfStrings" }, "ListOfStructs": { "shape": "ListOfStructs" }, "Long": { "shape": "Long" }, "MapOfListsOfStrings": { "shape": "MapOfListsOfStrings" }, "MapOfMaps": { "shape": "MapOfMapOfStrings" }, "MapOfStrings": { "shape": "MapOfStrings" }, "MapOfStructs": { "shape": "MapOfStructs" }, "RecursiveList": { "shape": "ListOfKitchenSinks" }, "RecursiveMap": { "shape": "MapOfKitchenSinks" }, "RecursiveStruct": { "shape": "KitchenSink" }, "SimpleStruct": { "shape": "SimpleStruct" }, "String": { "shape": "String" }, "StructWithJsonName": { "shape": "StructWithJsonName" }, "Timestamp": { "shape": "Timestamp" }, "UnixTimestamp": { "shape": "SyntheticTimestamp_epoch_seconds" } } }, "Blob": { "type": "blob" }, "Boolean": { "type": "boolean", "box": true }, "Double": { "type": "double", "box": true }, "EmptyStruct": { "type": "structure", "members": {} }, "Float": { "type": "float", "box": true }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "Integer": { "type": "integer", "box": true }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "JsonValue": { "type": "string" }, "ListOfListOfStrings": { "type": "list", "member": { "shape": "ListOfStrings" } }, "ListOfMapsOfStrings": { "type": "list", "member": { "shape": "MapOfStrings" } }, "ListOfStrings": { "type": "list", "member": { "shape": "String" } }, "ListOfStructs": { "type": "list", "member": { "shape": "SimpleStruct" } }, "Long": { "type": "long", "box": true }, "MapOfListsOfStrings": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "ListOfStrings" } }, "MapOfMapOfStrings": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "MapOfStrings" } }, "MapOfStrings": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "MapOfStructs": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "SimpleStruct" } }, "ListOfKitchenSinks": { "type": "list", "member": { "shape": "KitchenSink" } }, "MapOfKitchenSinks": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "KitchenSink" } }, "SimpleStruct": { "type": "structure", "members": { "Value": { "shape": "String" } } }, "String": { "type": "string" }, "StructWithJsonName": { "type": "structure", "members": { "Value": { "shape": "String" } } }, "Timestamp": { "type": "timestamp" }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" } }, "cases": [ { "id": "parses_operations_with_empty_json_bodies", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses operations with empty JSON bodies", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{}" } }, { "id": "parses_string_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses string shapes", "result": { "String": "string-value" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"String\":\"string-value\"}" } }, { "id": "parses_integer_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses integer shapes", "result": { "Integer": 1234 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Integer\":1234}" } }, { "id": "parses_long_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses long shapes", "result": { "Long": 1234567890123456789 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Long\":1234567890123456789}" } }, { "id": "parses_float_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses float shapes", "result": { "Float": 1234.5 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Float\":1234.5}" } }, { "id": "parses_double_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses double shapes", "result": { "Double": 1.2345678912345679E8 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Double\":123456789.12345679}" } }, { "id": "parses_boolean_shapes_true", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses boolean shapes (true)", "result": { "Boolean": true }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Boolean\":true}" } }, { "id": "parses_boolean_false", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses boolean (false)", "result": { "Boolean": false }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Boolean\":false}" } }, { "id": "parses_blob_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses blob shapes", "result": { "Blob": "binary-value" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Blob\":\"YmluYXJ5LXZhbHVl\"}" } }, { "id": "parses_timestamp_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses timestamp shapes", "result": { "Timestamp": 946845296 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Timestamp\":946845296}" } }, { "id": "parses_iso8601_timestamps", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses iso8601 timestamps", "result": { "Iso8601Timestamp": 946845296 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"Iso8601Timestamp\":\"2000-01-02T20:34:56Z\"}" } }, { "id": "parses_httpdate_timestamps", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses httpdate timestamps", "result": { "HttpdateTimestamp": 946845296 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"HttpdateTimestamp\":\"Sun, 02 Jan 2000 20:34:56 GMT\"}" } }, { "id": "parses_list_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses list shapes", "result": { "ListOfStrings": [ "abc", "mno", "xyz" ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"ListOfStrings\":[\"abc\",\"mno\",\"xyz\"]}" } }, { "id": "parses_list_of_map_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses list of map shapes", "result": { "ListOfMapsOfStrings": [ { "size": "large" }, { "color": "red" } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"ListOfMapsOfStrings\":[{\"size\":\"large\"},{\"color\":\"red\"}]}" } }, { "id": "parses_list_of_list_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses list of list shapes", "result": { "ListOfLists": [ [ "abc", "mno", "xyz" ], [ "hjk", "qrs", "tuv" ] ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"ListOfLists\":[[\"abc\",\"mno\",\"xyz\"],[\"hjk\",\"qrs\",\"tuv\"]]}" } }, { "id": "parses_list_of_structure_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses list of structure shapes", "result": { "ListOfStructs": [ { "Value": "value-1" }, { "Value": "value-2" } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"ListOfStructs\":[{\"Value\":\"value-1\"},{\"Value\":\"value-2\"}]}" } }, { "id": "parses_list_of_recursive_structure_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses list of recursive structure shapes", "result": { "RecursiveList": [ { "RecursiveList": [ { "RecursiveList": [ { "String": "value" } ] } ] } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"RecursiveList\":[{\"RecursiveList\":[{\"RecursiveList\":[{\"String\":\"value\"}]}]}]}" } }, { "id": "parses_map_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses map shapes", "result": { "MapOfStrings": { "size": "large", "color": "red" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"MapOfStrings\":{\"size\":\"large\",\"color\":\"red\"}}" } }, { "id": "parses_map_of_list_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses map of list shapes", "result": { "MapOfListsOfStrings": { "sizes": [ "large", "small" ], "colors": [ "red", "green" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"MapOfListsOfStrings\":{\"sizes\":[\"large\",\"small\"],\"colors\":[\"red\",\"green\"]}}" } }, { "id": "parses_map_of_map_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses map of map shapes", "result": { "MapOfMaps": { "sizes": { "large": "L", "medium": "M" }, "colors": { "red": "R", "blue": "B" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"MapOfMaps\":{\"sizes\":{\"large\":\"L\",\"medium\":\"M\"},\"colors\":{\"red\":\"R\",\"blue\":\"B\"}}}" } }, { "id": "parses_map_of_structure_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses map of structure shapes", "result": { "MapOfStructs": { "size": { "Value": "small" }, "color": { "Value": "red" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"MapOfStructs\":{\"size\":{\"Value\":\"small\"},\"color\":{\"Value\":\"red\"}}}" } }, { "id": "parses_map_of_recursive_structure_shapes", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses map of recursive structure shapes", "result": { "RecursiveMap": { "key-1": { "RecursiveMap": { "key-2": { "RecursiveMap": { "key-3": { "String": "value" } } } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\"RecursiveMap\":{\"key-1\":{\"RecursiveMap\":{\"key-2\":{\"RecursiveMap\":{\"key-3\":{\"String\":\"value\"}}}}}}}" } }, { "id": "parses_the_request_id_from_the_response", "given": { "name": "KitchenSinkOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "KitchenSink" } }, "description": "Parses the request id from the response", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1", "X-Amzn-Requestid": "amazon-uniq-request-id" }, "body": "{}" } } ] }, { "description": "Test cases for NullOperation operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "NullOperationInputOutput": { "type": "structure", "members": { "string": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "AwsJson11StructuresDontDeserializeNullValues", "given": { "name": "NullOperation", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "NullOperationInputOutput" } }, "description": "Null structure values are dropped", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"string\": null\n}" } } ] }, { "description": "Test cases for PutAndGetInlineDocuments operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "PutAndGetInlineDocumentsInputOutput": { "type": "structure", "members": { "inlineDocument": { "shape": "Document" } } }, "Document": { "type": "structure", "members": {}, "document": true } }, "cases": [ { "id": "PutAndGetInlineDocumentsInput", "given": { "name": "PutAndGetInlineDocuments", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "PutAndGetInlineDocumentsInputOutput" }, "documentation": "

This example serializes an inline document as part of the payload.

" }, "description": "Serializes inline documents in a JSON response.", "result": { "inlineDocument": { "foo": "bar" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"inlineDocument\": {\"foo\": \"bar\"}\n}" } } ] }, { "description": "Test cases for SimpleScalarProperties operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2018-01-01", "jsonVersion": "1.1", "targetPrefix": "JsonProtocol" }, "shapes": { "SimpleScalarPropertiesInputOutput": { "type": "structure", "members": { "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double" } } }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "AwsJson11SupportsNaNFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarPropertiesInputOutput" } }, "description": "Supports handling NaN float values.", "result": { "floatValue": "NaN", "doubleValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"floatValue\": \"NaN\",\n \"doubleValue\": \"NaN\"\n}" } }, { "id": "AwsJson11SupportsInfinityFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarPropertiesInputOutput" } }, "description": "Supports handling Infinity float values.", "result": { "floatValue": "Infinity", "doubleValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"floatValue\": \"Infinity\",\n \"doubleValue\": \"Infinity\"\n}" } }, { "id": "AwsJson11SupportsNegativeInfinityFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarPropertiesInputOutput" } }, "description": "Supports handling -Infinity float values.", "result": { "floatValue": "-Infinity", "doubleValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.1" }, "body": "{\n \"floatValue\": \"-Infinity\",\n \"doubleValue\": \"-Infinity\"\n}" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/json_1_0.json000066400000000000000000001366231510465277300312310ustar00rootroot00000000000000[ { "description": "Test cases for EmptyInputAndEmptyOutput operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "EmptyInputAndEmptyOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "AwsJson10EmptyInputAndEmptyOutputSendJsonObject", "given": { "name": "EmptyInputAndEmptyOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "EmptyInputAndEmptyOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has an empty input and empty output structure that reuses the same shape. While this should be rare, code generators must support this.

" }, "description": "A service will always return a JSON object for operations with modeled output.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "AwsJson10InvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple JSON errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"__type\": \"aws.protocoltests.json10#InvalidGreeting\",\n \"Message\": \"Hi\"\n}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "FooError": { "type": "structure", "members": {}, "documentation": "

This error has test cases that test some of the dark corners of Amazon service framework history. It should only be implemented by clients.

", "exception": true, "fault": true } }, "cases": [ { "id": "AwsJson10FooErrorUsingXAmznErrorType", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Serializes the X-Amzn-ErrorType header. For an example service, see Amazon EKS.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "FooError" } } }, { "id": "AwsJson10FooErrorUsingXAmznErrorTypeWithUri", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some X-Amzn-Errortype headers contain URLs. Clients need to split the URL on ':' and take only the first half of the string. For example, 'ValidationException:http://internal.amazon.com/coral/com.amazon.coral.validate/'\nis to be interpreted as 'ValidationException'.\n\nFor an example service see Amazon Polly.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" } } }, { "id": "AwsJson10FooErrorUsingXAmznErrorTypeWithUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "X-Amzn-Errortype might contain a URL and a namespace. Client should extract only the shape name. This is a pathalogical case that might not actually happen in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "aws.protocoltests.json10#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" } } }, { "id": "AwsJson10FooErrorUsingCode", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "This example uses the 'code' property in the output rather than X-Amzn-Errortype. Some services do this though it's preferable to send the X-Amzn-Errortype. Client implementations must first check for the X-Amzn-Errortype and then check for a top-level 'code' property.\n\nFor example service see Amazon S3 Glacier.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"code\": \"FooError\"\n}" } }, { "id": "AwsJson10FooErrorUsingCodeAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using code, and it might contain a namespace. Clients should just take the last part of the string after '#'.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"code\": \"aws.protocoltests.json10#FooError\"\n}" } }, { "id": "AwsJson10FooErrorUsingCodeUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using code, and it might contain a namespace. It also might contain a URI. Clients should just take the last part of the string after '#' and before \":\". This is a pathalogical case that might not occur in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"code\": \"aws.protocoltests.json10#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/\"\n}" } }, { "id": "AwsJson10FooErrorWithDunderType", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"__type\": \"FooError\"\n}" } }, { "id": "AwsJson10FooErrorWithDunderTypeAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type, and it might contain a namespace. Clients should just take the last part of the string after '#'.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"__type\": \"aws.protocoltests.json10#FooError\"\n}" } }, { "id": "AwsJson10FooErrorWithDunderTypeUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type, and it might contain a namespace. It also might contain a URI. Clients should just take the last part of the string after '#' and before \":\". This is a pathalogical case that might not occur in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"__type\": \"aws.protocoltests.json10#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/\"\n}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "ComplexError": { "type": "structure", "members": { "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String" } } } }, "cases": [ { "id": "AwsJson10ComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "description": "Parses a complex error with no message member", "errorCode": "ComplexError", "error": { "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 400, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"__type\": \"aws.protocoltests.json10#ComplexError\",\n \"TopLevel\": \"Top level\",\n \"Nested\": {\n \"Foo\": \"bar\"\n }\n}" } }, { "id": "AwsJson10EmptyComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "description": "Parses a complex error with an empty body", "errorCode": "ComplexError", "error": {}, "response": { "status_code": 400, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"__type\": \"aws.protocoltests.json10#ComplexError\"\n}" } } ] }, { "description": "Test cases for JsonUnions operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "JsonUnionsOutput": { "type": "structure", "members": { "contents": { "shape": "MyUnion" } } }, "MyUnion": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "booleanValue": { "shape": "Boolean" }, "numberValue": { "shape": "Integer" }, "blobValue": { "shape": "Blob" }, "timestampValue": { "shape": "Timestamp" }, "enumValue": { "shape": "FooEnum" }, "intEnumValue": { "shape": "IntegerEnum" }, "listValue": { "shape": "StringList" }, "mapValue": { "shape": "StringMap" }, "structureValue": { "shape": "GreetingStruct" } }, "documentation": "

A union with a representative set of types for members.

", "union": true }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Blob": { "type": "blob" }, "Timestamp": { "type": "timestamp" }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "IntegerEnum": { "type": "integer", "box": true }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } } }, "cases": [ { "id": "AwsJson10DeserializeStringUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a string union value", "result": { "contents": { "stringValue": "foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"stringValue\": \"foo\"\n }\n}" } }, { "id": "AwsJson10DeserializeBooleanUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a boolean union value", "result": { "contents": { "booleanValue": true } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"booleanValue\": true\n }\n}" } }, { "id": "AwsJson10DeserializeNumberUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a number union value", "result": { "contents": { "numberValue": 1 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"numberValue\": 1\n }\n}" } }, { "id": "AwsJson10DeserializeBlobUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a blob union value", "result": { "contents": { "blobValue": "foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"blobValue\": \"Zm9v\"\n }\n}" } }, { "id": "AwsJson10DeserializeTimestampUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a timestamp union value", "result": { "contents": { "timestampValue": 1398796238 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"timestampValue\": 1398796238\n }\n}" } }, { "id": "AwsJson10DeserializeEnumUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes an enum union value", "result": { "contents": { "enumValue": "Foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"enumValue\": \"Foo\"\n }\n}" } }, { "id": "AwsJson10DeserializeIntEnumUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes an intEnum union value", "result": { "contents": { "intEnumValue": 1 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"intEnumValue\": 1\n }\n}" } }, { "id": "AwsJson10DeserializeListUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a list union value", "result": { "contents": { "listValue": [ "foo", "bar" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"listValue\": [\"foo\", \"bar\"]\n }\n}" } }, { "id": "AwsJson10DeserializeMapUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a map union value", "result": { "contents": { "mapValue": { "foo": "bar", "spam": "eggs" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"mapValue\": {\n \"foo\": \"bar\",\n \"spam\": \"eggs\"\n }\n }\n}" } }, { "id": "AwsJson10DeserializeStructureUnionValue", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a structure union value", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } }, { "id": "AwsJson10DeserializeIgnoreType", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Ignores an unrecognized __type property", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"__type\": \"aws.protocoltests.json10#MyUnion\",\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } }, { "id": "AwsJson10DeserializeAllowNulls", "given": { "name": "JsonUnions", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "JsonUnionsOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Allows for `: null` to be set for all unset fields", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"contents\": {\n \"stringValue\": null,\n \"booleanValue\": null,\n \"numberValue\": null,\n \"blobValue\": null,\n \"timestampValue\": null,\n \"enumValue\": null,\n \"intEnumValue\": null,\n \"listValue\": null,\n \"mapValue\": null,\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } } ] }, { "description": "Test cases for NoInputAndNoOutput operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": {}, "cases": [ { "id": "AwsJson10HandlesEmptyOutputShape", "given": { "name": "NoInputAndNoOutput", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input or output. While this should be rare, code generators must support this.

" }, "description": "When no output is defined, the service is expected to return\nan empty payload, however, client must ignore a JSON payload\nif one is returned. This ensures that if output is added later,\nthen it will not break the client.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{}" } }, { "id": "AwsJson10HandlesUnexpectedJsonOutput", "given": { "name": "NoInputAndNoOutput", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input or output. While this should be rare, code generators must support this.

" }, "description": "This client-only test builds on handles_empty_output_shape,\nby including unexpected fields in the JSON. A client\nneeds to ignore JSON output that is empty or that contains\nJSON object data.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"foo\": true\n}" } }, { "id": "AwsJson10ServiceRespondsWithNoPayload", "given": { "name": "NoInputAndNoOutput", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input or output. While this should be rare, code generators must support this.

" }, "description": "When no output is defined, the service is expected to return\nan empty payload. Despite the lack of a payload, the service\nis expected to always send a Content-Type header. Clients must\nhandle cases where a service returns a JSON object and where\na service returns no JSON at all.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "" } } ] }, { "description": "Test cases for NoInputAndOutput operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "NoInputAndOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "AwsJson10NoInputAndOutput", "given": { "name": "NoInputAndOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "NoInputAndOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input and the output is empty. While this should be rare, code generators must support this.

" }, "description": "Empty output always serializes an empty object payload.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{}" } } ] }, { "description": "Test cases for SimpleScalarProperties operation", "metadata": { "protocol": "json", "protocols": [ "json" ], "apiVersion": "2020-07-14", "jsonVersion": "1.0", "targetPrefix": "JsonRpc10" }, "shapes": { "SimpleScalarPropertiesOutput": { "type": "structure", "members": { "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double" } } }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "AwsJson10SupportsNaNFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarPropertiesOutput" } }, "description": "Supports handling NaN float values.", "result": { "floatValue": "NaN", "doubleValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"floatValue\": \"NaN\",\n \"doubleValue\": \"NaN\"\n}" } }, { "id": "AwsJson10SupportsInfinityFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarPropertiesOutput" } }, "description": "Supports handling Infinity float values.", "result": { "floatValue": "Infinity", "doubleValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"floatValue\": \"Infinity\",\n \"doubleValue\": \"Infinity\"\n}" } }, { "id": "AwsJson10SupportsNegativeInfinityFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarPropertiesOutput" } }, "description": "Supports handling -Infinity float values.", "result": { "floatValue": "-Infinity", "doubleValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/x-amz-json-1.0" }, "body": "{\n \"floatValue\": \"-Infinity\",\n \"doubleValue\": \"-Infinity\"\n}" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/query.json000066400000000000000000002563131510465277300307650ustar00rootroot00000000000000[ { "description": "Test cases for DatetimeOffsets operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "DatetimeOffsetsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "AwsQueryDateTimeWithNegativeOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 2019-12-16T22:48:18-01:00\n \n\n" } }, { "id": "AwsQueryDateTimeWithPositiveOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 2019-12-17T00:48:18+01:00\n \n\n" } } ] }, { "description": "Test cases for EmptyInputAndEmptyOutput operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "EmptyInputAndEmptyOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "QueryEmptyInputAndEmptyOutput", "given": { "name": "EmptyInputAndEmptyOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "EmptyInputAndEmptyOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response members.

While this should be rare, code generators must support this.

" }, "description": "Empty output", "result": {}, "response": { "status_code": 200 } } ] }, { "description": "Test cases for FlattenedXmlMap operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "FlattenedXmlMapOutput": { "type": "structure", "members": { "myMap": { "shape": "FooEnumMap", "flattened": true } } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "String": { "type": "string" } }, "cases": [ { "id": "QueryQueryFlattenedXmlMap", "given": { "name": "FlattenedXmlMap", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FlattenedXmlMapOutput" }, "documentation": "

Flattened maps

" }, "description": "Serializes flattened XML maps in responses", "result": { "myMap": { "foo": "Foo", "baz": "Baz" } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n foo\n Foo\n \n \n baz\n Baz\n \n \n" } } ] }, { "description": "Test cases for FlattenedXmlMapWithXmlName operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "FlattenedXmlMapWithXmlNameOutput": { "type": "structure", "members": { "myMap": { "shape": "FlattenedXmlMapWithXmlNameOutputMap", "flattened": true, "locationName": "KVP" } } }, "FlattenedXmlMapWithXmlNameOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "K" }, "value": { "shape": "String", "locationName": "V" } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryQueryFlattenedXmlMapWithXmlName", "given": { "name": "FlattenedXmlMapWithXmlName", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FlattenedXmlMapWithXmlNameOutput" }, "documentation": "

Flattened maps with @xmlName

" }, "description": "Serializes flattened XML maps in responses that have xmlName on members", "result": { "myMap": { "a": "A", "b": "B" } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n a\n A\n \n \n b\n B\n \n \n" } } ] }, { "description": "Test cases for FlattenedXmlMapWithXmlNamespace operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "FlattenedXmlMapWithXmlNamespaceOutput": { "type": "structure", "members": { "myMap": { "shape": "FlattenedXmlMapWithXmlNamespaceOutputMap", "flattened": true, "locationName": "KVP", "xmlNamespace": "https://the-member.example.com" } } }, "FlattenedXmlMapWithXmlNamespaceOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "K", "xmlNamespace": "https://the-key.example.com" }, "value": { "shape": "String", "locationName": "V", "xmlNamespace": "https://the-value.example.com" } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryQueryFlattenedXmlMapWithXmlNamespace", "given": { "name": "FlattenedXmlMapWithXmlNamespace", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FlattenedXmlMapWithXmlNamespaceOutput" }, "documentation": "

Flattened maps with @xmlNamespace and @xmlName

" }, "description": "Serializes flattened XML maps in responses that have xmlNamespace and xmlName on members", "result": { "myMap": { "a": "A", "b": "B" } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n a\n A\n \n \n b\n B\n \n \n" } } ] }, { "description": "Test cases for FractionalSeconds operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "FractionalSecondsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "AwsQueryDateTimeWithFractionalSeconds", "given": { "name": "FractionalSeconds", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FractionalSecondsOutput" } }, "description": "Ensures that clients can correctly parse datetime timestamps with fractional seconds", "result": { "datetime": 9.46845296123E8 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 2000-01-02T20:34:56.123Z\n \n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "GreetingWithErrorsOutput": { "type": "structure", "members": { "greeting": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryGreetingWithErrors", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "GreetingWithErrorsOutput" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
" }, "description": "Ensures that operations with errors successfully know how to deserialize the successful response", "result": { "greeting": "Hello" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Hello\n \n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "QueryInvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
", "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple XML errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Sender\n InvalidGreeting\n Hi\n \n foo-id\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "CustomCodeError": { "type": "structure", "members": { "Message": { "shape": "String" } }, "error": { "code": "Customized", "httpStatusCode": 402, "senderFault": true }, "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "QueryCustomizedError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
", "errors": [ { "shape": "CustomCodeError" } ] }, "description": "Parses customized XML errors", "errorCode": "Customized", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 402, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Sender\n Customized\n Hi\n \n foo-id\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "ComplexError": { "type": "structure", "members": { "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String" } } } }, "cases": [ { "id": "QueryComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
", "errors": [ { "shape": "ComplexError" } ] }, "errorCode": "ComplexError", "error": { "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 400, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Sender\n ComplexError\n Top level\n \n bar\n \n \n foo-id\n\n" } } ] }, { "description": "Test cases for IgnoresWrappingXmlName operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "IgnoresWrappingXmlNameOutput": { "type": "structure", "members": { "foo": { "shape": "String" } }, "locationName": "IgnoreMe" }, "String": { "type": "string" } }, "cases": [ { "id": "QueryIgnoresWrappingXmlName", "given": { "name": "IgnoresWrappingXmlName", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "IgnoresWrappingXmlNameOutput" }, "documentation": "

The xmlName trait on the output structure is ignored in AWS Query.

The wrapping element is always operation name + "Response", and inside of that wrapper is another wrapper named operation name + "Result".

" }, "description": "The xmlName trait on the output structure is ignored in AWS Query", "result": { "foo": "bar" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n bar\n \n\n" } } ] }, { "description": "Test cases for NoInputAndNoOutput operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": {}, "cases": [ { "id": "QueryNoInputAndNoOutput", "given": { "name": "NoInputAndNoOutput", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input or output.

While this should be rare, code generators must support this.

" }, "description": "Empty output. Note that no assertion is made on the output body itself.", "result": {}, "response": { "status_code": 200 } } ] }, { "description": "Test cases for NoInputAndOutput operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "NoInputAndOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "QueryNoInputAndOutput", "given": { "name": "NoInputAndOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "NoInputAndOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request payload or response members.

While this should be rare, code generators must support this.

" }, "description": "Empty output", "result": {}, "response": { "status_code": 200 } } ] }, { "description": "Test cases for RecursiveXmlShapes operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "RecursiveXmlShapesOutput": { "type": "structure", "members": { "nested": { "shape": "RecursiveXmlShapesOutputNested1" } } }, "RecursiveXmlShapesOutputNested1": { "type": "structure", "members": { "foo": { "shape": "String" }, "nested": { "shape": "RecursiveXmlShapesOutputNested2" } } }, "String": { "type": "string" }, "RecursiveXmlShapesOutputNested2": { "type": "structure", "members": { "bar": { "shape": "String" }, "recursiveMember": { "shape": "RecursiveXmlShapesOutputNested1" } } } }, "cases": [ { "id": "QueryRecursiveShapes", "given": { "name": "RecursiveXmlShapes", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RecursiveXmlShapesOutput" }, "documentation": "

Recursive shapes

" }, "description": "Serializes recursive structures", "result": { "nested": { "foo": "Foo1", "nested": { "bar": "Bar1", "recursiveMember": { "foo": "Foo2", "nested": { "bar": "Bar2" } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n Foo1\n \n Bar1\n \n Foo2\n \n Bar2\n \n \n \n \n \n\n" } } ] }, { "description": "Test cases for SimpleScalarXmlProperties operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "SimpleScalarXmlPropertiesOutput": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "emptyStringValue": { "shape": "String" }, "trueBooleanValue": { "shape": "Boolean" }, "falseBooleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "shortValue": { "shape": "Integer" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double", "locationName": "DoubleDribble" } } }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "QuerySimpleScalarProperties", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Serializes simple scalar properties", "result": { "stringValue": "string", "emptyStringValue": "", "trueBooleanValue": true, "falseBooleanValue": false, "byteValue": 1, "shortValue": 2, "integerValue": 3, "longValue": 4, "floatValue": 5.5, "doubleValue": 6.5 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n string\n \n true\n false\n 1\n 2\n 3\n 4\n 5.5\n 6.5\n \n\n" } }, { "id": "AwsQuerySupportsNaNFloatOutputs", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Supports handling NaN float values.", "result": { "floatValue": "NaN", "doubleValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n NaN\n NaN\n \n\n" } }, { "id": "AwsQuerySupportsInfinityFloatOutputs", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Supports handling Infinity float values.", "result": { "floatValue": "Infinity", "doubleValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Infinity\n Infinity\n \n\n" } }, { "id": "AwsQuerySupportsNegativeInfinityFloatOutputs", "given": { "name": "SimpleScalarXmlProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarXmlPropertiesOutput" } }, "description": "Supports handling -Infinity float values.", "result": { "floatValue": "-Infinity", "doubleValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n -Infinity\n -Infinity\n \n\n" } } ] }, { "description": "Test cases for XmlBlobs operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlBlobsOutput": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "QueryXmlBlobs", "given": { "name": "XmlBlobs", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlBlobsOutput" }, "documentation": "

Blobs are base64 encoded

" }, "description": "Blobs are base64 encoded", "result": { "data": "value" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n dmFsdWU=\n \n\n" } } ] }, { "description": "Test cases for XmlEmptyBlobs operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlBlobsOutput": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "QueryXmlEmptyBlobs", "given": { "name": "XmlEmptyBlobs", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlBlobsOutput" } }, "description": "Empty blobs are deserialized as empty string", "result": { "data": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n\n" } }, { "id": "QueryXmlEmptySelfClosedBlobs", "given": { "name": "XmlEmptyBlobs", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlBlobsOutput" } }, "description": "Empty self closed blobs are deserialized as empty string", "result": { "data": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlEmptyLists operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlListsOutput": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "renamedListMembers": { "shape": "RenamedListMembers", "locationName": "renamed" }, "flattenedList": { "shape": "RenamedListMembers", "flattened": true }, "flattenedList2": { "shape": "RenamedListMembers", "flattened": true, "locationName": "customName" }, "flattenedListWithMemberNamespace": { "shape": "ListWithMemberNamespace", "flattened": true }, "flattenedListWithNamespace": { "shape": "ListWithNamespace", "flattened": true }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "RenamedListMembers": { "type": "list", "member": { "shape": "String", "locationName": "item" } }, "ListWithMemberNamespace": { "type": "list", "member": { "shape": "String", "xmlNamespace": "https://xml-member.example.com" }, "xmlNamespace": "https://xml-list.example.com" }, "ListWithNamespace": { "type": "list", "member": { "shape": "String" }, "xmlNamespace": "https://xml-list.example.com" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember", "locationName": "item" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "QueryXmlEmptyLists", "given": { "name": "XmlEmptyLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlListsOutput" } }, "description": "Deserializes empty XML lists", "result": { "stringList": [], "stringSet": [] }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n \n\n" } } ] }, { "description": "Test cases for XmlEmptyMaps operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlMapsOutput": { "type": "structure", "members": { "myMap": { "shape": "XmlMapsOutputMap" } } }, "XmlMapsOutputMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "GreetingStruct" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryXmlEmptyMaps", "given": { "name": "XmlEmptyMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlMapsOutput" } }, "description": "Deserializes Empty XML maps", "result": { "myMap": {} }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n \n\n" } }, { "id": "QueryXmlEmptySelfClosedMaps", "given": { "name": "XmlEmptyMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlMapsOutput" } }, "description": "Deserializes Self-Closed XML maps", "result": { "myMap": {} }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlEnums operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlEnumsOutput": { "type": "structure", "members": { "fooEnum1": { "shape": "FooEnum" }, "fooEnum2": { "shape": "FooEnum" }, "fooEnum3": { "shape": "FooEnum" }, "fooEnumList": { "shape": "FooEnumList" }, "fooEnumSet": { "shape": "FooEnumSet" }, "fooEnumMap": { "shape": "FooEnumMap" } } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumSet": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryXmlEnums", "given": { "name": "XmlEnums", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlEnumsOutput" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

" }, "description": "Serializes simple scalar properties", "result": { "fooEnum1": "Foo", "fooEnum2": "0", "fooEnum3": "1", "fooEnumList": [ "Foo", "0" ], "fooEnumSet": [ "Foo", "0" ], "fooEnumMap": { "hi": "Foo", "zero": "0" } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Foo\n 0\n 1\n \n Foo\n 0\n \n \n Foo\n 0\n \n \n \n hi\n Foo\n \n \n zero\n 0\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlIntEnums operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlIntEnumsOutput": { "type": "structure", "members": { "intEnum1": { "shape": "IntegerEnum" }, "intEnum2": { "shape": "IntegerEnum" }, "intEnum3": { "shape": "IntegerEnum" }, "intEnumList": { "shape": "IntegerEnumList" }, "intEnumSet": { "shape": "IntegerEnumSet" }, "intEnumMap": { "shape": "IntegerEnumMap" } } }, "IntegerEnum": { "type": "integer", "box": true }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumSet": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "IntegerEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryXmlIntEnums", "given": { "name": "XmlIntEnums", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlIntEnumsOutput" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

" }, "description": "Serializes simple scalar properties", "result": { "intEnum1": 1, "intEnum2": 2, "intEnum3": 3, "intEnumList": [ 1, 2 ], "intEnumSet": [ 1, 2 ], "intEnumMap": { "a": 1, "b": 2 } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 1\n 2\n 3\n \n 1\n 2\n \n \n 1\n 2\n \n \n \n a\n 1\n \n \n b\n 2\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlLists operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlListsOutput": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "renamedListMembers": { "shape": "RenamedListMembers", "locationName": "renamed" }, "flattenedList": { "shape": "RenamedListMembers", "flattened": true }, "flattenedList2": { "shape": "RenamedListMembers", "flattened": true, "locationName": "customName" }, "flattenedListWithMemberNamespace": { "shape": "ListWithMemberNamespace", "flattened": true }, "flattenedListWithNamespace": { "shape": "ListWithNamespace", "flattened": true }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "RenamedListMembers": { "type": "list", "member": { "shape": "String", "locationName": "item" } }, "ListWithMemberNamespace": { "type": "list", "member": { "shape": "String", "xmlNamespace": "https://xml-member.example.com" }, "xmlNamespace": "https://xml-list.example.com" }, "ListWithNamespace": { "type": "list", "member": { "shape": "String" }, "xmlNamespace": "https://xml-list.example.com" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember", "locationName": "item" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "QueryXmlLists", "given": { "name": "XmlLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlListsOutput" }, "documentation": "

This test case serializes XML lists for the following cases for both input and output:

  1. Normal XML lists.
  2. Normal XML sets.
  3. XML lists of lists.
  4. XML lists with @xmlName on its members
  5. Flattened XML lists.
  6. Flattened XML lists with @xmlName.
  7. Lists of structures.
" }, "description": "Tests for XML list serialization", "result": { "stringList": [ "foo", "bar" ], "stringSet": [ "foo", "bar" ], "integerList": [ 1, 2 ], "booleanList": [ true, false ], "timestampList": [ 1398796238, 1398796238 ], "enumList": [ "Foo", "0" ], "intEnumList": [ 1, 2 ], "nestedStringList": [ [ "foo", "bar" ], [ "baz", "qux" ] ], "renamedListMembers": [ "foo", "bar" ], "flattenedList": [ "hi", "bye" ], "flattenedList2": [ "yep", "nope" ], "flattenedListWithMemberNamespace": [ "a", "b" ], "flattenedListWithNamespace": [ "a", "b" ], "structureList": [ { "a": "1", "b": "2" }, { "a": "3", "b": "4" } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n foo\n bar\n \n \n foo\n bar\n \n \n 1\n 2\n \n \n true\n false\n \n \n 2014-04-29T18:30:38Z\n 2014-04-29T18:30:38Z\n \n \n Foo\n 0\n \n \n 1\n 2\n \n \n \n foo\n bar\n \n \n baz\n qux\n \n \n \n foo\n bar\n \n hi\n bye\n yep\n nope\n a\n b\n a\n b\n \n \n 1\n 2\n \n \n 3\n 4\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlMaps operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlMapsOutput": { "type": "structure", "members": { "myMap": { "shape": "XmlMapsOutputMap" } } }, "XmlMapsOutputMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "GreetingStruct" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryXmlMaps", "given": { "name": "XmlMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlMapsOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Tests for XML map serialization", "result": { "myMap": { "foo": { "hi": "there" }, "baz": { "hi": "bye" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n foo\n \n there\n \n \n \n baz\n \n bye\n \n \n \n \n\n" } } ] }, { "description": "Test cases for XmlMapsXmlName operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlMapsXmlNameOutput": { "type": "structure", "members": { "myMap": { "shape": "XmlMapsXmlNameOutputMap" } } }, "XmlMapsXmlNameOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "Attribute" }, "value": { "shape": "GreetingStruct", "locationName": "Setting" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "QueryQueryXmlMapsXmlName", "given": { "name": "XmlMapsXmlName", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlMapsXmlNameOutput" } }, "description": "Serializes XML lists", "result": { "myMap": { "foo": { "hi": "there" }, "baz": { "hi": "bye" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n \n foo\n \n there\n \n \n \n baz\n \n bye\n \n \n \n \n\n" } } ] }, { "description": "Test cases for XmlNamespaces operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlNamespacesOutput": { "type": "structure", "members": { "nested": { "shape": "XmlNamespaceNested" } }, "xmlNamespace": "http://foo.com" }, "XmlNamespaceNested": { "type": "structure", "members": { "foo": { "shape": "String", "xmlNamespace": { "prefix": "baz", "uri": "http://baz.com" } }, "values": { "shape": "XmlNamespacedList", "xmlNamespace": "http://qux.com" } }, "xmlNamespace": "http://boo.com" }, "String": { "type": "string" }, "XmlNamespacedList": { "type": "list", "member": { "shape": "String", "xmlNamespace": "http://bux.com" } } }, "cases": [ { "id": "QueryXmlNamespaces", "given": { "name": "XmlNamespaces", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlNamespacesOutput" } }, "description": "Serializes XML namespaces", "result": { "nested": { "foo": "Foo", "values": [ "Bar", "Baz" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n \n Foo\n \n Bar\n Baz\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlTimestamps operation", "metadata": { "protocol": "query", "protocols": [ "query" ], "apiVersion": "2020-01-08" }, "shapes": { "XmlTimestampsOutput": { "type": "structure", "members": { "normal": { "shape": "Timestamp" }, "dateTime": { "shape": "SyntheticTimestamp_date_time" }, "dateTimeOnTarget": { "shape": "DateTime" }, "epochSeconds": { "shape": "SyntheticTimestamp_epoch_seconds" }, "epochSecondsOnTarget": { "shape": "EpochSeconds" }, "httpDate": { "shape": "SyntheticTimestamp_http_date" }, "httpDateOnTarget": { "shape": "HttpDate" } } }, "Timestamp": { "type": "timestamp" }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "EpochSeconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "HttpDate": { "type": "timestamp", "timestampFormat": "rfc822" } }, "cases": [ { "id": "QueryXmlTimestamps", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Tests how normal timestamps are serialized", "result": { "normal": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 2014-04-29T18:30:38Z\n \n\n" } }, { "id": "QueryXmlTimestampsWithDateTimeFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time works like normal timestamps", "result": { "dateTime": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 2014-04-29T18:30:38Z\n \n\n" } }, { "id": "QueryXmlTimestampsWithDateTimeOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time on the target shape works like normal timestamps", "result": { "dateTimeOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 2014-04-29T18:30:38Z\n \n\n" } }, { "id": "QueryXmlTimestampsWithEpochSecondsFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds works", "result": { "epochSeconds": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 1398796238\n \n\n" } }, { "id": "QueryXmlTimestampsWithEpochSecondsOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds on the target shape works", "result": { "epochSecondsOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n 1398796238\n \n\n" } }, { "id": "QueryXmlTimestampsWithHttpDateFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date works", "result": { "httpDate": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Tue, 29 Apr 2014 18:30:38 GMT\n \n\n" } }, { "id": "QueryXmlTimestampsWithHttpDateOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "XmlTimestampsOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date on the target shape works", "result": { "httpDateOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "text/xml" }, "body": "\n \n Tue, 29 Apr 2014 18:30:38 GMT\n \n\n" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/rest-json.json000066400000000000000000005014311510465277300315360ustar00rootroot00000000000000[ { "description": "Test cases for DatetimeOffsets operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "DatetimeOffsetsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "RestJsonDateTimeWithNegativeOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/DatetimeOffsets", "responseCode": 200 }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "body": " {\n \"datetime\": \"2019-12-16T22:48:18-01:00\"\n }\n" } }, { "id": "RestJsonDateTimeWithPositiveOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/DatetimeOffsets", "responseCode": 200 }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "body": " {\n \"datetime\": \"2019-12-17T00:48:18+01:00\"\n }\n" } } ] }, { "description": "Test cases for DocumentType operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "DocumentTypeInputOutput": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "documentValue": { "shape": "Document" } } }, "String": { "type": "string" }, "Document": { "type": "structure", "members": {}, "document": true } }, "cases": [ { "id": "DocumentOutput", "given": { "name": "DocumentType", "http": { "method": "PUT", "requestUri": "/DocumentType", "responseCode": 200 }, "output": { "shape": "DocumentTypeInputOutput" }, "documentation": "

This example serializes a document as part of the payload.

", "idempotent": true }, "description": "Serializes documents as part of the JSON response payload with no escaping.", "result": { "stringValue": "string", "documentValue": { "foo": "bar" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringValue\": \"string\",\n \"documentValue\": {\n \"foo\": \"bar\"\n }\n}" } }, { "id": "DocumentOutputString", "given": { "name": "DocumentType", "http": { "method": "PUT", "requestUri": "/DocumentType", "responseCode": 200 }, "output": { "shape": "DocumentTypeInputOutput" }, "documentation": "

This example serializes a document as part of the payload.

", "idempotent": true }, "description": "Document types can be JSON scalars too.", "result": { "stringValue": "string", "documentValue": "hello" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringValue\": \"string\",\n \"documentValue\": \"hello\"\n}" } }, { "id": "DocumentOutputNumber", "given": { "name": "DocumentType", "http": { "method": "PUT", "requestUri": "/DocumentType", "responseCode": 200 }, "output": { "shape": "DocumentTypeInputOutput" }, "documentation": "

This example serializes a document as part of the payload.

", "idempotent": true }, "description": "Document types can be JSON scalars too.", "result": { "stringValue": "string", "documentValue": 10 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringValue\": \"string\",\n \"documentValue\": 10\n}" } }, { "id": "DocumentOutputBoolean", "given": { "name": "DocumentType", "http": { "method": "PUT", "requestUri": "/DocumentType", "responseCode": 200 }, "output": { "shape": "DocumentTypeInputOutput" }, "documentation": "

This example serializes a document as part of the payload.

", "idempotent": true }, "description": "Document types can be JSON scalars too.", "result": { "stringValue": "string", "documentValue": false }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringValue\": \"string\",\n \"documentValue\": false\n}" } }, { "id": "DocumentOutputArray", "given": { "name": "DocumentType", "http": { "method": "PUT", "requestUri": "/DocumentType", "responseCode": 200 }, "output": { "shape": "DocumentTypeInputOutput" }, "documentation": "

This example serializes a document as part of the payload.

", "idempotent": true }, "description": "Document types can be JSON arrays.", "result": { "stringValue": "string", "documentValue": [ true, false ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringValue\": \"string\",\n \"documentValue\": [\n true,\n false\n ]\n}" } } ] }, { "description": "Test cases for DocumentTypeAsMapValue operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "DocumentTypeAsMapValueInputOutput": { "type": "structure", "members": { "docValuedMap": { "shape": "DocumentValuedMap" } } }, "DocumentValuedMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "Document" } }, "Document": { "type": "structure", "members": {}, "document": true }, "String": { "type": "string" } }, "cases": [ { "id": "DocumentTypeAsMapValueOutput", "given": { "name": "DocumentTypeAsMapValue", "http": { "method": "PUT", "requestUri": "/DocumentTypeAsMapValue", "responseCode": 200 }, "output": { "shape": "DocumentTypeAsMapValueInputOutput" }, "documentation": "

This example serializes documents as the value of maps.

", "idempotent": true }, "description": "Serializes a map that uses documents as the value.", "result": { "docValuedMap": { "foo": { "f": 1, "o": 2 }, "bar": [ "b", "a", "r" ], "baz": "BAZ" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"docValuedMap\": {\n \"foo\": { \"f\": 1, \"o\": 2 },\n \"bar\": [ \"b\", \"a\", \"r\" ],\n \"baz\": \"BAZ\"\n }\n}" } } ] }, { "description": "Test cases for DocumentTypeAsPayload operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "DocumentTypeAsPayloadInputOutput": { "type": "structure", "members": { "documentValue": { "shape": "Document" } }, "payload": "documentValue" }, "Document": { "type": "structure", "members": {}, "document": true } }, "cases": [ { "id": "DocumentTypeAsPayloadOutput", "given": { "name": "DocumentTypeAsPayload", "http": { "method": "PUT", "requestUri": "/DocumentTypeAsPayload", "responseCode": 200 }, "output": { "shape": "DocumentTypeAsPayloadInputOutput" }, "documentation": "

This example serializes a document as the entire HTTP payload.

", "idempotent": true }, "description": "Serializes a document as the target of the httpPayload trait.", "result": { "documentValue": { "foo": "bar" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"foo\": \"bar\"\n}" } }, { "id": "DocumentTypeAsPayloadOutputString", "given": { "name": "DocumentTypeAsPayload", "http": { "method": "PUT", "requestUri": "/DocumentTypeAsPayload", "responseCode": 200 }, "output": { "shape": "DocumentTypeAsPayloadInputOutput" }, "documentation": "

This example serializes a document as the entire HTTP payload.

", "idempotent": true }, "description": "Serializes a document as a payload string.", "result": { "documentValue": "hello" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "\"hello\"" } } ] }, { "description": "Test cases for EmptyInputAndEmptyOutput operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "EmptyInputAndEmptyOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "RestJsonEmptyInputAndEmptyOutput", "given": { "name": "EmptyInputAndEmptyOutput", "http": { "method": "POST", "requestUri": "/EmptyInputAndEmptyOutput", "responseCode": 200 }, "output": { "shape": "EmptyInputAndEmptyOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has an empty input and empty output structure that reuses the same shape. While this should be rare, code generators must support this.

" }, "description": "As of January 2021, server implementations are expected to\nrespond with a JSON object regardless of if the output\nparameters are empty.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{}" } }, { "id": "RestJsonEmptyInputAndEmptyOutputJsonObjectOutput", "given": { "name": "EmptyInputAndEmptyOutput", "http": { "method": "POST", "requestUri": "/EmptyInputAndEmptyOutput", "responseCode": 200 }, "output": { "shape": "EmptyInputAndEmptyOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has an empty input and empty output structure that reuses the same shape. While this should be rare, code generators must support this.

" }, "description": "This test ensures that clients can gracefully handle\nsituations where a service omits a JSON payload entirely.", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for FractionalSeconds operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "FractionalSecondsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "RestJsonDateTimeWithFractionalSeconds", "given": { "name": "FractionalSeconds", "http": { "method": "POST", "requestUri": "/FractionalSeconds", "responseCode": 200 }, "output": { "shape": "FractionalSecondsOutput" } }, "description": "Ensures that clients can correctly parse datetime timestamps with fractional seconds", "result": { "datetime": 9.46845296123E8 }, "response": { "status_code": 200, "body": " {\n \"datetime\": \"2000-01-02T20:34:56.123Z\"\n }\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "GreetingWithErrorsOutput": { "type": "structure", "members": { "greeting": { "shape": "String", "location": "header", "locationName": "X-Greeting" } } }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonGreetingWithErrors", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "output": { "shape": "GreetingWithErrorsOutput" }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true }, "description": "Ensures that operations with errors successfully know how\nto deserialize a successful response. As of January 2021,\nserver implementations are expected to respond with a\nJSON object regardless of if the output parameters are\nempty.", "result": { "greeting": "Hello" }, "response": { "status_code": 200, "headers": { "X-Greeting": "Hello" }, "body": "{}" } }, { "id": "RestJsonGreetingWithErrorsNoPayload", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "output": { "shape": "GreetingWithErrorsOutput" }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true }, "description": "This test is similar to RestJsonGreetingWithErrors, but it\nensures that clients can gracefully deal with a server\nomitting a response payload.", "result": { "greeting": "Hello" }, "response": { "status_code": 200, "headers": { "X-Greeting": "Hello" }, "body": "" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "FooError": { "type": "structure", "members": {}, "documentation": "

This error has test cases that test some of the dark corners of Amazon service framework history. It should only be implemented by clients.

", "error": { "httpStatusCode": 500 }, "exception": true, "fault": true } }, "cases": [ { "id": "RestJsonFooErrorUsingXAmznErrorType", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Serializes the X-Amzn-ErrorType header. For an example service, see Amazon EKS.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "FooError" } } }, { "id": "RestJsonFooErrorUsingXAmznErrorTypeWithUri", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some X-Amzn-Errortype headers contain URLs. Clients need to split the URL on ':' and take only the first half of the string. For example, 'ValidationException:http://internal.amazon.com/coral/com.amazon.coral.validate/'\nis to be interpreted as 'ValidationException'.\n\nFor an example service see Amazon Polly.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" } } }, { "id": "RestJsonFooErrorUsingXAmznErrorTypeWithUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "X-Amzn-Errortype might contain a URL and a namespace. Client should extract only the shape name. This is a pathalogical case that might not actually happen in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "X-Amzn-Errortype": "aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/" } } }, { "id": "RestJsonFooErrorUsingCode", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "This example uses the 'code' property in the output rather than X-Amzn-Errortype. Some services do this though it's preferable to send the X-Amzn-Errortype. Client implementations must first check for the X-Amzn-Errortype and then check for a top-level 'code' property.\n\nFor example service see Amazon S3 Glacier.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/json" }, "body": "{\n \"code\": \"FooError\"\n}" } }, { "id": "RestJsonFooErrorUsingCodeAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using code, and it might contain a namespace. Clients should just take the last part of the string after '#'.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/json" }, "body": "{\n \"code\": \"aws.protocoltests.restjson#FooError\"\n}" } }, { "id": "RestJsonFooErrorUsingCodeUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using code, and it might contain a namespace. It also might contain a URI. Clients should just take the last part of the string after '#' and before \":\". This is a pathalogical case that might not occur in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/json" }, "body": "{\n \"code\": \"aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/\"\n}" } }, { "id": "RestJsonFooErrorWithDunderType", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/json" }, "body": "{\n \"__type\": \"FooError\"\n}" } }, { "id": "RestJsonFooErrorWithDunderTypeAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type, and it might contain a namespace. Clients should just take the last part of the string after '#'.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/json" }, "body": "{\n \"__type\": \"aws.protocoltests.restjson#FooError\"\n}" } }, { "id": "RestJsonFooErrorWithDunderTypeUriAndNamespace", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "FooError" } ] }, "description": "Some services serialize errors using __type, and it might contain a namespace. It also might contain a URI. Clients should just take the last part of the string after '#' and before \":\". This is a pathalogical case that might not occur in any deployed AWS service.", "errorCode": "FooError", "error": {}, "response": { "status_code": 500, "headers": { "Content-Type": "application/json" }, "body": "{\n \"__type\": \"aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/\"\n}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "ComplexError": { "type": "structure", "members": { "Header": { "shape": "String", "location": "header", "locationName": "X-Header" }, "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "error": { "httpStatusCode": 403, "senderFault": true }, "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String", "locationName": "Fooooo" } } } }, "cases": [ { "id": "RestJsonComplexErrorWithNoMessage", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "description": "Serializes a complex error with no message member", "errorCode": "ComplexError", "error": { "Header": "Header", "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 403, "headers": { "Content-Type": "application/json", "X-Amzn-Errortype": "ComplexError", "X-Header": "Header" }, "body": "{\n \"TopLevel\": \"Top level\",\n \"Nested\": {\n \"Fooooo\": \"bar\"\n }\n}" } }, { "id": "RestJsonEmptyComplexErrorWithNoMessage", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "errorCode": "ComplexError", "error": {}, "response": { "status_code": 403, "headers": { "Content-Type": "application/json", "X-Amzn-Errortype": "ComplexError" }, "body": "{}" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "error": { "httpStatusCode": 400, "senderFault": true }, "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonInvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has four possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.
  4. A FooError.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple JSON errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "application/json", "X-Amzn-Errortype": "InvalidGreeting" }, "body": "{\n \"Message\": \"Hi\"\n}" } } ] }, { "description": "Test cases for HttpEnumPayload operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "EnumPayloadInput": { "type": "structure", "members": { "payload": { "shape": "StringEnum" } }, "payload": "payload" }, "StringEnum": { "type": "string", "enum": [ "enumvalue" ] } }, "cases": [ { "id": "RestJsonEnumPayloadResponse", "given": { "name": "HttpEnumPayload", "http": { "method": "POST", "requestUri": "/EnumPayload", "responseCode": 200 }, "output": { "shape": "EnumPayloadInput" } }, "result": { "payload": "enumvalue" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/plain" }, "body": "enumvalue" } } ] }, { "description": "Test cases for HttpPayloadTraits operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadTraitsInputOutput": { "type": "structure", "members": { "foo": { "shape": "String", "location": "header", "locationName": "X-Foo" }, "blob": { "shape": "Blob" } }, "payload": "blob" }, "String": { "type": "string" }, "Blob": { "type": "blob" } }, "cases": [ { "id": "RestJsonHttpPayloadTraitsWithBlob", "given": { "name": "HttpPayloadTraits", "http": { "method": "POST", "requestUri": "/HttpPayloadTraits", "responseCode": 200 }, "output": { "shape": "HttpPayloadTraitsInputOutput" }, "documentation": "

This example serializes a blob shape in the payload.

In this example, no JSON document is synthesized because the payload is not a structure or a union type.

" }, "description": "Serializes a blob in the HTTP payload", "result": { "foo": "Foo", "blob": "blobby blob blob" }, "response": { "status_code": 200, "headers": { "X-Foo": "Foo" }, "body": "blobby blob blob" } }, { "id": "RestJsonHttpPayloadTraitsWithNoBlobBody", "given": { "name": "HttpPayloadTraits", "http": { "method": "POST", "requestUri": "/HttpPayloadTraits", "responseCode": 200 }, "output": { "shape": "HttpPayloadTraitsInputOutput" }, "documentation": "

This example serializes a blob shape in the payload.

In this example, no JSON document is synthesized because the payload is not a structure or a union type.

" }, "description": "Serializes an empty blob in the HTTP payload", "result": { "foo": "Foo" }, "response": { "status_code": 200, "headers": { "X-Foo": "Foo" }, "body": "" } } ] }, { "description": "Test cases for HttpPayloadWithStructure operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithStructureInputOutput": { "type": "structure", "members": { "nested": { "shape": "NestedPayload" } }, "payload": "nested" }, "NestedPayload": { "type": "structure", "members": { "greeting": { "shape": "String" }, "name": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonHttpPayloadWithStructure", "given": { "name": "HttpPayloadWithStructure", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithStructure", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithStructureInputOutput" }, "documentation": "

This example serializes a structure in the payload.

Note that serializing a structure changes the wrapper element name to match the targeted structure.

", "idempotent": true }, "description": "Serializes a structure in the payload", "result": { "nested": { "greeting": "hello", "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"greeting\": \"hello\",\n \"name\": \"Phreddy\"\n}" } } ] }, { "description": "Test cases for HttpPayloadWithUnion operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithUnionInputOutput": { "type": "structure", "members": { "nested": { "shape": "UnionPayload" } }, "payload": "nested" }, "UnionPayload": { "type": "structure", "members": { "greeting": { "shape": "String" } }, "union": true }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonHttpPayloadWithUnion", "given": { "name": "HttpPayloadWithUnion", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithUnion", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithUnionInputOutput" }, "documentation": "

This example serializes a union in the payload.

", "idempotent": true }, "description": "Serializes a union in the payload.", "result": { "nested": { "greeting": "hello" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"greeting\": \"hello\"\n}" } }, { "id": "RestJsonHttpPayloadWithUnsetUnion", "given": { "name": "HttpPayloadWithUnion", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithUnion", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithUnionInputOutput" }, "documentation": "

This example serializes a union in the payload.

", "idempotent": true }, "description": "No payload is sent if the union has no value.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Length": "0" }, "body": "" } } ] }, { "description": "Test cases for HttpPrefixHeaders operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPrefixHeadersOutput": { "type": "structure", "members": { "foo": { "shape": "String", "location": "header", "locationName": "x-foo" }, "fooMap": { "shape": "StringMap", "location": "headers", "locationName": "x-foo-" } } }, "String": { "type": "string" }, "StringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } } }, "cases": [ { "id": "RestJsonHttpPrefixHeadersArePresent", "given": { "name": "HttpPrefixHeaders", "http": { "method": "GET", "requestUri": "/HttpPrefixHeaders", "responseCode": 200 }, "output": { "shape": "HttpPrefixHeadersOutput" }, "documentation": "

This examples adds headers to the input of a request and response by prefix.

" }, "description": "Adds headers by prefix", "result": { "foo": "Foo", "fooMap": { "abc": "Abc value", "def": "Def value" } }, "response": { "status_code": 200, "headers": { "x-foo": "Foo", "x-foo-abc": "Abc value", "x-foo-def": "Def value" } } } ] }, { "description": "Test cases for HttpPrefixHeadersInResponse operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPrefixHeadersInResponseOutput": { "type": "structure", "members": { "prefixHeaders": { "shape": "StringMap", "location": "headers", "locationName": "" } } }, "StringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "String": { "type": "string" } }, "cases": [ { "id": "HttpPrefixHeadersResponse", "given": { "name": "HttpPrefixHeadersInResponse", "http": { "method": "GET", "requestUri": "/HttpPrefixHeadersResponse", "responseCode": 200 }, "output": { "shape": "HttpPrefixHeadersInResponseOutput" }, "documentation": "

Clients that perform this test extract all headers from the response.

" }, "description": "(de)serializes all response headers", "result": { "prefixHeaders": { "x-foo": "Foo", "hello": "Hello" } }, "response": { "status_code": 200, "headers": { "hello": "Hello", "x-foo": "Foo" } } } ] }, { "description": "Test cases for HttpResponseCode operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpResponseCodeOutput": { "type": "structure", "members": { "Status": { "shape": "Integer", "location": "statusCode" } } }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "RestJsonHttpResponseCode", "given": { "name": "HttpResponseCode", "http": { "method": "PUT", "requestUri": "/HttpResponseCode", "responseCode": 200 }, "output": { "shape": "HttpResponseCodeOutput" }, "idempotent": true }, "description": "Binds the http response code to an output structure. Note that\neven though all members are bound outside of the payload, an\nempty JSON object is serialized in the response. However,\nclients should be able to handle an empty JSON object or an\nempty payload without failing to deserialize a response.", "result": { "Status": 201 }, "response": { "status_code": 201, "headers": { "Content-Type": "application/json" }, "body": "{}" } }, { "id": "RestJsonHttpResponseCodeWithNoPayload", "given": { "name": "HttpResponseCode", "http": { "method": "PUT", "requestUri": "/HttpResponseCode", "responseCode": 200 }, "output": { "shape": "HttpResponseCodeOutput" }, "idempotent": true }, "description": "This test ensures that clients gracefully handle cases where\nthe service responds with no payload rather than an empty JSON\nobject.", "result": { "Status": 201 }, "response": { "status_code": 201, "body": "" } } ] }, { "description": "Test cases for HttpStringPayload operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "StringPayloadInput": { "type": "structure", "members": { "payload": { "shape": "String" } }, "payload": "payload" }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonStringPayloadResponse", "given": { "name": "HttpStringPayload", "http": { "method": "POST", "requestUri": "/StringPayload", "responseCode": 200 }, "output": { "shape": "StringPayloadInput" } }, "result": { "payload": "rawstring" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/plain" }, "body": "rawstring" } } ] }, { "description": "Test cases for IgnoreQueryParamsInResponse operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "IgnoreQueryParamsInResponseOutput": { "type": "structure", "members": { "baz": { "shape": "String", "location": "uri", "locationName": "baz" } } }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonIgnoreQueryParamsInResponse", "given": { "name": "IgnoreQueryParamsInResponse", "http": { "method": "GET", "requestUri": "/IgnoreQueryParamsInResponse", "responseCode": 200 }, "output": { "shape": "IgnoreQueryParamsInResponseOutput" }, "documentation": "

This example ensures that query string bound request parameters are serialized in the body of responses if the structure is used in both the request and response.

" }, "description": "Query parameters must be ignored when serializing the output\nof an operation. As of January 2021, server implementations\nare expected to respond with a JSON object regardless of\nif the output parameters are empty.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{}" } }, { "id": "RestJsonIgnoreQueryParamsInResponseNoPayload", "given": { "name": "IgnoreQueryParamsInResponse", "http": { "method": "GET", "requestUri": "/IgnoreQueryParamsInResponse", "responseCode": 200 }, "output": { "shape": "IgnoreQueryParamsInResponseOutput" }, "documentation": "

This example ensures that query string bound request parameters are serialized in the body of responses if the structure is used in both the request and response.

" }, "description": "This test is similar to RestJsonIgnoreQueryParamsInResponse,\nbut it ensures that clients gracefully handle responses from\nthe server that do not serialize an empty JSON object.", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for InputAndOutputWithHeaders operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "InputAndOutputWithHeadersIO": { "type": "structure", "members": { "headerString": { "shape": "String", "location": "header", "locationName": "X-String" }, "headerByte": { "shape": "Integer", "location": "header", "locationName": "X-Byte" }, "headerShort": { "shape": "Integer", "location": "header", "locationName": "X-Short" }, "headerInteger": { "shape": "Integer", "location": "header", "locationName": "X-Integer" }, "headerLong": { "shape": "Long", "location": "header", "locationName": "X-Long" }, "headerFloat": { "shape": "Float", "location": "header", "locationName": "X-Float" }, "headerDouble": { "shape": "Double", "location": "header", "locationName": "X-Double" }, "headerTrueBool": { "shape": "Boolean", "location": "header", "locationName": "X-Boolean1" }, "headerFalseBool": { "shape": "Boolean", "location": "header", "locationName": "X-Boolean2" }, "headerStringList": { "shape": "StringList", "location": "header", "locationName": "X-StringList" }, "headerStringSet": { "shape": "StringSet", "location": "header", "locationName": "X-StringSet" }, "headerIntegerList": { "shape": "IntegerList", "location": "header", "locationName": "X-IntegerList" }, "headerBooleanList": { "shape": "BooleanList", "location": "header", "locationName": "X-BooleanList" }, "headerTimestampList": { "shape": "TimestampList", "location": "header", "locationName": "X-TimestampList" }, "headerEnum": { "shape": "FooEnum", "location": "header", "locationName": "X-Enum" }, "headerEnumList": { "shape": "FooEnumList", "location": "header", "locationName": "X-EnumList" }, "headerIntegerEnum": { "shape": "IntegerEnum", "location": "header", "locationName": "X-IntegerEnum" }, "headerIntegerEnumList": { "shape": "IntegerEnumList", "location": "header", "locationName": "X-IntegerEnumList" } } }, "String": { "type": "string" }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true }, "Boolean": { "type": "boolean", "box": true }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnum": { "type": "integer", "box": true }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "Timestamp": { "type": "timestamp" } }, "cases": [ { "id": "RestJsonInputAndOutputWithStringHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with string header bindings", "result": { "headerString": "Hello", "headerStringList": [ "a", "b", "c" ], "headerStringSet": [ "a", "b", "c" ] }, "response": { "status_code": 200, "headers": { "X-String": "Hello", "X-StringList": "a, b, c", "X-StringSet": "a, b, c" } } }, { "id": "RestJsonInputAndOutputWithQuotedStringHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with string list header bindings that require quoting", "result": { "headerStringList": [ "b,c", "\"def\"", "a" ] }, "response": { "status_code": 200, "headers": { "X-StringList": "\"b,c\", \"\\\"def\\\"\", a" } } }, { "id": "RestJsonInputAndOutputWithNumericHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with numeric header bindings", "result": { "headerByte": 1, "headerShort": 123, "headerInteger": 123, "headerLong": 123, "headerFloat": 1.1, "headerDouble": 1.1, "headerIntegerList": [ 1, 2, 3 ] }, "response": { "status_code": 200, "headers": { "X-Byte": "1", "X-Double": "1.1", "X-Float": "1.1", "X-Integer": "123", "X-IntegerList": "1, 2, 3", "X-Long": "123", "X-Short": "123" } } }, { "id": "RestJsonInputAndOutputWithBooleanHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with boolean header bindings", "result": { "headerTrueBool": true, "headerFalseBool": false, "headerBooleanList": [ true, false, true ] }, "response": { "status_code": 200, "headers": { "X-Boolean1": "true", "X-Boolean2": "false", "X-BooleanList": "true, false, true" } } }, { "id": "RestJsonInputAndOutputWithTimestampHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with timestamp header bindings", "result": { "headerTimestampList": [ 1576540098, 1576540098 ] }, "response": { "status_code": 200, "headers": { "X-TimestampList": "Mon, 16 Dec 2019 23:48:18 GMT, Mon, 16 Dec 2019 23:48:18 GMT" } } }, { "id": "RestJsonInputAndOutputWithEnumHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with enum header bindings", "result": { "headerEnum": "Foo", "headerEnumList": [ "Foo", "Bar", "Baz" ] }, "response": { "status_code": 200, "headers": { "X-Enum": "Foo", "X-EnumList": "Foo, Bar, Baz" } } }, { "id": "RestJsonInputAndOutputWithIntEnumHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with intEnum header bindings", "result": { "headerIntegerEnum": 1, "headerIntegerEnumList": [ 1, 2, 3 ] }, "response": { "status_code": 200, "headers": { "X-IntegerEnum": "1", "X-IntegerEnumList": "1, 2, 3" } } }, { "id": "RestJsonSupportsNaNFloatHeaderOutputs", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Supports handling NaN float header values.", "result": { "headerFloat": "NaN", "headerDouble": "NaN" }, "response": { "status_code": 200, "headers": { "X-Double": "NaN", "X-Float": "NaN" } } }, { "id": "RestJsonSupportsInfinityFloatHeaderOutputs", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Supports handling Infinity float header values.", "result": { "headerFloat": "Infinity", "headerDouble": "Infinity" }, "response": { "status_code": 200, "headers": { "X-Double": "Infinity", "X-Float": "Infinity" } } }, { "id": "RestJsonSupportsNegativeInfinityFloatHeaderOutputs", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Supports handling -Infinity float header values.", "result": { "headerFloat": "-Infinity", "headerDouble": "-Infinity" }, "response": { "status_code": 200, "headers": { "X-Double": "-Infinity", "X-Float": "-Infinity" } } } ] }, { "description": "Test cases for JsonBlobs operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "JsonBlobsInputOutput": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "RestJsonJsonBlobs", "given": { "name": "JsonBlobs", "http": { "method": "POST", "requestUri": "/JsonBlobs", "responseCode": 200 }, "output": { "shape": "JsonBlobsInputOutput" }, "documentation": "

Blobs are base64 encoded

" }, "description": "Blobs are base64 encoded", "result": { "data": "value" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"data\": \"dmFsdWU=\"\n}" } } ] }, { "description": "Test cases for JsonEnums operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "JsonEnumsInputOutput": { "type": "structure", "members": { "fooEnum1": { "shape": "FooEnum" }, "fooEnum2": { "shape": "FooEnum" }, "fooEnum3": { "shape": "FooEnum" }, "fooEnumList": { "shape": "FooEnumList" }, "fooEnumSet": { "shape": "FooEnumSet" }, "fooEnumMap": { "shape": "FooEnumMap" } } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumSet": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonJsonEnums", "given": { "name": "JsonEnums", "http": { "method": "PUT", "requestUri": "/JsonEnums", "responseCode": 200 }, "output": { "shape": "JsonEnumsInputOutput" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "fooEnum1": "Foo", "fooEnum2": "0", "fooEnum3": "1", "fooEnumList": [ "Foo", "0" ], "fooEnumSet": [ "Foo", "0" ], "fooEnumMap": { "hi": "Foo", "zero": "0" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"fooEnum1\": \"Foo\",\n \"fooEnum2\": \"0\",\n \"fooEnum3\": \"1\",\n \"fooEnumList\": [\n \"Foo\",\n \"0\"\n ],\n \"fooEnumSet\": [\n \"Foo\",\n \"0\"\n ],\n \"fooEnumMap\": {\n \"hi\": \"Foo\",\n \"zero\": \"0\"\n }\n}" } } ] }, { "description": "Test cases for JsonIntEnums operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "JsonIntEnumsInputOutput": { "type": "structure", "members": { "integerEnum1": { "shape": "IntegerEnum" }, "integerEnum2": { "shape": "IntegerEnum" }, "integerEnum3": { "shape": "IntegerEnum" }, "integerEnumList": { "shape": "IntegerEnumList" }, "integerEnumSet": { "shape": "IntegerEnumSet" }, "integerEnumMap": { "shape": "IntegerEnumMap" } } }, "IntegerEnum": { "type": "integer", "box": true }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumSet": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "IntegerEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "RestJsonJsonIntEnums", "given": { "name": "JsonIntEnums", "http": { "method": "PUT", "requestUri": "/JsonIntEnums", "responseCode": 200 }, "output": { "shape": "JsonIntEnumsInputOutput" }, "documentation": "

This example serializes intEnums as top level properties, in lists, sets, and maps.

", "idempotent": true }, "description": "Serializes intEnums as integers", "result": { "integerEnum1": 1, "integerEnum2": 2, "integerEnum3": 3, "integerEnumList": [ 1, 2, 3 ], "integerEnumSet": [ 1, 2 ], "integerEnumMap": { "abc": 1, "def": 2 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"integerEnum1\": 1,\n \"integerEnum2\": 2,\n \"integerEnum3\": 3,\n \"integerEnumList\": [\n 1,\n 2,\n 3\n ],\n \"integerEnumSet\": [\n 1,\n 2\n ],\n \"integerEnumMap\": {\n \"abc\": 1,\n \"def\": 2\n }\n}" } } ] }, { "description": "Test cases for JsonLists operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "JsonListsInputOutput": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "RestJsonLists", "given": { "name": "JsonLists", "http": { "method": "PUT", "requestUri": "/JsonLists", "responseCode": 200 }, "output": { "shape": "JsonListsInputOutput" }, "documentation": "

This test case serializes JSON lists for the following cases for both input and output:

  1. Normal JSON lists.
  2. Normal JSON sets.
  3. JSON lists of lists.
  4. Lists of structures.
", "idempotent": true }, "description": "Serializes JSON lists", "result": { "stringList": [ "foo", "bar" ], "stringSet": [ "foo", "bar" ], "integerList": [ 1, 2 ], "booleanList": [ true, false ], "timestampList": [ 1398796238, 1398796238 ], "enumList": [ "Foo", "0" ], "intEnumList": [ 1, 2 ], "nestedStringList": [ [ "foo", "bar" ], [ "baz", "qux" ] ], "structureList": [ { "a": "1", "b": "2" }, { "a": "3", "b": "4" } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringList\": [\n \"foo\",\n \"bar\"\n ],\n \"stringSet\": [\n \"foo\",\n \"bar\"\n ],\n \"integerList\": [\n 1,\n 2\n ],\n \"booleanList\": [\n true,\n false\n ],\n \"timestampList\": [\n 1398796238,\n 1398796238\n ],\n \"enumList\": [\n \"Foo\",\n \"0\"\n ],\n \"intEnumList\": [\n 1,\n 2\n ],\n \"nestedStringList\": [\n [\n \"foo\",\n \"bar\"\n ],\n [\n \"baz\",\n \"qux\"\n ]\n ],\n \"myStructureList\": [\n {\n \"value\": \"1\",\n \"other\": \"2\"\n },\n {\n \"value\": \"3\",\n \"other\": \"4\"\n }\n ]\n}" } }, { "id": "RestJsonListsEmpty", "given": { "name": "JsonLists", "http": { "method": "PUT", "requestUri": "/JsonLists", "responseCode": 200 }, "output": { "shape": "JsonListsInputOutput" }, "documentation": "

This test case serializes JSON lists for the following cases for both input and output:

  1. Normal JSON lists.
  2. Normal JSON sets.
  3. JSON lists of lists.
  4. Lists of structures.
", "idempotent": true }, "description": "Serializes empty JSON lists", "result": { "stringList": [] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringList\": []\n}" } } ] }, { "description": "Test cases for JsonMaps operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "JsonMapsInputOutput": { "type": "structure", "members": { "denseStructMap": { "shape": "DenseStructMap" }, "denseNumberMap": { "shape": "DenseNumberMap" }, "denseBooleanMap": { "shape": "DenseBooleanMap" }, "denseStringMap": { "shape": "DenseStringMap" }, "denseSetMap": { "shape": "DenseSetMap" } } }, "DenseStructMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "GreetingStruct" } }, "DenseNumberMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "Integer" } }, "DenseBooleanMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "Boolean" } }, "DenseStringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "DenseSetMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "StringSet" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } } }, "cases": [ { "id": "RestJsonJsonMaps", "given": { "name": "JsonMaps", "http": { "method": "POST", "requestUri": "/JsonMaps", "responseCode": 200 }, "output": { "shape": "JsonMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Deserializes JSON maps", "result": { "denseStructMap": { "foo": { "hi": "there" }, "baz": { "hi": "bye" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"denseStructMap\": {\n \"foo\": {\n \"hi\": \"there\"\n },\n \"baz\": {\n \"hi\": \"bye\"\n }\n }\n}" } }, { "id": "RestJsonDeserializesZeroValuesInMaps", "given": { "name": "JsonMaps", "http": { "method": "POST", "requestUri": "/JsonMaps", "responseCode": 200 }, "output": { "shape": "JsonMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Ensure that 0 and false are sent over the wire in all maps and lists", "result": { "denseNumberMap": { "x": 0 }, "denseBooleanMap": { "x": false } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"denseNumberMap\": {\n \"x\": 0\n },\n \"denseBooleanMap\": {\n \"x\": false\n }\n}" } }, { "id": "RestJsonDeserializesDenseSetMap", "given": { "name": "JsonMaps", "http": { "method": "POST", "requestUri": "/JsonMaps", "responseCode": 200 }, "output": { "shape": "JsonMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "A response that contains a dense map of sets.", "result": { "denseSetMap": { "x": [], "y": [ "a", "b" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"denseSetMap\": {\n \"x\": [],\n \"y\": [\"a\", \"b\"]\n }\n}" } }, { "id": "RestJsonDeserializesDenseSetMapAndSkipsNull", "given": { "name": "JsonMaps", "http": { "method": "POST", "requestUri": "/JsonMaps", "responseCode": 200 }, "output": { "shape": "JsonMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD\ndrop the null key-value pair.", "result": { "denseSetMap": { "x": [], "y": [ "a", "b" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"denseSetMap\": {\n \"x\": [],\n \"y\": [\"a\", \"b\"],\n \"z\": null\n }\n}" } } ] }, { "description": "Test cases for JsonTimestamps operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "JsonTimestampsInputOutput": { "type": "structure", "members": { "normal": { "shape": "Timestamp" }, "dateTime": { "shape": "SyntheticTimestamp_date_time" }, "dateTimeOnTarget": { "shape": "DateTime" }, "epochSeconds": { "shape": "SyntheticTimestamp_epoch_seconds" }, "epochSecondsOnTarget": { "shape": "EpochSeconds" }, "httpDate": { "shape": "SyntheticTimestamp_http_date" }, "httpDateOnTarget": { "shape": "HttpDate" } } }, "Timestamp": { "type": "timestamp" }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "EpochSeconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "HttpDate": { "type": "timestamp", "timestampFormat": "rfc822" } }, "cases": [ { "id": "RestJsonJsonTimestamps", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Tests how normal timestamps are serialized", "result": { "normal": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"normal\": 1398796238\n}" } }, { "id": "RestJsonJsonTimestampsWithDateTimeFormat", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time works like normal timestamps", "result": { "dateTime": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"dateTime\": \"2014-04-29T18:30:38Z\"\n}" } }, { "id": "RestJsonJsonTimestampsWithDateTimeOnTargetFormat", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time on the target shape works like normal timestamps", "result": { "dateTimeOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"dateTimeOnTarget\": \"2014-04-29T18:30:38Z\"\n}" } }, { "id": "RestJsonJsonTimestampsWithEpochSecondsFormat", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds works", "result": { "epochSeconds": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"epochSeconds\": 1398796238\n}" } }, { "id": "RestJsonJsonTimestampsWithEpochSecondsOnTargetFormat", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds on the target shape works", "result": { "epochSecondsOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"epochSecondsOnTarget\": 1398796238\n}" } }, { "id": "RestJsonJsonTimestampsWithHttpDateFormat", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date works", "result": { "httpDate": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"httpDate\": \"Tue, 29 Apr 2014 18:30:38 GMT\"\n}" } }, { "id": "RestJsonJsonTimestampsWithHttpDateOnTargetFormat", "given": { "name": "JsonTimestamps", "http": { "method": "POST", "requestUri": "/JsonTimestamps", "responseCode": 200 }, "output": { "shape": "JsonTimestampsInputOutput" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date on the target shape works", "result": { "httpDateOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"httpDateOnTarget\": \"Tue, 29 Apr 2014 18:30:38 GMT\"\n}" } } ] }, { "description": "Test cases for JsonUnions operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "UnionInputOutput": { "type": "structure", "members": { "contents": { "shape": "MyUnion" } }, "documentation": "

A shared structure that contains a single union member.

" }, "MyUnion": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "booleanValue": { "shape": "Boolean" }, "numberValue": { "shape": "Integer" }, "blobValue": { "shape": "Blob" }, "timestampValue": { "shape": "Timestamp" }, "enumValue": { "shape": "FooEnum" }, "listValue": { "shape": "StringList" }, "mapValue": { "shape": "StringMap" }, "structureValue": { "shape": "GreetingStruct" }, "renamedStructureValue": { "shape": "RenamedGreeting" } }, "documentation": "

A union with a representative set of types for members.

", "union": true }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Blob": { "type": "blob" }, "Timestamp": { "type": "timestamp" }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "RenamedGreeting": { "type": "structure", "members": { "salutation": { "shape": "String" } } } }, "cases": [ { "id": "RestJsonDeserializeStringUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a string union value", "result": { "contents": { "stringValue": "foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"stringValue\": \"foo\"\n }\n}" } }, { "id": "RestJsonDeserializeBooleanUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a boolean union value", "result": { "contents": { "booleanValue": true } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"booleanValue\": true\n }\n}" } }, { "id": "RestJsonDeserializeNumberUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a number union value", "result": { "contents": { "numberValue": 1 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"numberValue\": 1\n }\n}" } }, { "id": "RestJsonDeserializeBlobUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a blob union value", "result": { "contents": { "blobValue": "foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"blobValue\": \"Zm9v\"\n }\n}" } }, { "id": "RestJsonDeserializeTimestampUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a timestamp union value", "result": { "contents": { "timestampValue": 1398796238 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"timestampValue\": 1398796238\n }\n}" } }, { "id": "RestJsonDeserializeEnumUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes an enum union value", "result": { "contents": { "enumValue": "Foo" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"enumValue\": \"Foo\"\n }\n}" } }, { "id": "RestJsonDeserializeListUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a list union value", "result": { "contents": { "listValue": [ "foo", "bar" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"listValue\": [\"foo\", \"bar\"]\n }\n}" } }, { "id": "RestJsonDeserializeMapUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a map union value", "result": { "contents": { "mapValue": { "foo": "bar", "spam": "eggs" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"mapValue\": {\n \"foo\": \"bar\",\n \"spam\": \"eggs\"\n }\n }\n}" } }, { "id": "RestJsonDeserializeStructureUnionValue", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Deserializes a structure union value", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } }, { "id": "RestJsonDeserializeIgnoreType", "given": { "name": "JsonUnions", "http": { "method": "PUT", "requestUri": "/JsonUnions", "responseCode": 200 }, "output": { "shape": "UnionInputOutput" }, "documentation": "

This operation uses unions for inputs and outputs.

", "idempotent": true }, "description": "Ignores an unrecognized __type property", "result": { "contents": { "structureValue": { "hi": "hello" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"contents\": {\n \"__type\": \"aws.protocoltests.json10#MyUnion\",\n \"structureValue\": {\n \"hi\": \"hello\"\n }\n }\n}" } } ] }, { "description": "Test cases for MediaTypeHeader operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "MediaTypeHeaderOutput": { "type": "structure", "members": { "json": { "shape": "JsonValue", "jsonvalue": true, "location": "header", "locationName": "X-Json" } } }, "JsonValue": { "type": "string" } }, "cases": [ { "id": "MediaTypeHeaderOutputBase64", "given": { "name": "MediaTypeHeader", "http": { "method": "GET", "requestUri": "/MediaTypeHeader", "responseCode": 200 }, "output": { "shape": "MediaTypeHeaderOutput" }, "documentation": "

This example ensures that mediaType strings are base64 encoded in headers.

" }, "description": "Headers that target strings with a mediaType are base64 encoded", "result": { "json": "true" }, "response": { "status_code": 200, "headers": { "X-Json": "dHJ1ZQ==" } } } ] }, { "description": "Test cases for NoInputAndNoOutput operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": {}, "cases": [ { "id": "RestJsonNoInputAndNoOutput", "given": { "name": "NoInputAndNoOutput", "http": { "method": "POST", "requestUri": "/NoInputAndNoOutput", "responseCode": 200 }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input or output. While this should be rare, code generators must support this.

" }, "description": "When an operation does not define output, the service will respond\nwith an empty payload, and may optionally include the content-type\nheader.", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for NoInputAndOutput operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "NoInputAndOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "RestJsonNoInputAndOutputWithJson", "given": { "name": "NoInputAndOutput", "http": { "method": "POST", "requestUri": "/NoInputAndOutputOutput", "responseCode": 200 }, "output": { "shape": "NoInputAndOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input and the output is empty. While this should be rare, code generators must support this.

" }, "description": "Operations that define output and do not bind anything to\nthe payload return a JSON object in the response.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{}" } }, { "id": "RestJsonNoInputAndOutputNoPayload", "given": { "name": "NoInputAndOutput", "http": { "method": "POST", "requestUri": "/NoInputAndOutputOutput", "responseCode": 200 }, "output": { "shape": "NoInputAndOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input and the output is empty. While this should be rare, code generators must support this.

" }, "description": "This test is similar to RestJsonNoInputAndOutputWithJson, but\nit ensures that clients can gracefully handle responses that\nomit a JSON payload.", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for PostUnionWithJsonName operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "PostUnionWithJsonNameOutput": { "type": "structure", "required": [ "value" ], "members": { "value": { "shape": "UnionWithJsonName" } } }, "UnionWithJsonName": { "type": "structure", "members": { "foo": { "shape": "String", "locationName": "FOO" }, "bar": { "shape": "String" }, "baz": { "shape": "String", "locationName": "_baz" } }, "union": true }, "String": { "type": "string" } }, "cases": [ { "id": "PostUnionWithJsonNameResponse1", "given": { "name": "PostUnionWithJsonName", "http": { "method": "POST", "requestUri": "/PostUnionWithJsonName", "responseCode": 200 }, "output": { "shape": "PostUnionWithJsonNameOutput" }, "documentation": "

This operation defines a union that uses jsonName on some members.

" }, "description": "Tests that jsonName works with union members.", "result": { "value": { "foo": "hi" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"value\": {\n \"FOO\": \"hi\"\n }\n}" } }, { "id": "PostUnionWithJsonNameResponse2", "given": { "name": "PostUnionWithJsonName", "http": { "method": "POST", "requestUri": "/PostUnionWithJsonName", "responseCode": 200 }, "output": { "shape": "PostUnionWithJsonNameOutput" }, "documentation": "

This operation defines a union that uses jsonName on some members.

" }, "description": "Tests that jsonName works with union members.", "result": { "value": { "baz": "hi" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"value\": {\n \"_baz\": \"hi\"\n }\n}" } }, { "id": "PostUnionWithJsonNameResponse3", "given": { "name": "PostUnionWithJsonName", "http": { "method": "POST", "requestUri": "/PostUnionWithJsonName", "responseCode": 200 }, "output": { "shape": "PostUnionWithJsonNameOutput" }, "documentation": "

This operation defines a union that uses jsonName on some members.

" }, "description": "Tests that jsonName works with union members.", "result": { "value": { "bar": "hi" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"value\": {\n \"bar\": \"hi\"\n }\n}" } } ] }, { "description": "Test cases for RecursiveShapes operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "RecursiveShapesInputOutput": { "type": "structure", "members": { "nested": { "shape": "RecursiveShapesInputOutputNested1" } } }, "RecursiveShapesInputOutputNested1": { "type": "structure", "members": { "foo": { "shape": "String" }, "nested": { "shape": "RecursiveShapesInputOutputNested2" } } }, "String": { "type": "string" }, "RecursiveShapesInputOutputNested2": { "type": "structure", "members": { "bar": { "shape": "String" }, "recursiveMember": { "shape": "RecursiveShapesInputOutputNested1" } } } }, "cases": [ { "id": "RestJsonRecursiveShapes", "given": { "name": "RecursiveShapes", "http": { "method": "PUT", "requestUri": "/RecursiveShapes", "responseCode": 200 }, "output": { "shape": "RecursiveShapesInputOutput" }, "documentation": "

Recursive shapes

", "idempotent": true }, "description": "Serializes recursive structures", "result": { "nested": { "foo": "Foo1", "nested": { "bar": "Bar1", "recursiveMember": { "foo": "Foo2", "nested": { "bar": "Bar2" } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"nested\": {\n \"foo\": \"Foo1\",\n \"nested\": {\n \"bar\": \"Bar1\",\n \"recursiveMember\": {\n \"foo\": \"Foo2\",\n \"nested\": {\n \"bar\": \"Bar2\"\n }\n }\n }\n }\n}" } } ] }, { "description": "Test cases for SimpleScalarProperties operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "SimpleScalarPropertiesInputOutput": { "type": "structure", "members": { "foo": { "shape": "String", "location": "header", "locationName": "X-Foo" }, "stringValue": { "shape": "String" }, "trueBooleanValue": { "shape": "Boolean" }, "falseBooleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "shortValue": { "shape": "Integer" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double", "locationName": "DoubleDribble" } } }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "RestJsonSimpleScalarProperties", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesInputOutput" }, "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "foo": "Foo", "stringValue": "string", "trueBooleanValue": true, "falseBooleanValue": false, "byteValue": 1, "shortValue": 2, "integerValue": 3, "longValue": 4, "floatValue": 5.5, "doubleValue": 6.5 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json", "X-Foo": "Foo" }, "body": "{\n \"stringValue\": \"string\",\n \"trueBooleanValue\": true,\n \"falseBooleanValue\": false,\n \"byteValue\": 1,\n \"shortValue\": 2,\n \"integerValue\": 3,\n \"longValue\": 4,\n \"floatValue\": 5.5,\n \"DoubleDribble\": 6.5\n}" } }, { "id": "RestJsonDoesntDeserializeNullStructureValues", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesInputOutput" }, "idempotent": true }, "description": "Rest Json should not deserialize null structure values", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"stringValue\": null\n}" } }, { "id": "RestJsonSupportsNaNFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesInputOutput" }, "idempotent": true }, "description": "Supports handling NaN float values.", "result": { "floatValue": "NaN", "doubleValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"floatValue\": \"NaN\",\n \"DoubleDribble\": \"NaN\"\n}" } }, { "id": "RestJsonSupportsInfinityFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesInputOutput" }, "idempotent": true }, "description": "Supports handling Infinity float values.", "result": { "floatValue": "Infinity", "doubleValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"floatValue\": \"Infinity\",\n \"DoubleDribble\": \"Infinity\"\n}" } }, { "id": "RestJsonSupportsNegativeInfinityFloatInputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesInputOutput" }, "idempotent": true }, "description": "Supports handling -Infinity float values.", "result": { "floatValue": "-Infinity", "doubleValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/json" }, "body": "{\n \"floatValue\": \"-Infinity\",\n \"DoubleDribble\": \"-Infinity\"\n}" } } ] }, { "description": "Test cases for TimestampFormatHeaders operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": { "TimestampFormatHeadersIO": { "type": "structure", "members": { "memberEpochSeconds": { "shape": "SyntheticTimestamp_epoch_seconds", "location": "header", "locationName": "X-memberEpochSeconds" }, "memberHttpDate": { "shape": "SyntheticTimestamp_http_date", "location": "header", "locationName": "X-memberHttpDate" }, "memberDateTime": { "shape": "SyntheticTimestamp_date_time", "location": "header", "locationName": "X-memberDateTime" }, "defaultFormat": { "shape": "Timestamp", "location": "header", "locationName": "X-defaultFormat" }, "targetEpochSeconds": { "shape": "EpochSeconds", "location": "header", "locationName": "X-targetEpochSeconds" }, "targetHttpDate": { "shape": "HttpDate", "location": "header", "locationName": "X-targetHttpDate" }, "targetDateTime": { "shape": "DateTime", "location": "header", "locationName": "X-targetDateTime" } } }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "Timestamp": { "type": "timestamp" }, "EpochSeconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "HttpDate": { "type": "timestamp", "timestampFormat": "rfc822" }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "RestJsonTimestampFormatHeaders", "given": { "name": "TimestampFormatHeaders", "http": { "method": "POST", "requestUri": "/TimestampFormatHeaders", "responseCode": 200 }, "output": { "shape": "TimestampFormatHeadersIO" }, "documentation": "

This example tests how timestamp request and response headers are serialized.

" }, "description": "Tests how timestamp response headers are serialized", "result": { "memberEpochSeconds": 1576540098, "memberHttpDate": 1576540098, "memberDateTime": 1576540098, "defaultFormat": 1576540098, "targetEpochSeconds": 1576540098, "targetHttpDate": 1576540098, "targetDateTime": 1576540098 }, "response": { "status_code": 200, "headers": { "X-defaultFormat": "Mon, 16 Dec 2019 23:48:18 GMT", "X-memberDateTime": "2019-12-16T23:48:18Z", "X-memberEpochSeconds": "1576540098", "X-memberHttpDate": "Mon, 16 Dec 2019 23:48:18 GMT", "X-targetDateTime": "2019-12-16T23:48:18Z", "X-targetEpochSeconds": "1576540098", "X-targetHttpDate": "Mon, 16 Dec 2019 23:48:18 GMT" } } } ] }, { "description": "Test cases for UnitInputAndOutput operation", "metadata": { "protocol": "rest-json", "protocols": [ "rest-json" ], "apiVersion": "2019-12-16" }, "shapes": {}, "cases": [ { "id": "RestJsonUnitInputAndOutputNoOutput", "given": { "name": "UnitInputAndOutput", "http": { "method": "POST", "requestUri": "/UnitInputAndOutput", "responseCode": 200 }, "documentation": "

This test is similar to NoInputAndNoOutput, but uses explicit Unit types.

" }, "description": "When an operation defines Unit output, the service will respond\nwith an empty payload, and may optionally include the content-type\nheader.", "result": {}, "response": { "status_code": 200, "body": "" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/rest-xml.json000066400000000000000000005150601510465277300313700ustar00rootroot00000000000000[ { "description": "Test cases for BodyWithXmlName operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "BodyWithXmlNameInputOutput": { "type": "structure", "members": { "nested": { "shape": "PayloadWithXmlName" } }, "locationName": "Ahoy" }, "PayloadWithXmlName": { "type": "structure", "members": { "name": { "shape": "String" } }, "locationName": "Hello" }, "String": { "type": "string" } }, "cases": [ { "id": "BodyWithXmlName", "given": { "name": "BodyWithXmlName", "http": { "method": "PUT", "requestUri": "/BodyWithXmlName", "responseCode": 200 }, "output": { "shape": "BodyWithXmlNameInputOutput" }, "documentation": "

The following example serializes a body that uses an XML name, changing the wrapper name.

", "idempotent": true }, "description": "Serializes a payload using a wrapper name based on the xmlName", "result": { "nested": { "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "Phreddy" } } ] }, { "description": "Test cases for DatetimeOffsets operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "DatetimeOffsetsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "RestXmlDateTimeWithNegativeOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/DatetimeOffsets", "responseCode": 200 }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 2019-12-16T22:48:18-01:00\n\n" } }, { "id": "RestXmlDateTimeWithPositiveOffset", "given": { "name": "DatetimeOffsets", "http": { "method": "POST", "requestUri": "/DatetimeOffsets", "responseCode": 200 }, "output": { "shape": "DatetimeOffsetsOutput" } }, "description": "Ensures that clients can correctly parse datetime (timestamps) with offsets", "result": { "datetime": 1576540098 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 2019-12-17T00:48:18+01:00\n\n" } } ] }, { "description": "Test cases for EmptyInputAndEmptyOutput operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "EmptyInputAndEmptyOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "EmptyInputAndEmptyOutput", "given": { "name": "EmptyInputAndEmptyOutput", "http": { "method": "POST", "requestUri": "/EmptyInputAndEmptyOutput", "responseCode": 200 }, "output": { "shape": "EmptyInputAndEmptyOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has an empty input and empty output structure that reuses the same shape. While this should be rare, code generators must support this.

" }, "description": "Empty output serializes no payload", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for FlattenedXmlMap operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "FlattenedXmlMapResponse": { "type": "structure", "members": { "myMap": { "shape": "FooEnumMap", "flattened": true } } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "String": { "type": "string" } }, "cases": [ { "id": "FlattenedXmlMap", "given": { "name": "FlattenedXmlMap", "http": { "method": "POST", "requestUri": "/FlattenedXmlMap", "responseCode": 200 }, "output": { "shape": "FlattenedXmlMapResponse" }, "documentation": "

Flattened maps

" }, "description": "Serializes flattened XML maps in responses", "result": { "myMap": { "foo": "Foo", "baz": "Baz" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n foo\n Foo\n \n \n baz\n Baz\n \n" } } ] }, { "description": "Test cases for FlattenedXmlMapWithXmlName operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "FlattenedXmlMapWithXmlNameResponse": { "type": "structure", "members": { "myMap": { "shape": "FlattenedXmlMapWithXmlNameInputOutputMap", "flattened": true, "locationName": "KVP" } } }, "FlattenedXmlMapWithXmlNameInputOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "K" }, "value": { "shape": "String", "locationName": "V" } }, "String": { "type": "string" } }, "cases": [ { "id": "FlattenedXmlMapWithXmlName", "given": { "name": "FlattenedXmlMapWithXmlName", "http": { "method": "POST", "requestUri": "/FlattenedXmlMapWithXmlName", "responseCode": 200 }, "output": { "shape": "FlattenedXmlMapWithXmlNameResponse" }, "documentation": "

Flattened maps with @xmlName

" }, "description": "Serializes flattened XML maps in responses that have xmlName on members", "result": { "myMap": { "a": "A", "b": "B" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n a\n A\n \n \n b\n B\n \n" } } ] }, { "description": "Test cases for FlattenedXmlMapWithXmlNamespace operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "FlattenedXmlMapWithXmlNamespaceOutput": { "type": "structure", "members": { "myMap": { "shape": "FlattenedXmlMapWithXmlNamespaceOutputMap", "flattened": true, "locationName": "KVP", "xmlNamespace": "https://the-member.example.com" } } }, "FlattenedXmlMapWithXmlNamespaceOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "K", "xmlNamespace": "https://the-key.example.com" }, "value": { "shape": "String", "locationName": "V", "xmlNamespace": "https://the-value.example.com" } }, "String": { "type": "string" } }, "cases": [ { "id": "RestXmlFlattenedXmlMapWithXmlNamespace", "given": { "name": "FlattenedXmlMapWithXmlNamespace", "http": { "method": "POST", "requestUri": "/FlattenedXmlMapWithXmlNamespace", "responseCode": 200 }, "output": { "shape": "FlattenedXmlMapWithXmlNamespaceOutput" }, "documentation": "

Flattened maps with @xmlNamespace and @xmlName

" }, "description": "Serializes flattened XML maps in responses that have xmlNamespace and xmlName on members", "result": { "myMap": { "a": "A", "b": "B" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n a\n A\n \n \n b\n B\n \n" } } ] }, { "description": "Test cases for FractionalSeconds operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "FractionalSecondsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "RestXmlDateTimeWithFractionalSeconds", "given": { "name": "FractionalSeconds", "http": { "method": "POST", "requestUri": "/FractionalSeconds", "responseCode": 200 }, "output": { "shape": "FractionalSecondsOutput" } }, "description": "Ensures that clients can correctly parse datetime timestamps with fractional seconds", "result": { "datetime": 9.46845296123E8 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 2000-01-02T20:34:56.123Z\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "GreetingWithErrorsOutput": { "type": "structure", "members": { "greeting": { "shape": "String", "location": "header", "locationName": "X-Greeting" } } }, "String": { "type": "string" } }, "cases": [ { "id": "GreetingWithErrors", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "output": { "shape": "GreetingWithErrorsOutput" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true }, "description": "Ensures that operations with errors successfully know how to deserialize the successful response", "result": { "greeting": "Hello" }, "response": { "status_code": 200, "headers": { "X-Greeting": "Hello" }, "body": "" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "error": { "httpStatusCode": 400, "senderFault": true }, "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "InvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple XML errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "application/xml" }, "body": "\n \n Sender\n InvalidGreeting\n Hi\n setting\n \n foo-id\n\n" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "ComplexError": { "type": "structure", "members": { "Header": { "shape": "String", "location": "header", "locationName": "X-Header" }, "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "error": { "httpStatusCode": 403, "senderFault": true }, "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String" } } } }, "cases": [ { "id": "ComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "PUT", "requestUri": "/GreetingWithErrors", "responseCode": 200 }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A BadRequest error.

Implementations must be able to successfully take a response and properly (de)serialize successful and error responses based on the the presence of the

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "errorCode": "ComplexError", "error": { "Header": "Header", "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 403, "headers": { "Content-Type": "application/xml", "X-Header": "Header" }, "body": "\n \n Sender\n ComplexError\n Hi\n Top level\n \n bar\n \n \n foo-id\n\n" } } ] }, { "description": "Test cases for HttpEnumPayload operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "EnumPayloadInput": { "type": "structure", "members": { "payload": { "shape": "StringEnum" } }, "payload": "payload" }, "StringEnum": { "type": "string", "enum": [ "enumvalue" ] } }, "cases": [ { "id": "RestXmlEnumPayloadResponse", "given": { "name": "HttpEnumPayload", "http": { "method": "POST", "requestUri": "/EnumPayload", "responseCode": 200 }, "output": { "shape": "EnumPayloadInput" } }, "result": { "payload": "enumvalue" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/plain" }, "body": "enumvalue" } } ] }, { "description": "Test cases for HttpPayloadTraits operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadTraitsInputOutput": { "type": "structure", "members": { "foo": { "shape": "String", "location": "header", "locationName": "X-Foo" }, "blob": { "shape": "Blob" } }, "payload": "blob" }, "String": { "type": "string" }, "Blob": { "type": "blob" } }, "cases": [ { "id": "HttpPayloadTraitsWithBlob", "given": { "name": "HttpPayloadTraits", "http": { "method": "POST", "requestUri": "/HttpPayloadTraits", "responseCode": 200 }, "output": { "shape": "HttpPayloadTraitsInputOutput" }, "documentation": "

This example serializes a blob shape in the payload.

In this example, no XML document is synthesized because the payload is not a structure or a union type.

" }, "description": "Serializes a blob in the HTTP payload", "result": { "foo": "Foo", "blob": "blobby blob blob" }, "response": { "status_code": 200, "headers": { "X-Foo": "Foo" }, "body": "blobby blob blob" } }, { "id": "HttpPayloadTraitsWithNoBlobBody", "given": { "name": "HttpPayloadTraits", "http": { "method": "POST", "requestUri": "/HttpPayloadTraits", "responseCode": 200 }, "output": { "shape": "HttpPayloadTraitsInputOutput" }, "documentation": "

This example serializes a blob shape in the payload.

In this example, no XML document is synthesized because the payload is not a structure or a union type.

" }, "description": "Serializes an empty blob in the HTTP payload", "result": { "foo": "Foo" }, "response": { "status_code": 200, "headers": { "X-Foo": "Foo" }, "body": "" } } ] }, { "description": "Test cases for HttpPayloadWithMemberXmlName operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithMemberXmlNameInputOutput": { "type": "structure", "members": { "nested": { "shape": "PayloadWithXmlName", "locationName": "Hola" } }, "payload": "nested" }, "PayloadWithXmlName": { "type": "structure", "members": { "name": { "shape": "String" } }, "locationName": "Hello" }, "String": { "type": "string" } }, "cases": [ { "id": "HttpPayloadWithMemberXmlName", "given": { "name": "HttpPayloadWithMemberXmlName", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithMemberXmlName", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithMemberXmlNameInputOutput" }, "documentation": "

The following example serializes a payload that uses an XML name on the member, changing the wrapper name.

", "idempotent": true }, "description": "Serializes a structure in the payload using a wrapper name based on member xmlName", "result": { "nested": { "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "Phreddy" } } ] }, { "description": "Test cases for HttpPayloadWithStructure operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithStructureInputOutput": { "type": "structure", "members": { "nested": { "shape": "NestedPayload", "locationName": "NestedPayload" } }, "payload": "nested" }, "NestedPayload": { "type": "structure", "members": { "greeting": { "shape": "String" }, "name": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "HttpPayloadWithStructure", "given": { "name": "HttpPayloadWithStructure", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithStructure", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithStructureInputOutput" }, "documentation": "

This example serializes a structure in the payload.

Note that serializing a structure changes the wrapper element name to match the targeted structure.

", "idempotent": true }, "description": "Serializes a structure in the payload", "result": { "nested": { "greeting": "hello", "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n hello\n Phreddy\n\n" } } ] }, { "description": "Test cases for HttpPayloadWithUnion operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithUnionInputOutput": { "type": "structure", "members": { "nested": { "shape": "UnionPayload", "locationName": "nested" } }, "payload": "nested" }, "UnionPayload": { "type": "structure", "members": { "greeting": { "shape": "String" } }, "union": true }, "String": { "type": "string" } }, "cases": [ { "id": "RestXmlHttpPayloadWithUnion", "given": { "name": "HttpPayloadWithUnion", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithUnion", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithUnionInputOutput" }, "documentation": "

This example serializes a union in the payload.

", "idempotent": true }, "description": "Serializes a union in the payload.", "result": { "nested": { "greeting": "hello" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n hello\n" } }, { "id": "RestXmlHttpPayloadWithUnsetUnion", "given": { "name": "HttpPayloadWithUnion", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithUnion", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithUnionInputOutput" }, "documentation": "

This example serializes a union in the payload.

", "idempotent": true }, "description": "No payload is sent if the union has no value.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Length": "0" }, "body": "" } } ] }, { "description": "Test cases for HttpPayloadWithXmlName operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithXmlNameInputOutput": { "type": "structure", "members": { "nested": { "shape": "PayloadWithXmlName", "locationName": "nested" } }, "payload": "nested" }, "PayloadWithXmlName": { "type": "structure", "members": { "name": { "shape": "String" } }, "locationName": "Hello" }, "String": { "type": "string" } }, "cases": [ { "id": "HttpPayloadWithXmlName", "given": { "name": "HttpPayloadWithXmlName", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithXmlName", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithXmlNameInputOutput" }, "documentation": "

The following example serializes a payload that uses an XML name, changing the wrapper name.

", "idempotent": true }, "description": "Serializes a structure in the payload using a wrapper name based on xmlName", "result": { "nested": { "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "Phreddy" } } ] }, { "description": "Test cases for HttpPayloadWithXmlNamespace operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithXmlNamespaceInputOutput": { "type": "structure", "members": { "nested": { "shape": "PayloadWithXmlNamespace", "locationName": "nested" } }, "payload": "nested" }, "PayloadWithXmlNamespace": { "type": "structure", "members": { "name": { "shape": "String" } }, "xmlNamespace": "http://foo.com" }, "String": { "type": "string" } }, "cases": [ { "id": "HttpPayloadWithXmlNamespace", "given": { "name": "HttpPayloadWithXmlNamespace", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithXmlNamespace", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithXmlNamespaceInputOutput" }, "documentation": "

The following example serializes a payload that uses an XML namespace.

", "idempotent": true }, "description": "Serializes a structure in the payload using a wrapper with an XML namespace", "result": { "nested": { "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n Phreddy\n" } } ] }, { "description": "Test cases for HttpPayloadWithXmlNamespaceAndPrefix operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPayloadWithXmlNamespaceAndPrefixInputOutput": { "type": "structure", "members": { "nested": { "shape": "PayloadWithXmlNamespaceAndPrefix", "locationName": "nested" } }, "payload": "nested" }, "PayloadWithXmlNamespaceAndPrefix": { "type": "structure", "members": { "name": { "shape": "String" } }, "xmlNamespace": { "prefix": "baz", "uri": "http://foo.com" } }, "String": { "type": "string" } }, "cases": [ { "id": "HttpPayloadWithXmlNamespaceAndPrefix", "given": { "name": "HttpPayloadWithXmlNamespaceAndPrefix", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithXmlNamespaceAndPrefix", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithXmlNamespaceAndPrefixInputOutput" }, "documentation": "

The following example serializes a payload that uses an XML namespace.

", "idempotent": true }, "description": "Serializes a structure in the payload using a wrapper with an XML namespace", "result": { "nested": { "name": "Phreddy" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n Phreddy\n" } } ] }, { "description": "Test cases for HttpPrefixHeaders operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpPrefixHeadersInputOutput": { "type": "structure", "members": { "foo": { "shape": "String", "location": "header", "locationName": "x-foo" }, "fooMap": { "shape": "FooPrefixHeaders", "location": "headers", "locationName": "x-foo-" } } }, "String": { "type": "string" }, "FooPrefixHeaders": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } } }, "cases": [ { "id": "HttpPrefixHeadersArePresent", "given": { "name": "HttpPrefixHeaders", "http": { "method": "GET", "requestUri": "/HttpPrefixHeaders", "responseCode": 200 }, "output": { "shape": "HttpPrefixHeadersInputOutput" }, "documentation": "

This examples adds headers to the input of a request and response by prefix.

" }, "description": "Adds headers by prefix", "result": { "foo": "Foo", "fooMap": { "abc": "Abc value", "def": "Def value" } }, "response": { "status_code": 200, "headers": { "x-foo": "Foo", "x-foo-abc": "Abc value", "x-foo-def": "Def value" }, "body": "" } }, { "id": "HttpPrefixHeadersAreNotPresent", "given": { "name": "HttpPrefixHeaders", "http": { "method": "GET", "requestUri": "/HttpPrefixHeaders", "responseCode": 200 }, "output": { "shape": "HttpPrefixHeadersInputOutput" }, "documentation": "

This examples adds headers to the input of a request and response by prefix.

" }, "description": "No prefix headers are serialized because the value is empty", "result": { "foo": "Foo", "fooMap": {} }, "response": { "status_code": 200, "headers": { "x-foo": "Foo" }, "body": "" } } ] }, { "description": "Test cases for HttpResponseCode operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "HttpResponseCodeOutput": { "type": "structure", "members": { "Status": { "shape": "Integer", "location": "statusCode" } } }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "RestXmlHttpResponseCode", "given": { "name": "HttpResponseCode", "http": { "method": "PUT", "requestUri": "/HttpResponseCode", "responseCode": 200 }, "output": { "shape": "HttpResponseCodeOutput" }, "idempotent": true }, "description": "Binds the http response code to an output structure.", "result": { "Status": 201 }, "response": { "status_code": 201, "headers": { "Content-Type": "application/xml" }, "body": "" } } ] }, { "description": "Test cases for HttpStringPayload operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "StringPayloadInput": { "type": "structure", "members": { "payload": { "shape": "String" } }, "payload": "payload" }, "String": { "type": "string" } }, "cases": [ { "id": "RestXmlStringPayloadResponse", "given": { "name": "HttpStringPayload", "http": { "method": "POST", "requestUri": "/StringPayload", "responseCode": 200 }, "output": { "shape": "StringPayloadInput" } }, "result": { "payload": "rawstring" }, "response": { "status_code": 200, "headers": { "Content-Type": "text/plain" }, "body": "rawstring" } } ] }, { "description": "Test cases for IgnoreQueryParamsInResponse operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "IgnoreQueryParamsInResponseOutput": { "type": "structure", "members": { "baz": { "shape": "String", "location": "uri", "locationName": "baz" } } }, "String": { "type": "string" } }, "cases": [ { "id": "IgnoreQueryParamsInResponse", "given": { "name": "IgnoreQueryParamsInResponse", "http": { "method": "GET", "requestUri": "/IgnoreQueryParamsInResponse", "responseCode": 200 }, "output": { "shape": "IgnoreQueryParamsInResponseOutput" }, "documentation": "

This example ensures that query string bound request parameters are serialized in the body of responses if the structure is used in both the request and response.

" }, "description": "Query parameters must be ignored when serializing the output of an operation", "result": { "baz": "bam" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "bam" } } ] }, { "description": "Test cases for InputAndOutputWithHeaders operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "InputAndOutputWithHeadersIO": { "type": "structure", "members": { "headerString": { "shape": "String", "location": "header", "locationName": "X-String" }, "headerByte": { "shape": "Integer", "location": "header", "locationName": "X-Byte" }, "headerShort": { "shape": "Integer", "location": "header", "locationName": "X-Short" }, "headerInteger": { "shape": "Integer", "location": "header", "locationName": "X-Integer" }, "headerLong": { "shape": "Long", "location": "header", "locationName": "X-Long" }, "headerFloat": { "shape": "Float", "location": "header", "locationName": "X-Float" }, "headerDouble": { "shape": "Double", "location": "header", "locationName": "X-Double" }, "headerTrueBool": { "shape": "Boolean", "location": "header", "locationName": "X-Boolean1" }, "headerFalseBool": { "shape": "Boolean", "location": "header", "locationName": "X-Boolean2" }, "headerStringList": { "shape": "StringList", "location": "header", "locationName": "X-StringList" }, "headerStringSet": { "shape": "StringSet", "location": "header", "locationName": "X-StringSet" }, "headerIntegerList": { "shape": "IntegerList", "location": "header", "locationName": "X-IntegerList" }, "headerBooleanList": { "shape": "BooleanList", "location": "header", "locationName": "X-BooleanList" }, "headerTimestampList": { "shape": "TimestampList", "location": "header", "locationName": "X-TimestampList" }, "headerEnum": { "shape": "FooEnum", "location": "header", "locationName": "X-Enum" }, "headerEnumList": { "shape": "FooEnumList", "location": "header", "locationName": "X-EnumList" } } }, "String": { "type": "string" }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true }, "Boolean": { "type": "boolean", "box": true }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "Timestamp": { "type": "timestamp" } }, "cases": [ { "id": "InputAndOutputWithStringHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with string header bindings", "result": { "headerString": "Hello", "headerStringList": [ "a", "b", "c" ], "headerStringSet": [ "a", "b", "c" ] }, "response": { "status_code": 200, "headers": { "X-String": "Hello", "X-StringList": "a, b, c", "X-StringSet": "a, b, c" }, "body": "" } }, { "id": "InputAndOutputWithNumericHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with numeric header bindings", "result": { "headerByte": 1, "headerShort": 123, "headerInteger": 123, "headerLong": 123, "headerFloat": 1.1, "headerDouble": 1.1, "headerIntegerList": [ 1, 2, 3 ] }, "response": { "status_code": 200, "headers": { "X-Byte": "1", "X-Double": "1.1", "X-Float": "1.1", "X-Integer": "123", "X-IntegerList": "1, 2, 3", "X-Long": "123", "X-Short": "123" }, "body": "" } }, { "id": "InputAndOutputWithBooleanHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with boolean header bindings", "result": { "headerTrueBool": true, "headerFalseBool": false, "headerBooleanList": [ true, false, true ] }, "response": { "status_code": 200, "headers": { "X-Boolean1": "true", "X-Boolean2": "false", "X-BooleanList": "true, false, true" }, "body": "" } }, { "id": "InputAndOutputWithTimestampHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with timestamp header bindings", "result": { "headerTimestampList": [ 1576540098, 1576540098 ] }, "response": { "status_code": 200, "headers": { "X-TimestampList": "Mon, 16 Dec 2019 23:48:18 GMT, Mon, 16 Dec 2019 23:48:18 GMT" }, "body": "" } }, { "id": "InputAndOutputWithEnumHeaders", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Tests responses with enum header bindings", "result": { "headerEnum": "Foo", "headerEnumList": [ "Foo", "Bar", "Baz" ] }, "response": { "status_code": 200, "headers": { "X-Enum": "Foo", "X-EnumList": "Foo, Bar, Baz" }, "body": "" } }, { "id": "RestXmlSupportsNaNFloatHeaderOutputs", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Supports handling NaN float header values.", "result": { "headerFloat": "NaN", "headerDouble": "NaN" }, "response": { "status_code": 200, "headers": { "X-Double": "NaN", "X-Float": "NaN" }, "body": "" } }, { "id": "RestXmlSupportsInfinityFloatHeaderOutputs", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Supports handling Infinity float header values.", "result": { "headerFloat": "Infinity", "headerDouble": "Infinity" }, "response": { "status_code": 200, "headers": { "X-Double": "Infinity", "X-Float": "Infinity" }, "body": "" } }, { "id": "RestXmlSupportsNegativeInfinityFloatHeaderOutputs", "given": { "name": "InputAndOutputWithHeaders", "http": { "method": "POST", "requestUri": "/InputAndOutputWithHeaders", "responseCode": 200 }, "output": { "shape": "InputAndOutputWithHeadersIO" }, "documentation": "

The example tests how requests and responses are serialized when there is no input or output payload but there are HTTP header bindings.

" }, "description": "Supports handling -Infinity float header values.", "result": { "headerFloat": "-Infinity", "headerDouble": "-Infinity" }, "response": { "status_code": 200, "headers": { "X-Double": "-Infinity", "X-Float": "-Infinity" }, "body": "" } } ] }, { "description": "Test cases for NestedXmlMaps operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "NestedXmlMapsResponse": { "type": "structure", "members": { "nestedMap": { "shape": "NestedMap" }, "flatNestedMap": { "shape": "NestedMap", "flattened": true } } }, "NestedMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnumMap" } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "String": { "type": "string" } }, "cases": [ { "id": "NestedXmlMapResponse", "given": { "name": "NestedXmlMaps", "http": { "method": "POST", "requestUri": "/NestedXmlMaps", "responseCode": 200 }, "output": { "shape": "NestedXmlMapsResponse" } }, "description": "Tests responses with nested maps.", "result": { "nestedMap": { "foo": { "bar": "Bar" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n foo\n \n \n bar\n Bar\n \n \n \n \n" } }, { "id": "FlatNestedXmlMapResponse", "given": { "name": "NestedXmlMaps", "http": { "method": "POST", "requestUri": "/NestedXmlMaps", "responseCode": 200 }, "output": { "shape": "NestedXmlMapsResponse" } }, "description": "Tests responses with nested flat maps. Since maps can only be\nflattened when they're structure members, only the outer map is flat.", "result": { "flatNestedMap": { "foo": { "bar": "Bar" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n foo\n \n \n bar\n Bar\n \n \n \n" } } ] }, { "description": "Test cases for NestedXmlMapWithXmlName operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "NestedXmlMapWithXmlNameInputOutput": { "type": "structure", "members": { "nestedXmlMapWithXmlNameMap": { "shape": "NestedXmlMapWithXmlNameMap" } } }, "NestedXmlMapWithXmlNameMap": { "type": "map", "key": { "shape": "String", "locationName": "OuterKey" }, "value": { "shape": "NestedXmlMapWithXmlNameInnerMap" } }, "NestedXmlMapWithXmlNameInnerMap": { "type": "map", "key": { "shape": "String", "locationName": "InnerKey" }, "value": { "shape": "String", "locationName": "InnerValue" } }, "String": { "type": "string" } }, "cases": [ { "id": "NestedXmlMapWithXmlNameDeserializes", "given": { "name": "NestedXmlMapWithXmlName", "http": { "method": "POST", "requestUri": "/NestedXmlMapWithXmlName", "responseCode": 200 }, "output": { "shape": "NestedXmlMapWithXmlNameInputOutput" }, "documentation": "

Nested Xml Maps with key/values with @xmlName

" }, "description": "Serializes nested XML maps in responses that have xmlName on members", "result": { "nestedXmlMapWithXmlNameMap": { "foo": { "bar": "Baz", "fizz": "Buzz" }, "qux": { "foobar": "Bar", "fizzbuzz": "Buzz" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": " \n \n \n foo\n \n \n bar\n Baz\n \n \n fizz\n Buzz\n \n \n \n \n qux\n \n \n foobar\n Bar\n \n \n fizzbuzz\n Buzz\n \n \n \n \n \n" } } ] }, { "description": "Test cases for NoInputAndNoOutput operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": {}, "cases": [ { "id": "NoInputAndNoOutput", "given": { "name": "NoInputAndNoOutput", "http": { "method": "POST", "requestUri": "/NoInputAndNoOutput", "responseCode": 200 }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input or output. While this should be rare, code generators must support this.

" }, "description": "No output serializes no payload", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for NoInputAndOutput operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "NoInputAndOutputOutput": { "type": "structure", "members": {} } }, "cases": [ { "id": "NoInputAndOutput", "given": { "name": "NoInputAndOutput", "http": { "method": "POST", "requestUri": "/NoInputAndOutputOutput", "responseCode": 200 }, "output": { "shape": "NoInputAndOutputOutput" }, "documentation": "

The example tests how requests and responses are serialized when there's no request or response payload because the operation has no input and the output is empty. While this should be rare, code generators must support this.

" }, "description": "Empty output serializes no payload", "result": {}, "response": { "status_code": 200, "body": "" } } ] }, { "description": "Test cases for RecursiveShapes operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "RecursiveShapesResponse": { "type": "structure", "members": { "nested": { "shape": "RecursiveShapesInputOutputNested1" } } }, "RecursiveShapesInputOutputNested1": { "type": "structure", "members": { "foo": { "shape": "String" }, "nested": { "shape": "RecursiveShapesInputOutputNested2" } } }, "String": { "type": "string" }, "RecursiveShapesInputOutputNested2": { "type": "structure", "members": { "bar": { "shape": "String" }, "recursiveMember": { "shape": "RecursiveShapesInputOutputNested1" } } } }, "cases": [ { "id": "RecursiveShapes", "given": { "name": "RecursiveShapes", "http": { "method": "PUT", "requestUri": "/RecursiveShapes", "responseCode": 200 }, "output": { "shape": "RecursiveShapesResponse" }, "documentation": "

Recursive shapes

", "idempotent": true }, "description": "Serializes recursive structures", "result": { "nested": { "foo": "Foo1", "nested": { "bar": "Bar1", "recursiveMember": { "foo": "Foo2", "nested": { "bar": "Bar2" } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n Foo1\n \n Bar1\n \n Foo2\n \n Bar2\n \n \n \n \n\n" } } ] }, { "description": "Test cases for SimpleScalarProperties operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "SimpleScalarPropertiesResponse": { "type": "structure", "members": { "foo": { "shape": "String", "location": "header", "locationName": "X-Foo" }, "stringValue": { "shape": "String" }, "trueBooleanValue": { "shape": "Boolean" }, "falseBooleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "shortValue": { "shape": "Integer" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double", "locationName": "DoubleDribble" } } }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "SimpleScalarProperties", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "foo": "Foo", "stringValue": "string", "trueBooleanValue": true, "falseBooleanValue": false, "byteValue": 1, "shortValue": 2, "integerValue": 3, "longValue": 4, "floatValue": 5.5, "doubleValue": 6.5 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml", "X-Foo": "Foo" }, "body": "\n string\n true\n false\n 1\n 2\n 3\n 4\n 5.5\n 6.5\n\n" } }, { "id": "SimpleScalarPropertiesComplexEscapes", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Serializes string with escaping.\n\nThis validates the three escape types: literal, decimal and hexadecimal. It also validates that unescaping properly\nhandles the case where unescaping an & produces a newly formed escape sequence (this should not be re-unescaped).\n\nServers may produce different output, this test is designed different unescapes clients must handle\n", "result": { "foo": "Foo", "stringValue": "escaped data: <\r\n" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml", "X-Foo": "Foo" }, "body": "\n escaped data: &lt; \n\n" } }, { "id": "SimpleScalarPropertiesWithEscapedCharacter", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Serializes string with escaping", "result": { "foo": "Foo", "stringValue": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml", "X-Foo": "Foo" }, "body": "\n <string>\n\n" } }, { "id": "SimpleScalarPropertiesWithXMLPreamble", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Serializes simple scalar properties with xml preamble, comments and CDATA", "result": { "foo": "Foo", "stringValue": "string" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml", "X-Foo": "Foo" }, "body": "\n\n \n string\n \n\n" } }, { "id": "SimpleScalarPropertiesWithWhiteSpace", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Serializes string containing white space", "result": { "foo": "Foo", "stringValue": " string with white space " }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml", "X-Foo": "Foo" }, "body": "\n\n string with white space \n\n" } }, { "id": "SimpleScalarPropertiesPureWhiteSpace", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Serializes string containing white space", "result": { "foo": "Foo", "stringValue": " " }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml", "X-Foo": "Foo" }, "body": "\n\n \n\n" } }, { "id": "RestXmlSupportsNaNFloatOutputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Supports handling NaN float values.", "result": { "floatValue": "NaN", "doubleValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n NaN\n NaN\n\n" } }, { "id": "RestXmlSupportsInfinityFloatOutputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Supports handling Infinity float values.", "result": { "floatValue": "Infinity", "doubleValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n Infinity\n Infinity\n\n" } }, { "id": "RestXmlSupportsNegativeInfinityFloatOutputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "PUT", "requestUri": "/SimpleScalarProperties", "responseCode": 200 }, "output": { "shape": "SimpleScalarPropertiesResponse" }, "idempotent": true }, "description": "Supports handling -Infinity float values.", "result": { "floatValue": "-Infinity", "doubleValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n -Infinity\n -Infinity\n\n" } } ] }, { "description": "Test cases for TimestampFormatHeaders operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "TimestampFormatHeadersIO": { "type": "structure", "members": { "memberEpochSeconds": { "shape": "SyntheticTimestamp_epoch_seconds", "location": "header", "locationName": "X-memberEpochSeconds" }, "memberHttpDate": { "shape": "SyntheticTimestamp_http_date", "location": "header", "locationName": "X-memberHttpDate" }, "memberDateTime": { "shape": "SyntheticTimestamp_date_time", "location": "header", "locationName": "X-memberDateTime" }, "defaultFormat": { "shape": "Timestamp", "location": "header", "locationName": "X-defaultFormat" }, "targetEpochSeconds": { "shape": "EpochSeconds", "location": "header", "locationName": "X-targetEpochSeconds" }, "targetHttpDate": { "shape": "HttpDate", "location": "header", "locationName": "X-targetHttpDate" }, "targetDateTime": { "shape": "DateTime", "location": "header", "locationName": "X-targetDateTime" } } }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "Timestamp": { "type": "timestamp" }, "EpochSeconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "HttpDate": { "type": "timestamp", "timestampFormat": "rfc822" }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "TimestampFormatHeaders", "given": { "name": "TimestampFormatHeaders", "http": { "method": "POST", "requestUri": "/TimestampFormatHeaders", "responseCode": 200 }, "output": { "shape": "TimestampFormatHeadersIO" }, "documentation": "

The example tests how timestamp request and response headers are serialized.

" }, "description": "Tests how timestamp response headers are serialized", "result": { "memberEpochSeconds": 1576540098, "memberHttpDate": 1576540098, "memberDateTime": 1576540098, "defaultFormat": 1576540098, "targetEpochSeconds": 1576540098, "targetHttpDate": 1576540098, "targetDateTime": 1576540098 }, "response": { "status_code": 200, "headers": { "X-defaultFormat": "Mon, 16 Dec 2019 23:48:18 GMT", "X-memberDateTime": "2019-12-16T23:48:18Z", "X-memberEpochSeconds": "1576540098", "X-memberHttpDate": "Mon, 16 Dec 2019 23:48:18 GMT", "X-targetDateTime": "2019-12-16T23:48:18Z", "X-targetEpochSeconds": "1576540098", "X-targetHttpDate": "Mon, 16 Dec 2019 23:48:18 GMT" }, "body": "" } } ] }, { "description": "Test cases for XmlAttributes operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlAttributesResponse": { "type": "structure", "members": { "foo": { "shape": "String" }, "attr": { "shape": "String", "locationName": "test", "xmlAttribute": true } } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlAttributes", "given": { "name": "XmlAttributes", "http": { "method": "PUT", "requestUri": "/XmlAttributes", "responseCode": 200 }, "output": { "shape": "XmlAttributesResponse" }, "documentation": "

This example serializes an XML attributes on synthesized document.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "foo": "hi", "attr": "test" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n hi\n\n" } } ] }, { "description": "Test cases for XmlAttributesOnPayload operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlAttributesOnPayloadResponse": { "type": "structure", "members": { "payload": { "shape": "XmlAttributesPayloadResponse" } }, "payload": "payload" }, "XmlAttributesPayloadResponse": { "type": "structure", "members": { "foo": { "shape": "String" }, "attr": { "shape": "String", "locationName": "test", "xmlAttribute": true } } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlAttributesOnPayload", "given": { "name": "XmlAttributesOnPayload", "http": { "method": "PUT", "requestUri": "/XmlAttributesOnPayload", "responseCode": 200 }, "output": { "shape": "XmlAttributesOnPayloadResponse" }, "documentation": "

This example serializes an XML attributes on a document targeted by httpPayload.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "payload": { "foo": "hi", "attr": "test" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n hi\n\n" } } ] }, { "description": "Test cases for XmlBlobs operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlBlobsResponse": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "XmlBlobs", "given": { "name": "XmlBlobs", "http": { "method": "POST", "requestUri": "/XmlBlobs", "responseCode": 200 }, "output": { "shape": "XmlBlobsResponse" }, "documentation": "

Blobs are base64 encoded

" }, "description": "Blobs are base64 encoded", "result": { "data": "value" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n dmFsdWU=\n\n" } } ] }, { "description": "Test cases for XmlEmptyBlobs operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlEmptyBlobsResponse": { "type": "structure", "members": { "data": { "shape": "Blob" } } }, "Blob": { "type": "blob" } }, "cases": [ { "id": "XmlEmptyBlobs", "given": { "name": "XmlEmptyBlobs", "http": { "method": "POST", "requestUri": "/XmlEmptyBlobs", "responseCode": 200 }, "output": { "shape": "XmlEmptyBlobsResponse" }, "documentation": "

Blobs are base64 encoded

" }, "description": "Empty blobs are deserialized as empty string", "result": { "data": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n\n" } }, { "id": "XmlEmptySelfClosedBlobs", "given": { "name": "XmlEmptyBlobs", "http": { "method": "POST", "requestUri": "/XmlEmptyBlobs", "responseCode": 200 }, "output": { "shape": "XmlEmptyBlobsResponse" }, "documentation": "

Blobs are base64 encoded

" }, "description": "Empty self closed blobs are deserialized as empty string", "result": { "data": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n\n" } } ] }, { "description": "Test cases for XmlEmptyLists operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlEmptyListsResponse": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "renamedListMembers": { "shape": "RenamedListMembers", "locationName": "renamed" }, "flattenedList": { "shape": "RenamedListMembers", "flattened": true }, "flattenedList2": { "shape": "RenamedListMembers", "flattened": true, "locationName": "customName" }, "flattenedListWithMemberNamespace": { "shape": "ListWithMemberNamespace", "flattened": true }, "flattenedListWithNamespace": { "shape": "ListWithNamespace", "flattened": true }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" }, "flattenedStructureList": { "shape": "StructureList", "flattened": true } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "RenamedListMembers": { "type": "list", "member": { "shape": "String", "locationName": "item" } }, "ListWithMemberNamespace": { "type": "list", "member": { "shape": "String", "xmlNamespace": "https://xml-member.example.com" }, "xmlNamespace": "https://xml-list.example.com" }, "ListWithNamespace": { "type": "list", "member": { "shape": "String" }, "xmlNamespace": "https://xml-list.example.com" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember", "locationName": "item" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "XmlEmptyLists", "given": { "name": "XmlEmptyLists", "http": { "method": "PUT", "requestUri": "/XmlEmptyLists", "responseCode": 200 }, "output": { "shape": "XmlEmptyListsResponse" }, "idempotent": true }, "description": "Deserializes Empty XML lists", "result": { "stringList": [], "stringSet": [] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n\n" } } ] }, { "description": "Test cases for XmlEmptyMaps operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlEmptyMapsResponse": { "type": "structure", "members": { "myMap": { "shape": "XmlMapsInputOutputMap" } } }, "XmlMapsInputOutputMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "GreetingStruct" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlEmptyMaps", "given": { "name": "XmlEmptyMaps", "http": { "method": "POST", "requestUri": "/XmlEmptyMaps", "responseCode": 200 }, "output": { "shape": "XmlEmptyMapsResponse" } }, "description": "Deserializes Empty XML maps", "result": { "myMap": {} }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n\n" } }, { "id": "XmlEmptySelfClosedMaps", "given": { "name": "XmlEmptyMaps", "http": { "method": "POST", "requestUri": "/XmlEmptyMaps", "responseCode": 200 }, "output": { "shape": "XmlEmptyMapsResponse" } }, "description": "Deserializes Empty Self-closed XML maps", "result": { "myMap": {} }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n\n" } } ] }, { "description": "Test cases for XmlEmptyStrings operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlEmptyStringsResponse": { "type": "structure", "members": { "emptyString": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlEmptyStrings", "given": { "name": "XmlEmptyStrings", "http": { "method": "PUT", "requestUri": "/XmlEmptyStrings", "responseCode": 200 }, "output": { "shape": "XmlEmptyStringsResponse" }, "idempotent": true }, "description": "Deserializes xml empty strings", "result": { "emptyString": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n\n" } }, { "id": "XmlEmptySelfClosedStrings", "given": { "name": "XmlEmptyStrings", "http": { "method": "PUT", "requestUri": "/XmlEmptyStrings", "responseCode": 200 }, "output": { "shape": "XmlEmptyStringsResponse" }, "idempotent": true }, "description": "Empty self closed string are deserialized as empty string", "result": { "emptyString": "" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n\n" } } ] }, { "description": "Test cases for XmlEnums operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlEnumsResponse": { "type": "structure", "members": { "fooEnum1": { "shape": "FooEnum" }, "fooEnum2": { "shape": "FooEnum" }, "fooEnum3": { "shape": "FooEnum" }, "fooEnumList": { "shape": "FooEnumList" }, "fooEnumSet": { "shape": "FooEnumSet" }, "fooEnumMap": { "shape": "FooEnumMap" } } }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumSet": { "type": "list", "member": { "shape": "FooEnum" } }, "FooEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "FooEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlEnums", "given": { "name": "XmlEnums", "http": { "method": "PUT", "requestUri": "/XmlEnums", "responseCode": 200 }, "output": { "shape": "XmlEnumsResponse" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "fooEnum1": "Foo", "fooEnum2": "0", "fooEnum3": "1", "fooEnumList": [ "Foo", "0" ], "fooEnumSet": [ "Foo", "0" ], "fooEnumMap": { "hi": "Foo", "zero": "0" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n Foo\n 0\n 1\n \n Foo\n 0\n \n \n Foo\n 0\n \n \n \n hi\n Foo\n \n \n zero\n 0\n \n \n\n" } } ] }, { "description": "Test cases for XmlIntEnums operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlIntEnumsResponse": { "type": "structure", "members": { "intEnum1": { "shape": "IntegerEnum" }, "intEnum2": { "shape": "IntegerEnum" }, "intEnum3": { "shape": "IntegerEnum" }, "intEnumList": { "shape": "IntegerEnumList" }, "intEnumSet": { "shape": "IntegerEnumSet" }, "intEnumMap": { "shape": "IntegerEnumMap" } } }, "IntegerEnum": { "type": "integer", "box": true }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumSet": { "type": "list", "member": { "shape": "IntegerEnum" } }, "IntegerEnumMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "IntegerEnum" } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlIntEnums", "given": { "name": "XmlIntEnums", "http": { "method": "PUT", "requestUri": "/XmlIntEnums", "responseCode": 200 }, "output": { "shape": "XmlIntEnumsResponse" }, "documentation": "

This example serializes enums as top level properties, in lists, sets, and maps.

", "idempotent": true }, "description": "Serializes simple scalar properties", "result": { "intEnum1": 1, "intEnum2": 2, "intEnum3": 3, "intEnumList": [ 1, 2 ], "intEnumSet": [ 1, 2 ], "intEnumMap": { "a": 1, "b": 2 } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 1\n 2\n 3\n \n 1\n 2\n \n \n 1\n 2\n \n \n \n a\n 1\n \n \n b\n 2\n \n \n\n" } } ] }, { "description": "Test cases for XmlLists operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlListsResponse": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "renamedListMembers": { "shape": "RenamedListMembers", "locationName": "renamed" }, "flattenedList": { "shape": "RenamedListMembers", "flattened": true }, "flattenedList2": { "shape": "RenamedListMembers", "flattened": true, "locationName": "customName" }, "flattenedListWithMemberNamespace": { "shape": "ListWithMemberNamespace", "flattened": true }, "flattenedListWithNamespace": { "shape": "ListWithNamespace", "flattened": true }, "structureList": { "shape": "StructureList", "locationName": "myStructureList" }, "flattenedStructureList": { "shape": "StructureList", "flattened": true } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "RenamedListMembers": { "type": "list", "member": { "shape": "String", "locationName": "item" } }, "ListWithMemberNamespace": { "type": "list", "member": { "shape": "String", "xmlNamespace": "https://xml-member.example.com" }, "xmlNamespace": "https://xml-list.example.com" }, "ListWithNamespace": { "type": "list", "member": { "shape": "String" }, "xmlNamespace": "https://xml-list.example.com" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember", "locationName": "item" } }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String", "locationName": "value" }, "b": { "shape": "String", "locationName": "other" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "XmlLists", "given": { "name": "XmlLists", "http": { "method": "PUT", "requestUri": "/XmlLists", "responseCode": 200 }, "output": { "shape": "XmlListsResponse" }, "documentation": "

This test case serializes XML lists for the following cases for both input and output:

  1. Normal XML lists.
  2. Normal XML sets.
  3. XML lists of lists.
  4. XML lists with @xmlName on its members
  5. Flattened XML lists.
  6. Flattened XML lists with @xmlName.
  7. Flattened XML lists with @xmlNamespace.
  8. Lists of structures.
  9. Flattened XML list of structures
", "idempotent": true }, "description": "Tests for XML list serialization", "result": { "stringList": [ "foo", "bar" ], "stringSet": [ "foo", "bar" ], "integerList": [ 1, 2 ], "booleanList": [ true, false ], "timestampList": [ 1398796238, 1398796238 ], "enumList": [ "Foo", "0" ], "intEnumList": [ 1, 2 ], "nestedStringList": [ [ "foo", "bar" ], [ "baz", "qux" ] ], "renamedListMembers": [ "foo", "bar" ], "flattenedList": [ "hi", "bye" ], "flattenedList2": [ "yep", "nope" ], "flattenedListWithMemberNamespace": [ "a", "b" ], "flattenedListWithNamespace": [ "a", "b" ], "structureList": [ { "a": "1", "b": "2" }, { "a": "3", "b": "4" } ], "flattenedStructureList": [ { "a": "5", "b": "6" }, { "a": "7", "b": "8" } ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n foo\n bar\n \n \n foo\n bar\n \n \n 1\n 2\n \n \n true\n false\n \n \n 2014-04-29T18:30:38Z\n 2014-04-29T18:30:38Z\n \n \n Foo\n 0\n \n \n 1\n 2\n \n \n \n foo\n bar\n \n \n baz\n qux\n \n \n \n foo\n bar\n \n hi\n bye\n yep\n nope\n a\n b\n a\n b\n \n \n 1\n 2\n \n \n 3\n 4\n \n \n \n 5\n 6\n \n \n 7\n 8\n \n\n" } } ] }, { "description": "Test cases for XmlMaps operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlMapsResponse": { "type": "structure", "members": { "myMap": { "shape": "XmlMapsInputOutputMap" } } }, "XmlMapsInputOutputMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "GreetingStruct" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlMaps", "given": { "name": "XmlMaps", "http": { "method": "POST", "requestUri": "/XmlMaps", "responseCode": 200 }, "output": { "shape": "XmlMapsResponse" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Tests for XML map serialization", "result": { "myMap": { "foo": { "hi": "there" }, "baz": { "hi": "bye" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n foo\n \n there\n \n \n \n baz\n \n bye\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlMapsXmlName operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlMapsXmlNameResponse": { "type": "structure", "members": { "myMap": { "shape": "XmlMapsXmlNameInputOutputMap" } } }, "XmlMapsXmlNameInputOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "Attribute" }, "value": { "shape": "GreetingStruct", "locationName": "Setting" } }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "XmlMapsXmlName", "given": { "name": "XmlMapsXmlName", "http": { "method": "POST", "requestUri": "/XmlMapsXmlName", "responseCode": 200 }, "output": { "shape": "XmlMapsXmlNameResponse" } }, "description": "Serializes XML lists", "result": { "myMap": { "foo": { "hi": "there" }, "baz": { "hi": "bye" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n foo\n \n there\n \n \n \n baz\n \n bye\n \n \n \n\n" } } ] }, { "description": "Test cases for XmlMapWithXmlNamespace operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlMapWithXmlNamespaceResponse": { "type": "structure", "members": { "myMap": { "shape": "XmlMapWithXmlNamespaceInputOutputMap", "locationName": "KVP", "xmlNamespace": "https://the-member.example.com" } } }, "XmlMapWithXmlNamespaceInputOutputMap": { "type": "map", "key": { "shape": "String", "locationName": "K", "xmlNamespace": "https://the-key.example.com" }, "value": { "shape": "String", "locationName": "V", "xmlNamespace": "https://the-value.example.com" } }, "String": { "type": "string" } }, "cases": [ { "id": "RestXmlXmlMapWithXmlNamespace", "given": { "name": "XmlMapWithXmlNamespace", "http": { "method": "POST", "requestUri": "/XmlMapWithXmlNamespace", "responseCode": 200 }, "output": { "shape": "XmlMapWithXmlNamespaceResponse" }, "documentation": "

Maps with @xmlNamespace and @xmlName

" }, "description": "Serializes XML maps in responses that have xmlNamespace and xmlName on members", "result": { "myMap": { "a": "A", "b": "B" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n a\n A\n \n \n b\n B\n \n \n" } } ] }, { "description": "Test cases for XmlNamespaces operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlNamespacesResponse": { "type": "structure", "members": { "nested": { "shape": "XmlNamespaceNested" } }, "xmlNamespace": "http://foo.com" }, "XmlNamespaceNested": { "type": "structure", "members": { "foo": { "shape": "String", "xmlNamespace": { "prefix": "baz", "uri": "http://baz.com" } }, "values": { "shape": "XmlNamespacedList", "xmlNamespace": "http://qux.com" } }, "xmlNamespace": "http://foo.com" }, "String": { "type": "string" }, "XmlNamespacedList": { "type": "list", "member": { "shape": "String", "xmlNamespace": "http://bux.com" } } }, "cases": [ { "id": "XmlNamespaces", "given": { "name": "XmlNamespaces", "http": { "method": "POST", "requestUri": "/XmlNamespaces", "responseCode": 200 }, "output": { "shape": "XmlNamespacesResponse" } }, "description": "Serializes XML namespaces", "result": { "nested": { "foo": "Foo", "values": [ "Bar", "Baz" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n Foo\n \n Bar\n Baz\n \n \n\n" } } ] }, { "description": "Test cases for XmlTimestamps operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlTimestampsResponse": { "type": "structure", "members": { "normal": { "shape": "Timestamp" }, "dateTime": { "shape": "SyntheticTimestamp_date_time" }, "dateTimeOnTarget": { "shape": "DateTime" }, "epochSeconds": { "shape": "SyntheticTimestamp_epoch_seconds" }, "epochSecondsOnTarget": { "shape": "EpochSeconds" }, "httpDate": { "shape": "SyntheticTimestamp_http_date" }, "httpDateOnTarget": { "shape": "HttpDate" } } }, "Timestamp": { "type": "timestamp" }, "SyntheticTimestamp_date_time": { "type": "timestamp", "timestampFormat": "iso8601" }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" }, "SyntheticTimestamp_epoch_seconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "EpochSeconds": { "type": "timestamp", "timestampFormat": "unixTimestamp" }, "SyntheticTimestamp_http_date": { "type": "timestamp", "timestampFormat": "rfc822" }, "HttpDate": { "type": "timestamp", "timestampFormat": "rfc822" } }, "cases": [ { "id": "XmlTimestamps", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Tests how normal timestamps are serialized", "result": { "normal": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 2014-04-29T18:30:38Z\n\n" } }, { "id": "XmlTimestampsWithDateTimeFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time works like normal timestamps", "result": { "dateTime": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 2014-04-29T18:30:38Z\n\n" } }, { "id": "XmlTimestampsWithDateTimeOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of date-time on the target shape works like normal timestamps", "result": { "dateTimeOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 2014-04-29T18:30:38Z\n\n" } }, { "id": "XmlTimestampsWithEpochSecondsFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds works", "result": { "epochSeconds": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 1398796238\n\n" } }, { "id": "XmlTimestampsWithEpochSecondsOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of epoch-seconds on the target shape works", "result": { "epochSecondsOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n 1398796238\n\n" } }, { "id": "XmlTimestampsWithHttpDateFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date works", "result": { "httpDate": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n Tue, 29 Apr 2014 18:30:38 GMT\n\n" } }, { "id": "XmlTimestampsWithHttpDateOnTargetFormat", "given": { "name": "XmlTimestamps", "http": { "method": "POST", "requestUri": "/XmlTimestamps", "responseCode": 200 }, "output": { "shape": "XmlTimestampsResponse" }, "documentation": "

This tests how timestamps are serialized, including using the default format of date-time and various @timestampFormat trait values.

" }, "description": "Ensures that the timestampFormat of http-date on the target shape works", "result": { "httpDateOnTarget": 1398796238 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n Tue, 29 Apr 2014 18:30:38 GMT\n\n" } } ] }, { "description": "Test cases for XmlUnions operation", "metadata": { "protocol": "rest-xml", "protocols": [ "rest-xml" ], "apiVersion": "2019-12-16" }, "shapes": { "XmlUnionsResponse": { "type": "structure", "members": { "unionValue": { "shape": "XmlUnionShape" } } }, "XmlUnionShape": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "booleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "shortValue": { "shape": "Integer" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double" }, "unionValue": { "shape": "XmlUnionShape" }, "structValue": { "shape": "XmlNestedUnionStruct" } }, "union": true }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Long": { "type": "long", "box": true }, "Float": { "type": "float", "box": true }, "Double": { "type": "double", "box": true }, "XmlNestedUnionStruct": { "type": "structure", "members": { "stringValue": { "shape": "String" }, "booleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "shortValue": { "shape": "Integer" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "floatValue": { "shape": "Float" }, "doubleValue": { "shape": "Double" } } } }, "cases": [ { "id": "XmlUnionsWithStructMember", "given": { "name": "XmlUnions", "http": { "method": "PUT", "requestUri": "/XmlUnions", "responseCode": 200 }, "output": { "shape": "XmlUnionsResponse" }, "idempotent": true }, "description": "Serializes union struct member", "result": { "unionValue": { "structValue": { "stringValue": "string", "booleanValue": true, "byteValue": 1, "shortValue": 2, "integerValue": 3, "longValue": 4, "floatValue": 5.5, "doubleValue": 6.5 } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n string\n true\n 1\n 2\n 3\n 4\n 5.5\n 6.5\n \n \n\n" } }, { "id": "XmlUnionsWithStringMember", "given": { "name": "XmlUnions", "http": { "method": "PUT", "requestUri": "/XmlUnions", "responseCode": 200 }, "output": { "shape": "XmlUnionsResponse" }, "idempotent": true }, "description": "Serializes union string member", "result": { "unionValue": { "stringValue": "some string" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n some string\n \n\n" } }, { "id": "XmlUnionsWithBooleanMember", "given": { "name": "XmlUnions", "http": { "method": "PUT", "requestUri": "/XmlUnions", "responseCode": 200 }, "output": { "shape": "XmlUnionsResponse" }, "idempotent": true }, "description": "Serializes union boolean member", "result": { "unionValue": { "booleanValue": true } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n true\n \n\n" } }, { "id": "XmlUnionsWithUnionMember", "given": { "name": "XmlUnions", "http": { "method": "PUT", "requestUri": "/XmlUnions", "responseCode": 200 }, "output": { "shape": "XmlUnionsResponse" }, "idempotent": true }, "description": "Serializes union member", "result": { "unionValue": { "unionValue": { "booleanValue": true } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/xml" }, "body": "\n \n \n true\n \n \n\n" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/output/smithy-rpc-v2-cbor.json000066400000000000000000001631631510465277300331670ustar00rootroot00000000000000[ { "description": "Test cases for EmptyInputOutput operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "EmptyStructure": { "type": "structure", "members": {} } }, "cases": [ { "id": "empty_output", "given": { "name": "EmptyInputOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "EmptyStructure" } }, "description": "When output structure is empty we write CBOR equivalent of {}", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v/8=" } }, { "id": "empty_output_no_body", "given": { "name": "EmptyInputOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "EmptyStructure" } }, "description": "When output structure is empty the client should accept an empty body", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "" } } ] }, { "description": "Test cases for Float16 operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "Float16Output": { "type": "structure", "members": { "value": { "shape": "Double" } } }, "Double": { "type": "double", "box": true } }, "cases": [ { "id": "RpcV2CborFloat16Inf", "given": { "name": "Float16", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "Float16Output" } }, "description": "Ensures that clients can correctly parse float16 +Inf.", "result": { "value": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWV2YWx1Zfl8AA==" } }, { "id": "RpcV2CborFloat16NegInf", "given": { "name": "Float16", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "Float16Output" } }, "description": "Ensures that clients can correctly parse float16 -Inf.", "result": { "value": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWV2YWx1Zfn8AA==" } }, { "id": "RpcV2CborFloat16LSBNaN", "given": { "name": "Float16", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "Float16Output" } }, "description": "Ensures that clients can correctly parse float16 NaN with high LSB.", "result": { "value": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWV2YWx1Zfl8AQ==" } }, { "id": "RpcV2CborFloat16MSBNaN", "given": { "name": "Float16", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "Float16Output" } }, "description": "Ensures that clients can correctly parse float16 NaN with high MSB.", "result": { "value": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWV2YWx1Zfl+AA==" } }, { "id": "RpcV2CborFloat16Subnormal", "given": { "name": "Float16", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "Float16Output" } }, "description": "Ensures that clients can correctly parse a subnormal float16.", "result": { "value": 4.76837158203125E-6 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWV2YWx1ZfkAUA==" } } ] }, { "description": "Test cases for FractionalSeconds operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "FractionalSecondsOutput": { "type": "structure", "members": { "datetime": { "shape": "DateTime" } } }, "DateTime": { "type": "timestamp", "timestampFormat": "iso8601" } }, "cases": [ { "id": "RpcV2CborDateTimeWithFractionalSeconds", "given": { "name": "FractionalSeconds", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "FractionalSecondsOutput" } }, "description": "Ensures that clients can correctly parse timestamps with fractional seconds", "result": { "datetime": 9.46845296123E8 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2hkYXRldGltZcH7Qcw32zgPvnf/" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "InvalidGreeting": { "type": "structure", "members": { "Message": { "shape": "String" } }, "documentation": "

This error is thrown when an invalid greeting value is provided.

", "exception": true }, "String": { "type": "string" } }, "cases": [ { "id": "RpcV2CborInvalidGreetingError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "InvalidGreeting" } ] }, "description": "Parses simple RpcV2 Cbor errors", "errorCode": "InvalidGreeting", "errorMessage": "Hi", "error": { "Message": "Hi" }, "response": { "status_code": 400, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2ZfX3R5cGV4LnNtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNJbnZhbGlkR3JlZXRpbmdnTWVzc2FnZWJIaf8=" } } ] }, { "description": "Test cases for GreetingWithErrors operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "ComplexError": { "type": "structure", "members": { "TopLevel": { "shape": "String" }, "Nested": { "shape": "ComplexNestedErrorData" } }, "documentation": "

This error is thrown when a request is invalid.

", "exception": true }, "String": { "type": "string" }, "ComplexNestedErrorData": { "type": "structure", "members": { "Foo": { "shape": "String" } } } }, "cases": [ { "id": "RpcV2CborComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "description": "Parses a complex error with no message member", "errorCode": "ComplexError", "error": { "TopLevel": "Top level", "Nested": { "Foo": "bar" } }, "response": { "status_code": 400, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3JoVG9wTGV2ZWxpVG9wIGxldmVsZk5lc3RlZL9jRm9vY2Jhcv//" } }, { "id": "RpcV2CborEmptyComplexError", "given": { "name": "GreetingWithErrors", "http": { "method": "POST", "requestUri": "/" }, "documentation": "

This operation has three possible return values:

  1. A successful response in the form of GreetingWithErrorsOutput
  2. An InvalidGreeting error.
  3. A ComplexError error.

Implementations must be able to successfully take a response and properly deserialize successful and error responses.

", "idempotent": true, "errors": [ { "shape": "ComplexError" } ] }, "errorCode": "ComplexError", "error": {}, "response": { "status_code": 400, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2ZfX3R5cGV4K3NtaXRoeS5wcm90b2NvbHRlc3RzLnJwY3YyQ2JvciNDb21wbGV4RXJyb3L/" } } ] }, { "description": "Test cases for NoInputOutput operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": {}, "cases": [ { "id": "no_output", "given": { "name": "NoInputOutput", "http": { "method": "POST", "requestUri": "/" } }, "description": "A `Content-Type` header should not be set if the response body is empty.", "result": {}, "response": { "status_code": 200, "headers": { "smithy-protocol": "rpc-v2-cbor" }, "body": "" } }, { "id": "NoOutputClientAllowsEmptyCbor", "given": { "name": "NoInputOutput", "http": { "method": "POST", "requestUri": "/" } }, "description": "Clients should accept a CBOR empty struct if there is no output.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v/8=" } }, { "id": "NoOutputClientAllowsEmptyBody", "given": { "name": "NoInputOutput", "http": { "method": "POST", "requestUri": "/" } }, "description": "Clients should accept an empty body if there is no output and\nshould not raise an error if the `Content-Type` header is set.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "" } } ] }, { "description": "Test cases for OptionalInputOutput operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "SimpleStructure": { "type": "structure", "members": { "value": { "shape": "String" } } }, "String": { "type": "string" } }, "cases": [ { "id": "optional_output", "given": { "name": "OptionalInputOutput", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleStructure" } }, "description": "When output is empty we write CBOR equivalent of {}", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v/8=" } } ] }, { "description": "Test cases for RecursiveShapes operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "RecursiveShapesInputOutput": { "type": "structure", "members": { "nested": { "shape": "RecursiveShapesInputOutputNested1" } } }, "RecursiveShapesInputOutputNested1": { "type": "structure", "members": { "foo": { "shape": "String" }, "nested": { "shape": "RecursiveShapesInputOutputNested2" } } }, "String": { "type": "string" }, "RecursiveShapesInputOutputNested2": { "type": "structure", "members": { "bar": { "shape": "String" }, "recursiveMember": { "shape": "RecursiveShapesInputOutputNested1" } } } }, "cases": [ { "id": "RpcV2CborRecursiveShapes", "given": { "name": "RecursiveShapes", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RecursiveShapesInputOutput" } }, "description": "Serializes recursive structures", "result": { "nested": { "foo": "Foo1", "nested": { "bar": "Bar1", "recursiveMember": { "foo": "Foo2", "nested": { "bar": "Bar2" } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2ZuZXN0ZWS/Y2Zvb2RGb28xZm5lc3RlZL9jYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyv2Nmb29kRm9vMmZuZXN0ZWS/Y2JhcmRCYXIy//////8=" } }, { "id": "RpcV2CborRecursiveShapesUsingDefiniteLength", "given": { "name": "RecursiveShapes", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RecursiveShapesInputOutput" } }, "description": "Deserializes recursive structures encoded using a map with definite length", "result": { "nested": { "foo": "Foo1", "nested": { "bar": "Bar1", "recursiveMember": { "foo": "Foo2", "nested": { "bar": "Bar2" } } } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWZuZXN0ZWSiY2Zvb2RGb28xZm5lc3RlZKJjYmFyZEJhcjFvcmVjdXJzaXZlTWVtYmVyomNmb29kRm9vMmZuZXN0ZWShY2JhcmRCYXIy" } } ] }, { "description": "Test cases for RpcV2CborDenseMaps operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "RpcV2CborDenseMapsInputOutput": { "type": "structure", "members": { "denseStructMap": { "shape": "DenseStructMap" }, "denseNumberMap": { "shape": "DenseNumberMap" }, "denseBooleanMap": { "shape": "DenseBooleanMap" }, "denseStringMap": { "shape": "DenseStringMap" }, "denseSetMap": { "shape": "DenseSetMap" } } }, "DenseStructMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "GreetingStruct" } }, "DenseNumberMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "Integer" } }, "DenseBooleanMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "Boolean" } }, "DenseStringMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "String" } }, "DenseSetMap": { "type": "map", "key": { "shape": "String" }, "value": { "shape": "StringSet" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "String": { "type": "string" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "GreetingStruct": { "type": "structure", "members": { "hi": { "shape": "String" } } } }, "cases": [ { "id": "RpcV2CborMaps", "given": { "name": "RpcV2CborDenseMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborDenseMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Deserializes maps", "result": { "denseStructMap": { "foo": { "hi": "there" }, "baz": { "hi": "bye" } } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oW5kZW5zZVN0cnVjdE1hcKJjZm9voWJoaWV0aGVyZWNiYXqhYmhpY2J5ZQ==" } }, { "id": "RpcV2CborDeserializesZeroValuesInMaps", "given": { "name": "RpcV2CborDenseMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborDenseMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Ensure that 0 and false are sent over the wire in all maps and lists", "result": { "denseNumberMap": { "x": 0 }, "denseBooleanMap": { "x": false } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "om5kZW5zZU51bWJlck1hcKFheABvZGVuc2VCb29sZWFuTWFwoWF49A==" } }, { "id": "RpcV2CborDeserializesDenseSetMap", "given": { "name": "RpcV2CborDenseMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborDenseMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "A response that contains a dense map of sets", "result": { "denseSetMap": { "x": [], "y": [ "a", "b" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWtkZW5zZVNldE1hcKJheIBheYJhYWFi" } }, { "id": "RpcV2CborDeserializesDenseSetMapAndSkipsNull", "given": { "name": "RpcV2CborDenseMaps", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborDenseMapsInputOutput" }, "documentation": "

The example tests basic map serialization.

" }, "description": "Clients SHOULD tolerate seeing a null value in a dense map, and they SHOULD\ndrop the null key-value pair.", "result": { "denseSetMap": { "x": [], "y": [ "a", "b" ] } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWtkZW5zZVNldE1hcKNheIBheYJhYWFiYXr2" } } ] }, { "description": "Test cases for RpcV2CborLists operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "RpcV2CborListInputOutput": { "type": "structure", "members": { "stringList": { "shape": "StringList" }, "stringSet": { "shape": "StringSet" }, "integerList": { "shape": "IntegerList" }, "booleanList": { "shape": "BooleanList" }, "timestampList": { "shape": "TimestampList" }, "enumList": { "shape": "FooEnumList" }, "intEnumList": { "shape": "IntegerEnumList" }, "nestedStringList": { "shape": "NestedStringList" }, "structureList": { "shape": "StructureList" }, "blobList": { "shape": "BlobList" } } }, "StringList": { "type": "list", "member": { "shape": "String" } }, "StringSet": { "type": "list", "member": { "shape": "String" } }, "IntegerList": { "type": "list", "member": { "shape": "Integer" } }, "BooleanList": { "type": "list", "member": { "shape": "Boolean" } }, "TimestampList": { "type": "list", "member": { "shape": "Timestamp" } }, "FooEnumList": { "type": "list", "member": { "shape": "FooEnum" } }, "IntegerEnumList": { "type": "list", "member": { "shape": "IntegerEnum" } }, "NestedStringList": { "type": "list", "member": { "shape": "StringList" }, "documentation": "

A list of lists of strings.

" }, "StructureList": { "type": "list", "member": { "shape": "StructureListMember" } }, "BlobList": { "type": "list", "member": { "shape": "Blob" } }, "Blob": { "type": "blob" }, "StructureListMember": { "type": "structure", "members": { "a": { "shape": "String" }, "b": { "shape": "String" } } }, "String": { "type": "string" }, "IntegerEnum": { "type": "integer", "box": true }, "FooEnum": { "type": "string", "enum": [ "Foo", "Baz", "Bar", "1", "0" ] }, "Timestamp": { "type": "timestamp" }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true } }, "cases": [ { "id": "RpcV2CborLists", "given": { "name": "RpcV2CborLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborListInputOutput" }, "documentation": "

This test case serializes JSON lists for the following cases for both input and output:

  1. Normal lists.
  2. Normal sets.
  3. Lists of lists.
  4. Lists of structures.
", "idempotent": true }, "description": "Serializes RpcV2 Cbor lists", "result": { "stringList": [ "foo", "bar" ], "stringSet": [ "foo", "bar" ], "integerList": [ 1, 2 ], "booleanList": [ true, false ], "timestampList": [ 1398796238, 1398796238 ], "enumList": [ "Foo", "0" ], "intEnumList": [ 1, 2 ], "nestedStringList": [ [ "foo", "bar" ], [ "baz", "qux" ] ], "structureList": [ { "a": "1", "b": "2" }, { "a": "3", "b": "4" } ], "blobList": [ "foo", "bar" ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2pzdHJpbmdMaXN0n2Nmb29jYmFy/2lzdHJpbmdTZXSfY2Zvb2NiYXL/a2ludGVnZXJMaXN0nwEC/2tib29sZWFuTGlzdJ/19P9tdGltZXN0YW1wTGlzdJ/B+0HU1/vzgAAAwftB1Nf784AAAP9oZW51bUxpc3SfY0Zvb2Ew/2tpbnRFbnVtTGlzdJ8BAv9wbmVzdGVkU3RyaW5nTGlzdJ+fY2Zvb2NiYXL/n2NiYXpjcXV4//9tc3RydWN0dXJlTGlzdJ+/YWFhMWFiYTL/v2FhYTNhYmE0//9oYmxvYkxpc3SfQ2Zvb0NiYXL//w==" } }, { "id": "RpcV2CborListsEmpty", "given": { "name": "RpcV2CborLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborListInputOutput" }, "documentation": "

This test case serializes JSON lists for the following cases for both input and output:

  1. Normal lists.
  2. Normal sets.
  3. Lists of lists.
  4. Lists of structures.
", "idempotent": true }, "description": "Serializes empty RpcV2 Cbor lists", "result": { "stringList": [] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2pzdHJpbmdMaXN0n///" } }, { "id": "RpcV2CborIndefiniteStringInsideIndefiniteListCanDeserialize", "given": { "name": "RpcV2CborLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborListInputOutput" }, "documentation": "

This test case serializes JSON lists for the following cases for both input and output:

  1. Normal lists.
  2. Normal sets.
  3. Lists of lists.
  4. Lists of structures.
", "idempotent": true }, "description": "Can deserialize indefinite length text strings inside an indefinite length list", "result": { "stringList": [ "An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string" ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2pzdHJpbmdMaXN0n394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n//8=" } }, { "id": "RpcV2CborIndefiniteStringInsideDefiniteListCanDeserialize", "given": { "name": "RpcV2CborLists", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "RpcV2CborListInputOutput" }, "documentation": "

This test case serializes JSON lists for the following cases for both input and output:

  1. Normal lists.
  2. Normal sets.
  3. Lists of lists.
  4. Lists of structures.
", "idempotent": true }, "description": "Can deserialize indefinite length text strings inside a definite length list", "result": { "stringList": [ "An example indefinite string, which will be chunked, on each comma", "Another example indefinite string with only one chunk", "This is a plain string" ] }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWpzdHJpbmdMaXN0g394HUFuIGV4YW1wbGUgaW5kZWZpbml0ZSBzdHJpbmcsdyB3aGljaCB3aWxsIGJlIGNodW5rZWQsbiBvbiBlYWNoIGNvbW1h/394NUFub3RoZXIgZXhhbXBsZSBpbmRlZmluaXRlIHN0cmluZyB3aXRoIG9ubHkgb25lIGNodW5r/3ZUaGlzIGlzIGEgcGxhaW4gc3RyaW5n" } } ] }, { "description": "Test cases for SimpleScalarProperties operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "SimpleScalarStructure": { "type": "structure", "members": { "trueBooleanValue": { "shape": "Boolean" }, "falseBooleanValue": { "shape": "Boolean" }, "byteValue": { "shape": "Integer" }, "doubleValue": { "shape": "Double" }, "floatValue": { "shape": "Float" }, "integerValue": { "shape": "Integer" }, "longValue": { "shape": "Long" }, "shortValue": { "shape": "Integer" }, "stringValue": { "shape": "String" }, "blobValue": { "shape": "Blob" } } }, "Boolean": { "type": "boolean", "box": true }, "Integer": { "type": "integer", "box": true }, "Double": { "type": "double", "box": true }, "Float": { "type": "float", "box": true }, "Long": { "type": "long", "box": true }, "String": { "type": "string" }, "Blob": { "type": "blob" } }, "cases": [ { "id": "RpcV2CborSimpleScalarProperties", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "Serializes simple scalar properties", "result": { "trueBooleanValue": true, "falseBooleanValue": false, "byteValue": 5, "doubleValue": 1.889, "floatValue": 7.625, "integerValue": 256, "shortValue": 9898, "stringValue": "simple", "blobValue": "foo" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v3B0cnVlQm9vbGVhblZhbHVl9XFmYWxzZUJvb2xlYW5WYWx1ZfRpYnl0ZVZhbHVlBWtkb3VibGVWYWx1Zfs//jlYEGJN02pmbG9hdFZhbHVl+kD0AABsaW50ZWdlclZhbHVlGQEAanNob3J0VmFsdWUZJqprc3RyaW5nVmFsdWVmc2ltcGxlaWJsb2JWYWx1ZUNmb2//" } }, { "id": "RpcV2CborSimpleScalarPropertiesUsingDefiniteLength", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "Deserializes simple scalar properties encoded using a map with definite length", "result": { "trueBooleanValue": true, "falseBooleanValue": false, "byteValue": 5, "doubleValue": 1.889, "floatValue": 7.625, "integerValue": 256, "shortValue": 9898, "stringValue": "simple", "blobValue": "foo" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "qXB0cnVlQm9vbGVhblZhbHVl9XFmYWxzZUJvb2xlYW5WYWx1ZfRpYnl0ZVZhbHVlBWtkb3VibGVWYWx1Zfs//jlYEGJN02pmbG9hdFZhbHVl+kD0AABsaW50ZWdlclZhbHVlGQEAanNob3J0VmFsdWUZJqprc3RyaW5nVmFsdWVmc2ltcGxlaWJsb2JWYWx1ZUNmb28=" } }, { "id": "RpcV2CborClientDoesntDeserializeNullStructureValues", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "RpcV2 Cbor should not deserialize null structure values", "result": {}, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2tzdHJpbmdWYWx1Zfb/" } }, { "id": "RpcV2CborSupportsNaNFloatOutputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "Supports handling NaN float values.", "result": { "doubleValue": "NaN", "floatValue": "NaN" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2tkb3VibGVWYWx1Zft/+AAAAAAAAGpmbG9hdFZhbHVl+n/AAAD/" } }, { "id": "RpcV2CborSupportsInfinityFloatOutputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "Supports handling Infinity float values.", "result": { "doubleValue": "Infinity", "floatValue": "Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2tkb3VibGVWYWx1Zft/8AAAAAAAAGpmbG9hdFZhbHVl+n+AAAD/" } }, { "id": "RpcV2CborSupportsNegativeInfinityFloatOutputs", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "Supports handling Negative Infinity float values.", "result": { "doubleValue": "-Infinity", "floatValue": "-Infinity" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/" } }, { "id": "RpcV2CborSupportsUpcastingDataOnDeserialize", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "Supports upcasting from a smaller byte representation of the same data type.", "result": { "doubleValue": 1.5, "floatValue": 7.625, "integerValue": 56, "longValue": 256, "shortValue": 10 }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2tkb3VibGVWYWx1Zfk+AGpmbG9hdFZhbHVl+UegbGludGVnZXJWYWx1ZRg4aWxvbmdWYWx1ZRkBAGpzaG9ydFZhbHVlCv8=" } }, { "id": "RpcV2CborExtraFieldsInTheBodyShouldBeSkippedByClients", "given": { "name": "SimpleScalarProperties", "http": { "method": "POST", "requestUri": "/" }, "output": { "shape": "SimpleScalarStructure" } }, "description": "The client should skip over additional fields that are not part of the structure. This allows a\nclient generated against an older Smithy model to be able to communicate with a server that is\ngenerated against a newer Smithy model.", "result": { "byteValue": 5, "doubleValue": 1.889, "falseBooleanValue": false, "floatValue": 7.625, "integerValue": 256, "longValue": 9873, "shortValue": 9898, "stringValue": "simple", "trueBooleanValue": true, "blobValue": "foo" }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "v2lieXRlVmFsdWUFa2RvdWJsZVZhbHVl+z/+OVgQYk3TcWZhbHNlQm9vbGVhblZhbHVl9GpmbG9hdFZhbHVl+kD0AABrZXh0cmFPYmplY3S/c2luZGVmaW5pdGVMZW5ndGhNYXC/a3dpdGhBbkFycmF5nwECA///cWRlZmluaXRlTGVuZ3RoTWFwo3J3aXRoQURlZmluaXRlQXJyYXmDAQIDeB1hbmRTb21lSW5kZWZpbml0ZUxlbmd0aFN0cmluZ3gfdGhhdCBoYXMsIGJlZW4gY2h1bmtlZCBvbiBjb21tYWxub3JtYWxTdHJpbmdjZm9vanNob3J0VmFsdWUZJw9uc29tZU90aGVyRmllbGR2dGhpcyBzaG91bGQgYmUgc2tpcHBlZP9saW50ZWdlclZhbHVlGQEAaWxvbmdWYWx1ZRkmkWpzaG9ydFZhbHVlGSaqa3N0cmluZ1ZhbHVlZnNpbXBsZXB0cnVlQm9vbGVhblZhbHVl9WlibG9iVmFsdWVDZm9v/w==" } } ] }, { "description": "Test cases for HttpPayloadWithUnion operation", "metadata": { "protocol": "smithy-rpc-v2-cbor", "protocols": [ "smithy-rpc-v2-cbor" ], "apiVersion": "2020-07-14", "targetPrefix": "RpcV2Protocol" }, "shapes": { "HttpPayloadWithUnionInputOutput": { "type": "structure", "members": { "nested": { "shape": "UnionPayload" } }, "payload": "nested" }, "UnionPayload": { "type": "structure", "members": { "greeting": { "shape": "String" } }, "union": true }, "String": { "type": "string" } }, "cases": [ { "id": "RpcV2CborPayloadWithUnion", "given": { "name": "HttpPayloadWithUnion", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithUnion", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithUnionInputOutput" }, "documentation": "

This example serializes a union in the payload.

", "idempotent": true }, "description": "Serializes a union in the payload.", "result": { "nested": { "greeting": "hello" } }, "response": { "status_code": 200, "headers": { "Content-Type": "application/cbor", "smithy-protocol": "rpc-v2-cbor" }, "body": "oWZuZXN0ZWShaGdyZWV0aW5nZWhlbGxv" } }, { "id": "RpcV2CborPayloadWithUnsetUnion", "given": { "name": "HttpPayloadWithUnion", "http": { "method": "PUT", "requestUri": "/HttpPayloadWithUnion", "responseCode": 200 }, "output": { "shape": "HttpPayloadWithUnionInputOutput" }, "documentation": "

This example serializes a union in the payload.

", "idempotent": true }, "description": "No payload is sent if the union has no value.", "result": {}, "response": { "status_code": 200, "headers": { "Content-Length": "0" }, "body": "v2tkb3VibGVWYWx1Zfv/8AAAAAAAAGpmbG9hdFZhbHVl+v+AAAD/" } } ] } ] aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/protocols/protocol-tests-ignore-list.json000066400000000000000000000067621510465277300335140ustar00rootroot00000000000000{ "general": { "input": { "suites": [ "Test cases for PutWithContentEncoding operation", "Test cases for QueryIdempotencyTokenAutoFill operation", "Test cases for HostWithPathOperation operation", "Idempotency token auto fill" ] } }, "protocols": { "query" : { "input": { "cases": [ "QueryFlattenedListArgWithXmlName" ] }, "output": { "cases": [ "QueryXmlLists", "QueryComplexError" ] } }, "ec2" : { "input" : { "cases": [ "Ec2QueryEndpointTraitWithHostLabel", "Ec2Lists", "Ec2TimestampsInput" ] }, "output": { "cases": [ "Ec2XmlLists", "Ec2ComplexError" ] } }, "json" : { "input": { "cases": [ "AwsJson11StructuresDontSerializeNullValues" ] }, "output" : { "cases": [ "AwsJson11FooErrorUsingXAmznErrorType", "AwsJson11FooErrorUsingXAmznErrorTypeWithUri", "AwsJson11FooErrorUsingXAmznErrorTypeWithUriAndNamespace", "AwsJson11FooErrorUsingCode", "AwsJson11FooErrorUsingCodeAndNamespace", "AwsJson11FooErrorUsingCodeUriAndNamespace" ] } }, "json_1_0" : { "output" : { "cases": [ "AwsJson10FooErrorUsingXAmznErrorType", "AwsJson10FooErrorUsingXAmznErrorTypeWithUri", "AwsJson10FooErrorUsingXAmznErrorTypeWithUriAndNamespace", "AwsJson10FooErrorUsingCode", "AwsJson10FooErrorUsingCodeAndNamespace", "AwsJson10FooErrorUsingCodeUriAndNamespace" ] } }, "rest-json" : { "input" : { "cases": [ "MediaTypeHeaderInputBase64", "RestJsonHttpChecksumRequired", "RestJsonNullAndEmptyHeaders", "RestJsonQueryPrecedence", "RestJsonHttpPayloadWithUnsetUnion", "DocumentTypeAsPayloadInputString", "DocumentTypeAsPayloadInput" ] }, "output" : { "cases": [ "RestJsonFooErrorUsingXAmznErrorType", "RestJsonFooErrorUsingXAmznErrorTypeWithUri", "RestJsonFooErrorUsingXAmznErrorTypeWithUriAndNamespace", "RestJsonHttpPayloadTraitsWithNoBlobBody", "RestJsonHttpPayloadWithUnsetUnion", "RestJsonInputAndOutputWithTimestampHeaders", "RestJsonInputAndOutputWithQuotedStringHeaders", "RestJsonDeserializesDenseSetMapAndSkipsNull", "MediaTypeHeaderOutputBase64" ] } }, "rest-xml": { "input": { "cases": [ "BodyWithXmlName", "RestXmlHttpPayloadWithUnion", "HttpPayloadWithXmlName", "HttpPayloadWithXmlNamespace", "HttpPayloadWithXmlNamespaceAndPrefix", "XmlAttributesOnPayload", "NullAndEmptyHeaders", "RestXmlQueryPrecedence" ] }, "output": { "cases": [ "InputAndOutputWithTimestampHeaders", "RestXmlHttpPayloadWithUnsetUnion", "HttpPayloadTraitsWithNoBlobBody", "ComplexError", "BodyWithXmlName", "XmlLists", "IgnoreQueryParamsInResponse" ] } }, "smithy-rpc-v2-cbor": { "output": { "cases": [ "RpcV2CborExtraFieldsInTheBodyShouldBeSkippedByClients", "RpcV2CborDeserializesDenseSetMapAndSkipsNull" ] } } } } aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_credentials.py000066400000000000000000002036311510465277300272420ustar00rootroot00000000000000""" These tests have been taken from https://github.com/boto/botocore/blob/develop/tests/unit/test_credentials.py and adapted to work with asyncio and pytest """ import asyncio import json import logging import os import subprocess import sys import tempfile import uuid from contextlib import asynccontextmanager from datetime import datetime, timedelta from functools import partial from typing import Optional from unittest import TestCase, mock import botocore.exceptions import pytest import wrapt from botocore.configprovider import ConfigValueStore from botocore.credentials import ( Credentials, JSONFileCache, ReadOnlyCredentials, ) from botocore.stub import Stubber from botocore.utils import ( FileWebIdentityTokenLoader, SSOTokenLoader, datetime2timestamp, ) from dateutil.tz import tzlocal, tzutc from aiobotocore import credentials from aiobotocore.credentials import ( AioAssumeRoleProvider, AioCanonicalNameCredentialSourcer, AioContainerProvider, AioEnvProvider, AioInstanceMetadataProvider, AioProfileProviderBuilder, AioSSOCredentialFetcher, AioSSOProvider, ) from aiobotocore.session import AioSession from tests.botocore_tests import random_chars from tests.botocore_tests.helpers import StubbedSession # From class TestCredentials(BaseEnvVar): @pytest.mark.parametrize( "access,secret", [('foo\xe2\x80\x99', 'bar\xe2\x80\x99'), ('foo', 'bar')] ) def test_credentials_normalization(access, secret): c = credentials.AioCredentials(access, secret) assert isinstance(c.access_key, str) assert isinstance(c.secret_key, str) # From class TestAssumeRoleCredentialFetcher(BaseEnvVar): def assume_role_client_creator(with_response): class _Client: def __init__(self, resp): self._resp = resp self._called = [] self._call_count = 0 async def assume_role(self, *args, **kwargs): self._call_count += 1 self._called.append((args, kwargs)) if isinstance(self._resp, list): return self._resp.pop(0) return self._resp async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): pass return mock.Mock(return_value=_Client(with_response)) def some_future_time(): timeobj = datetime.now(tzlocal()) return timeobj + timedelta(hours=24) def get_expected_creds_from_response(response): expiration = response['Credentials']['Expiration'] if isinstance(expiration, datetime): expiration = expiration.isoformat() return { 'access_key': response['Credentials']['AccessKeyId'], 'secret_key': response['Credentials']['SecretAccessKey'], 'token': response['Credentials']['SessionToken'], 'expiry_time': expiration, 'account_id': response.get('Credentials', {}).get('AccountId'), } # From class CredentialResolverTest(BaseEnvVar): @pytest.fixture def credential_provider(): def _f(method, canonical_name, creds='None'): # 'None' so that we can differentiate from None provider = mock.Mock() provider.METHOD = method provider.CANONICAL_NAME = canonical_name async def load(): if creds != 'None': return creds return mock.Mock() provider.load = load return provider return _f async def test_assumerolefetcher_no_cache(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } refresher = credentials.AioAssumeRoleCredentialFetcher( assume_role_client_creator(response), credentials.AioCredentials('a', 'b', 'c'), 'myrole', ) expected_response = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected_response async def test_assumerolefetcher_cache_key_with_role_session_name(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } cache = {} client_creator = assume_role_client_creator(response) role_session_name = 'my_session_name' refresher = credentials.AioAssumeRoleCredentialFetcher( client_creator, credentials.AioCredentials('a', 'b', 'c'), 'myrole', cache=cache, extra_args={'RoleSessionName': role_session_name}, ) await refresher.fetch_credentials() # This is the sha256 hex digest of the expected assume role args. cache_key = '2964201f5648c8be5b9460a9cf842d73a266daf2' assert cache_key in cache assert cache[cache_key] == response async def test_assumerolefetcher_cache_in_cache_but_expired(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } client_creator = assume_role_client_creator(response) cache = { 'development--myrole': { 'Credentials': { 'AccessKeyId': 'foo-cached', 'SecretAccessKey': 'bar-cached', 'SessionToken': 'baz-cached', 'Expiration': datetime.now(tzlocal()), } } } refresher = credentials.AioAssumeRoleCredentialFetcher( client_creator, credentials.AioCredentials('a', 'b', 'c'), 'myrole', cache=cache, ) expected = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected async def test_assumerolefetcher_mfa(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } client_creator = assume_role_client_creator(response) prompter = mock.Mock(return_value='token-code') mfa_serial = 'mfa' refresher = credentials.AioAssumeRoleCredentialFetcher( client_creator, credentials.AioCredentials('a', 'b', 'c'), 'myrole', extra_args={'SerialNumber': mfa_serial}, mfa_prompter=prompter, ) await refresher.fetch_credentials() # Slighly different to the botocore mock client = client_creator.return_value assert client._call_count == 1 call_kwargs = client._called[0][1] assert call_kwargs['SerialNumber'] == 'mfa' assert call_kwargs['RoleArn'] == 'myrole' assert call_kwargs['TokenCode'] == 'token-code' async def test_assumerolefetcher_account_id_with_valid_arn(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), 'AccountId': '123456789012', }, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'arn:aws:iam::123456789012:role/RoleA', }, } client_creator = assume_role_client_creator(with_response=response) refresher = credentials.AioAssumeRoleCredentialFetcher( client_creator, credentials.AioCredentials('a', 'b', 'c'), 'myrole', ) expected_response = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected_response assert response['account_id'] == '123456789012' async def test_assumerolefetcher_account_id_with_invalid_arn(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'invalid-arn', }, } client_creator = assume_role_client_creator(with_response=response) refresher = credentials.AioAssumeRoleCredentialFetcher( client_creator, credentials.AioCredentials('a', 'b', 'c'), 'myrole', ) expected_response = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected_response assert response['account_id'] is None @mock.patch('aiobotocore.credentials.register_feature_ids') async def test_assumerolefetcher_feature_ids_registered_during_get_credentials( mock_register, ): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time(), } } client_creator = assume_role_client_creator(with_response=response) fetcher = credentials.AioAssumeRoleCredentialFetcher( client_creator, credentials.AioCredentials('a', 'b', 'c'), 'myrole', ) test_feature_ids = {'test_feature_1', 'test_feature_2'} fetcher.feature_ids = test_feature_ids await fetcher.fetch_credentials() # Verify register_credential_feature_ids was called with test feature IDs mock_register.assert_called_once_with(test_feature_ids) async def test_recursive_assume_role(assume_role_setup): self = assume_role_setup config = ( '[profile A]\n' 'role_arn = arn:aws:iam::123456789:role/RoleA\n' 'source_profile = B\n\n' '[profile B]\n' 'role_arn = arn:aws:iam::123456789:role/RoleB\n' 'source_profile = C\n\n' '[profile C]\n' 'aws_access_key_id = abc123\n' 'aws_secret_access_key = def456\n' ) self.write_config(config) profile_b_creds = self.create_random_credentials() profile_b_response = self.create_assume_role_response(profile_b_creds) profile_a_creds = self.create_random_credentials() profile_a_response = self.create_assume_role_response(profile_a_creds) async with self.create_session(profile='A') as (session, stubber): stubber.add_response('assume_role', profile_b_response) stubber.add_response('assume_role', profile_a_response) actual_creds = await session.get_credentials() await self.assert_creds_equal(actual_creds, profile_a_creds) stubber.assert_no_pending_responses() # From class TestAssumeRole(BaseAssumeRoleTest): async def test_assume_role_resolves_account_id(assume_role_setup): self = assume_role_setup config = ( '[profile A]\n' 'role_arn = arn:aws:iam::1234567890:role/RoleA\n' 'source_profile = B\n\n' '[profile B]\n' 'aws_access_key_id = foo\n' 'aws_secret_access_key = bar\n' ) self.write_config(config) expected_creds = self.create_random_credentials() response = self.create_assume_role_response(expected_creds) async with self.create_session(profile='A') as (session, stubber): stubber.add_response('assume_role', response) actual_creds = await session.get_credentials() await self.assert_creds_equal(actual_creds, expected_creds) # So calling .account_id would cause deferred credentials to be loaded, # according to the source, you're supposed to call get_frozen_credentials # so will do that. actual_creds = await actual_creds.get_frozen_credentials() assert actual_creds.account_id == '1234567890' # From class TestAssumeRoleWithWebIdentityCredentialFetcher(BaseEnvVar): def assume_role_web_identity_client_creator(with_response): class _Client: def __init__(self, resp): self._resp = resp self._called = [] self._call_count = 0 async def assume_role_with_web_identity(self, *args, **kwargs): self._call_count += 1 self._called.append((args, kwargs)) if isinstance(self._resp, list): return self._resp.pop(0) return self._resp async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): pass return mock.Mock(return_value=_Client(with_response)) async def test_webidentfetcher_no_cache(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } refresher = credentials.AioAssumeRoleWithWebIdentityCredentialFetcher( assume_role_web_identity_client_creator(response), lambda: 'totally.a.token', 'myrole', ) expected_response = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected_response async def test_webidentfetcher_account_id_with_valid_arn(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), 'AccountId': '123456789012', }, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'arn:aws:iam::123456789012:role/RoleA', }, } client_creator = assume_role_web_identity_client_creator( with_response=response ) refresher = credentials.AioAssumeRoleWithWebIdentityCredentialFetcher( client_creator, lambda: 'totally.a.token', 'myrole', ) expected_response = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected_response assert response['account_id'] == '123456789012' async def test_webidentfetcher_account_id_with_invalid_arn(): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'invalid-arn', }, } client_creator = assume_role_web_identity_client_creator( with_response=response ) refresher = credentials.AioAssumeRoleWithWebIdentityCredentialFetcher( client_creator, lambda: 'totally.a.token', 'myrole', ) expected_response = get_expected_creds_from_response(response) response = await refresher.fetch_credentials() assert response == expected_response assert response['account_id'] is None @mock.patch('aiobotocore.credentials.register_feature_ids') async def test_webidentfetcher_feature_ids_registered_during_get_credentials( mock_register, ): response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time(), } } client_creator = assume_role_web_identity_client_creator( with_response=response ) fetcher = credentials.AioAssumeRoleWithWebIdentityCredentialFetcher( client_creator, lambda: 'totally.a.token', 'myrole', ) test_feature_ids = {'test_feature_1', 'test_feature_2'} fetcher.feature_ids = test_feature_ids await fetcher.fetch_credentials() # Verify register_credential_feature_ids was called with test feature IDs mock_register.assert_called_once_with(test_feature_ids) async def test_credresolver_load_credentials_single_provider( credential_provider, ): provider1 = credential_provider( 'provider1', 'CustomProvider1', credentials.AioCredentials('a', 'b', 'c'), ) resolver = credentials.AioCredentialResolver(providers=[provider1]) creds = await resolver.load_credentials() assert creds.access_key == 'a' assert creds.secret_key == 'b' assert creds.token == 'c' async def test_credresolver_no_providers(credential_provider): provider1 = credential_provider('provider1', 'CustomProvider1', None) resolver = credentials.AioCredentialResolver(providers=[provider1]) creds = await resolver.load_credentials() assert creds is None # From class TestCanonicalNameSourceProvider(BaseEnvVar): async def test_canonicalsourceprovider_source_creds(credential_provider): creds = credentials.AioCredentials('a', 'b', 'c') provider1 = credential_provider('provider1', 'CustomProvider1', creds) provider2 = credential_provider('provider2', 'CustomProvider2') provider = credentials.AioCanonicalNameCredentialSourcer( providers=[provider1, provider2] ) result = await provider.source_credentials('CustomProvider1') assert result is creds async def test_canonicalsourceprovider_source_creds_case_insensitive( credential_provider, ): creds = credentials.AioCredentials('a', 'b', 'c') provider1 = credential_provider('provider1', 'CustomProvider1', creds) provider2 = credential_provider('provider2', 'CustomProvider2') provider = credentials.AioCanonicalNameCredentialSourcer( providers=[provider1, provider2] ) result = await provider.source_credentials('cUsToMpRoViDeR1') assert result is creds # From class TestAssumeRoleCredentialProvider(unittest.TestCase): @pytest.fixture def assumerolecredprovider_config_loader(): fake_config = { 'profiles': { 'development': { 'role_arn': 'myrole', 'source_profile': 'longterm', }, 'longterm': { 'aws_access_key_id': 'akid', 'aws_secret_access_key': 'skid', }, 'non-static': { 'role_arn': 'myrole', 'credential_source': 'Environment', }, 'chained': { 'role_arn': 'chained-role', 'source_profile': 'development', }, } } def _f(config=None): return lambda: (config or fake_config) return _f async def test_assumerolecredprovider_assume_role_no_cache( credential_provider, assumerolecredprovider_config_loader ): creds = credentials.AioCredentials('a', 'b', 'c') provider1 = credential_provider('provider1', 'CustomProvider1', creds) provider2 = credential_provider('provider2', 'CustomProvider2') provider = credentials.AioCanonicalNameCredentialSourcer( providers=[provider1, provider2] ) result = await provider.source_credentials('cUsToMpRoViDeR1') assert result is creds response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } client_creator = assume_role_client_creator(response) provider = credentials.AioAssumeRoleProvider( assumerolecredprovider_config_loader(), client_creator, cache={}, profile_name='development', ) creds = await provider.load() # So calling .access_key would cause deferred credentials to be loaded, # according to the source, you're supposed to call get_frozen_credentials # so will do that. creds = await creds.get_frozen_credentials() assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' # MFA async def test_assumerolecredprovider_mfa( credential_provider, assumerolecredprovider_config_loader ): fake_config = { 'profiles': { 'development': { 'role_arn': 'myrole', 'source_profile': 'longterm', 'mfa_serial': 'mfa', }, 'longterm': { 'aws_access_key_id': 'akid', 'aws_secret_access_key': 'skid', }, 'non-static': { 'role_arn': 'myrole', 'credential_source': 'Environment', }, 'chained': { 'role_arn': 'chained-role', 'source_profile': 'development', }, } } response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': some_future_time().isoformat(), }, } client_creator = assume_role_client_creator(response) prompter = mock.Mock(return_value='token-code') provider = credentials.AioAssumeRoleProvider( assumerolecredprovider_config_loader(fake_config), client_creator, cache={}, profile_name='development', prompter=prompter, ) creds = await provider.load() # So calling .access_key would cause deferred credentials to be loaded, # according to the source, you're supposed to call get_frozen_credentials # so will do that. await creds.get_frozen_credentials() client = client_creator.return_value assert client._call_count == 1 call_kwargs = client._called[0][1] assert call_kwargs['SerialNumber'] == 'mfa' assert call_kwargs['RoleArn'] == 'myrole' assert call_kwargs['TokenCode'] == 'token-code' async def test_assumerolecredprovider_mfa_cannot_refresh_credentials( credential_provider, assumerolecredprovider_config_loader ): fake_config = { 'profiles': { 'development': { 'role_arn': 'myrole', 'source_profile': 'longterm', 'mfa_serial': 'mfa', }, 'longterm': { 'aws_access_key_id': 'akid', 'aws_secret_access_key': 'skid', }, 'non-static': { 'role_arn': 'myrole', 'credential_source': 'Environment', }, 'chained': { 'role_arn': 'chained-role', 'source_profile': 'development', }, } } expiration_time = some_future_time() response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': expiration_time.isoformat(), }, } client_creator = assume_role_client_creator(response) prompter = mock.Mock(return_value='token-code') provider = credentials.AioAssumeRoleProvider( assumerolecredprovider_config_loader(fake_config), client_creator, cache={}, profile_name='development', prompter=prompter, ) local_now = mock.Mock(return_value=datetime.now(tzlocal())) with mock.patch('aiobotocore.credentials._local_now', local_now): creds = await provider.load() await creds.get_frozen_credentials() local_now.return_value = expiration_time with pytest.raises(credentials.RefreshWithMFAUnsupportedError): await creds.get_frozen_credentials() # From class TestAssumeRoleWithWebIdentityCredentialProvider async def test_assumerolewebidentprovider_no_cache(): future = datetime.now(tzlocal()) + timedelta(hours=24) response = { 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': future.isoformat(), }, } # client client_creator = assume_role_web_identity_client_creator(response) mock_loader = mock.Mock(spec=FileWebIdentityTokenLoader) mock_loader.return_value = 'totally.a.token' mock_loader_cls = mock.Mock(return_value=mock_loader) config = { 'profiles': { 'some-profile': { 'role_arn': 'arn:aws:iam::123:role/role-name', 'web_identity_token_file': '/some/path/token.jwt', } } } provider = credentials.AioAssumeRoleWithWebIdentityProvider( load_config=lambda: config, client_creator=client_creator, cache={}, profile_name='some-profile', token_loader_cls=mock_loader_cls, ) creds = await provider.load() creds = await creds.get_frozen_credentials() assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' mock_loader_cls.assert_called_with('/some/path/token.jwt') # From class TestContainerProvider(BaseEnvVar): def full_url(url): return f'http://{credentials.AioContainerMetadataFetcher.IP_ADDRESS}{url}' async def test_containerprovider_can_retrieve_account_id(): environ = { 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI': '/latest/credentials?id=foo' } fetcher = mock.AsyncMock() fetcher.full_url = full_url timeobj = datetime.now(tzlocal()) timestamp = (timeobj + timedelta(hours=24)).isoformat() fetcher.retrieve_full_uri.return_value = { "AccessKeyId": "access_key", "SecretAccessKey": "secret_key", "Token": "token", "Expiration": timestamp, "AccountId": "account_id", } provider = credentials.AioContainerProvider(environ, fetcher) creds = await provider.load() fetcher.retrieve_full_uri.assert_called_with( full_url('/latest/credentials?id=foo'), headers=None ) assert creds.method == 'container-role' creds = await creds.get_frozen_credentials() assert creds.access_key == 'access_key' assert creds.secret_key == 'secret_key' assert creds.token == 'token' assert creds.account_id == 'account_id' # From class TestEnvVar(BaseEnvVar): async def test_envvarprovider_env_var_present(): environ = { 'AWS_ACCESS_KEY_ID': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar', } provider = credentials.AioEnvProvider(environ) creds = await provider.load() assert isinstance(creds, credentials.AioCredentials) assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.method == 'env' async def test_envvarprovider_env_var_absent(): environ = {} provider = credentials.AioEnvProvider(environ) creds = await provider.load() assert creds is None async def test_envvarprovider_env_var_expiry(): expiry_time = datetime.now(tzlocal()) - timedelta(hours=1) environ = { 'AWS_ACCESS_KEY_ID': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar', 'AWS_CREDENTIAL_EXPIRATION': expiry_time.isoformat(), } provider = credentials.AioEnvProvider(environ) creds = await provider.load() assert isinstance(creds, credentials.AioRefreshableCredentials) del environ['AWS_CREDENTIAL_EXPIRATION'] with pytest.raises(botocore.exceptions.PartialCredentialsError): await creds.get_frozen_credentials() async def test_envvarprovider_envvars_found_with_account_id(): environ = { 'AWS_ACCESS_KEY_ID': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar', 'AWS_ACCOUNT_ID': 'baz', } provider = credentials.AioEnvProvider(environ) creds = await provider.load() assert creds is not None assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.account_id == 'baz' assert creds.method == 'env' async def test_envvarprovider_can_override_account_id_env_var_mapping(): environ = { 'AWS_ACCESS_KEY_ID': 'foo', 'AWS_SECRET_ACCESS_KEY': 'bar', 'AWS_SESSION_TOKEN': 'baz', 'FOO_ACCOUNT_ID': 'bin', } provider = credentials.AioEnvProvider( environ, {'account_id': 'FOO_ACCOUNT_ID'} ) creds = await provider.load() assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' assert creds.account_id == 'bin' # From class TestConfigFileProvider(BaseEnvVar): @pytest.fixture def profile_config(): parser = mock.Mock() profile_config = { 'aws_access_key_id': 'a', 'aws_secret_access_key': 'b', 'aws_session_token': 'c', # Non creds related configs can be in a session's # config. 'region': 'us-west-2', 'output': 'json', } parsed = {'profiles': {'default': profile_config}} parser.return_value = parsed return parser async def test_configprovider_file_exists(profile_config): provider = credentials.AioConfigProvider( 'cli.cfg', 'default', profile_config ) creds = await provider.load() assert isinstance(creds, credentials.AioCredentials) assert creds.access_key == 'a' assert creds.secret_key == 'b' assert creds.method == 'config-file' async def test_configprovider_file_missing_profile(profile_config): provider = credentials.AioConfigProvider( 'cli.cfg', 'NOT-default', profile_config ) creds = await provider.load() assert creds is None async def test_configprovider_config_file_with_account_id(): profile_config = { 'aws_access_key_id': 'foo', 'aws_secret_access_key': 'bar', 'aws_session_token': 'baz', 'aws_account_id': 'bin', } parsed = {'profiles': {'default': profile_config}} parser = mock.Mock() parser.return_value = parsed provider = credentials.AioConfigProvider('cli.cfg', 'default', parser) creds = await provider.load() assert creds is not None assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' assert creds.method == 'config-file' assert creds.account_id == 'bin' # From class TestSharedCredentialsProvider(BaseEnvVar): async def test_sharedcredentials_file_exists(): parser = mock.Mock() parser.return_value = { 'default': { 'aws_access_key_id': 'foo', 'aws_secret_access_key': 'bar', } } provider = credentials.AioSharedCredentialProvider( creds_filename='~/.aws/creds', profile_name='default', ini_parser=parser, ) creds = await provider.load() assert isinstance(creds, credentials.AioCredentials) assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.method == 'shared-credentials-file' async def test_sharedcredentials_file_missing(): parser = mock.Mock() parser.side_effect = botocore.exceptions.ConfigNotFound(path='foo') provider = credentials.AioSharedCredentialProvider( creds_filename='~/.aws/creds', profile_name='dev', ini_parser=parser ) creds = await provider.load() assert creds is None async def test_sharedcredentials_credentials_file_exists_with_account_id(): parser = mock.Mock() parser.return_value = { 'default': { 'aws_access_key_id': 'foo', 'aws_secret_access_key': 'bar', 'aws_session_token': 'baz', 'aws_account_id': 'bin', } } provider = credentials.AioSharedCredentialProvider( creds_filename='~/.aws/creds', profile_name='default', ini_parser=parser, ) creds = await provider.load() assert creds is not None assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' assert creds.method == 'shared-credentials-file' assert creds.account_id == 'bin' # From class TestBotoProvider(BaseEnvVar): async def test_botoprovider_file_exists(): parser = mock.Mock() parser.return_value = { 'Credentials': { 'aws_access_key_id': 'a', 'aws_secret_access_key': 'b', } } provider = credentials.AioBotoProvider(environ={}, ini_parser=parser) creds = await provider.load() assert isinstance(creds, credentials.AioCredentials) assert creds.access_key == 'a' assert creds.secret_key == 'b' assert creds.method == 'boto-config' async def test_botoprovider_file_missing(): parser = mock.Mock() parser.side_effect = botocore.exceptions.ConfigNotFound(path='foo') provider = credentials.AioBotoProvider(environ={}, ini_parser=parser) creds = await provider.load() assert creds is None # From class TestOriginalEC2Provider(BaseEnvVar): async def test_originalec2provider_file_exists(): envrion = {'AWS_CREDENTIAL_FILE': 'foo.cfg'} parser = mock.Mock() parser.return_value = { 'AWSAccessKeyId': 'a', 'AWSSecretKey': 'b', } provider = credentials.AioOriginalEC2Provider( environ=envrion, parser=parser ) creds = await provider.load() assert isinstance(creds, credentials.AioCredentials) assert creds.access_key == 'a' assert creds.secret_key == 'b' assert creds.method == 'ec2-credentials-file' async def test_originalec2provider_file_missing(): provider = credentials.AioOriginalEC2Provider(environ={}) creds = await provider.load() assert creds is None # From class TestCreateCredentialResolver @pytest.fixture def mock_session(): def _f(config_loader: Optional[ConfigValueStore] = None) -> AioSession: if not config_loader: config_loader = ConfigValueStore() fake_instance_variables = { 'credentials_file': 'a', 'legacy_config_file': 'b', 'config_file': 'c', 'metadata_service_timeout': 1, 'metadata_service_num_attempts': 1, 'imds_use_ipv6': 'false', } def fake_get_component(self, key): if key == 'config_provider': return config_loader return None def fake_set_config_variable(self, logical_name, value): fake_instance_variables[logical_name] = value session = mock.Mock(spec=AioSession) session.get_component = fake_get_component session.full_config = {} for name, value in fake_instance_variables.items(): config_loader.set_config_variable(name, value) session.get_config_variable = config_loader.get_config_variable session.set_config_variable = fake_set_config_variable return session return _f async def test_createcredentialresolver(mock_session): session = mock_session() resolver = credentials.create_credential_resolver(session) assert isinstance(resolver, credentials.AioCredentialResolver) async def test_get_credentials(mock_session): session = mock_session() creds = await credentials.get_credentials(session) assert creds is None class Self: pass class _AsyncCtx: def __init__(self, value): self._value = value async def __aenter__(self): return self._value async def __aexit__(self, exc_type, exc_val, exc_tb): pass # From class TestSSOCredentialFetcher: @pytest.fixture async def ssl_credential_fetcher_setup(): async with AioSession().create_client( 'sso', region_name='us-east-1' ) as sso: self = Self() self.sso = sso self.stubber = Stubber(self.sso) self.mock_session = mock.Mock(spec=AioSession) self.mock_session.create_client.return_value = _AsyncCtx(sso) self.cache = {} self.sso_region = 'us-east-1' self.start_url = 'https://d-92671207e4.awsapps.com/start' self.role_name = 'test-role' self.account_id = '1234567890' self.access_token = { 'accessToken': 'some.sso.token', 'expiresAt': '2018-10-18T22:26:40Z', } # This is just an arbitrary point in time we can pin to self.now = datetime(2008, 9, 23, 12, 26, 40, tzinfo=tzutc()) # The SSO endpoint uses ms whereas the OIDC endpoint uses seconds self.now_timestamp = 1222172800000 self.mock_time_fetcher = mock.Mock(return_value=self.now) self.loader = mock.Mock(spec=SSOTokenLoader) self.loader.return_value = self.access_token self.fetcher = AioSSOCredentialFetcher( self.start_url, self.sso_region, self.role_name, self.account_id, self.mock_session.create_client, token_loader=self.loader, cache=self.cache, time_fetcher=self.mock_time_fetcher, ) tc = TestCase() self.assertEqual = tc.assertEqual self.assertRaises = tc.assertRaises self.assertFalse = tc.assertFalse yield self @pytest.fixture def base_env_var_setup(): self = Self() self.environ = {} with mock.patch('os.environ', self.environ): yield self def _some_future_time(): timeobj = datetime.now(tzlocal()) return timeobj + timedelta(hours=24) def _create_assume_role_response(credentials, expiration=None): if expiration is None: expiration = _some_future_time() response = { 'Credentials': { 'AccessKeyId': credentials.access_key, 'SecretAccessKey': credentials.secret_key, 'SessionToken': credentials.token, 'Expiration': expiration, }, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'arn:aws:iam::1234567890:user/myuser', }, } return response def _create_random_credentials(): return Credentials( f'fake-{random_chars(15)}', f'fake-{random_chars(35)}', f'fake-{random_chars(45)}', # The account_id gets resolved from the # Arn in create_assume_role_response(). account_id='1234567890', ) async def _assert_creds_equal(c1, c2): c1_frozen = c1 if not isinstance(c1_frozen, ReadOnlyCredentials): c1_frozen = await c1.get_frozen_credentials() c2_frozen = c2 if not isinstance(c2_frozen, ReadOnlyCredentials): c2_frozen = c2.get_frozen_credentials() assert c1_frozen == c2_frozen def _write_config(self, config): with open(self.config_file, 'w') as f: f.write(config) @pytest.fixture def base_assume_role_test_setup(base_env_var_setup): self = base_env_var_setup with tempfile.TemporaryDirectory() as td_name: self.tempdir = td_name self.config_file = os.path.join(self.tempdir, 'config') self.environ['AWS_CONFIG_FILE'] = self.config_file self.environ['AWS_SHARED_CREDENTIALS_FILE'] = str(uuid.uuid4()) self.some_future_time = _some_future_time self.create_assume_role_response = _create_assume_role_response self.create_random_credentials = _create_random_credentials self.assert_creds_equal = _assert_creds_equal self.write_config = partial(_write_config, self) yield self def _mock_provider(provider_cls): mock_instance = mock.Mock(spec=provider_cls) mock_instance.load.return_value = None mock_instance.METHOD = provider_cls.METHOD mock_instance.CANONICAL_NAME = provider_cls.CANONICAL_NAME return mock_instance class DummyContextWrapper(wrapt.ObjectProxy): async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): pass @asynccontextmanager async def _create_session(self, profile=None): session = StubbedSession(profile=profile) # We have to set bogus credentials here or otherwise we'll trigger # an early credential chain resolution. async with session.create_client( 'sts', aws_access_key_id='spam', aws_secret_access_key='eggs', ) as sts: self.mock_client_creator.return_value = DummyContextWrapper(sts) assume_role_provider = AioAssumeRoleProvider( load_config=lambda: session.full_config, client_creator=self.mock_client_creator, cache={}, profile_name=profile, credential_sourcer=AioCanonicalNameCredentialSourcer( [ self.env_provider, self.container_provider, self.metadata_provider, ] ), profile_provider_builder=AioProfileProviderBuilder( session, sso_token_cache=JSONFileCache(self.tempdir), ), ) async with session.stub('sts') as stubber: stubber.activate() component_name = 'credential_provider' resolver = session.get_component(component_name) available_methods = [p.METHOD for p in resolver.providers] replacements = { 'env': self.env_provider, 'iam-role': self.metadata_provider, 'container-role': self.container_provider, 'assume-role': assume_role_provider, } for name, provider in replacements.items(): try: index = available_methods.index(name) except ValueError: # The provider isn't in the session continue resolver.providers[index] = provider session.register_component('credential_provider', resolver) yield session, stubber @pytest.fixture def assume_role_setup(base_assume_role_test_setup): self = base_assume_role_test_setup self.environ['AWS_ACCESS_KEY_ID'] = 'access_key' self.environ['AWS_SECRET_ACCESS_KEY'] = 'secret_key' self.mock_provider = _mock_provider self.create_session = partial(_create_session, self) self.metadata_provider = self.mock_provider(AioInstanceMetadataProvider) self.env_provider = self.mock_provider(AioEnvProvider) self.container_provider = self.mock_provider(AioContainerProvider) self.mock_client_creator = mock.Mock(spec=AioSession.create_client) self.actual_client_region = None current_dir = os.path.dirname(os.path.abspath(__file__)) credential_process = os.path.join( current_dir, 'utils', 'credentialprocess.py' ) self.credential_process = f'{sys.executable} {credential_process}' yield self async def test_sso_credential_fetcher_can_fetch_credentials( ssl_credential_fetcher_setup, ): self = ssl_credential_fetcher_setup expected_params = { 'roleName': self.role_name, 'accountId': self.account_id, 'accessToken': self.access_token['accessToken'], } expected_response = { 'roleCredentials': { 'accessKeyId': 'foo', 'secretAccessKey': 'bar', 'sessionToken': 'baz', 'expiration': self.now_timestamp + 1000000, } } self.stubber.add_response( 'get_role_credentials', expected_response, expected_params=expected_params, ) with self.stubber: credentials = await self.fetcher.fetch_credentials() self.assertEqual(credentials['access_key'], 'foo') self.assertEqual(credentials['secret_key'], 'bar') self.assertEqual(credentials['token'], 'baz') self.assertEqual(credentials['expiry_time'], '2008-09-23T12:43:20Z') cache_key = '048db75bbe50955c16af7aba6ff9c41a3131bb7e' expected_cached_credentials = { 'ProviderType': 'sso', 'Credentials': { 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2008-09-23T12:43:20Z', 'AccountId': '1234567890', }, } self.assertEqual(self.cache[cache_key], expected_cached_credentials) async def test_sso_cred_fetcher_raises_helpful_message_on_unauthorized_exception( ssl_credential_fetcher_setup, ): self = ssl_credential_fetcher_setup expected_params = { 'roleName': self.role_name, 'accountId': self.account_id, 'accessToken': self.access_token['accessToken'], } self.stubber.add_client_error( 'get_role_credentials', service_error_code='UnauthorizedException', expected_params=expected_params, ) with self.assertRaises(botocore.exceptions.UnauthorizedSSOTokenError): with self.stubber: await self.fetcher.fetch_credentials() async def test_sso_cred_fetcher_expired_legacy_token_has_expected_behavior( ssl_credential_fetcher_setup, ): self = ssl_credential_fetcher_setup # Mock the current time to be in the future after the access token has expired now = datetime(2018, 10, 19, 12, 26, 40, tzinfo=tzutc()) mock_client = mock.AsyncMock() create_mock_client = mock.Mock(return_value=mock_client) fetcher = AioSSOCredentialFetcher( self.start_url, self.sso_region, self.role_name, self.account_id, create_mock_client, token_loader=self.loader, cache=self.cache, time_fetcher=mock.Mock(return_value=now), ) # since the cached token is expired, an UnauthorizedSSOTokenError should be # raised and GetRoleCredentials should not be called. with self.assertRaises(botocore.exceptions.UnauthorizedSSOTokenError): await fetcher.fetch_credentials() self.assertFalse(mock_client.get_role_credentials.called) @mock.patch('aiobotocore.credentials.register_feature_ids') async def test_sso_cred_fetcher_feature_ids_registered_during_get_credentials( mock_register, ssl_credential_fetcher_setup ): self = ssl_credential_fetcher_setup response = { 'roleCredentials': { 'accessKeyId': 'foo', 'secretAccessKey': 'bar', 'sessionToken': 'baz', 'expiration': self.now_timestamp + 1000000, } } params = { 'roleName': self.role_name, 'accountId': self.account_id, 'accessToken': self.access_token['accessToken'], } self.stubber.add_response( 'get_role_credentials', response, expected_params=params, ) self.stubber.activate() try: fetcher = AioSSOCredentialFetcher( self.start_url, self.sso_region, self.role_name, self.account_id, self.mock_session.create_client, token_loader=self.loader, cache=self.cache, time_fetcher=self.mock_time_fetcher, ) test_feature_ids = {'test_feature_1', 'test_feature_2'} fetcher.feature_ids = test_feature_ids await fetcher.fetch_credentials() mock_register.assert_called_once_with(test_feature_ids) finally: self.stubber.deactivate() # from TestSSOProvider @pytest.fixture async def sso_provider_setup(): self = Self() async with AioSession().create_client( 'sso', region_name='us-east-1' ) as sso: self.sso = sso self.stubber = Stubber(self.sso) self.mock_session = mock.Mock(spec=AioSession) self.mock_session.create_client.return_value = _AsyncCtx(sso) self.sso_region = 'us-east-1' self.start_url = 'https://d-92671207e4.awsapps.com/start' self.role_name = 'test-role' self.account_id = '1234567890' self.access_token = 'some.sso.token' self.profile_name = 'sso-profile' self.config = { 'sso_region': self.sso_region, 'sso_start_url': self.start_url, 'sso_role_name': self.role_name, 'sso_account_id': self.account_id, } self.expires_at = datetime.now(tzlocal()) + timedelta(hours=24) self.cached_creds_key = '048db75bbe50955c16af7aba6ff9c41a3131bb7e' self.cached_token_key = '13f9d35043871d073ab260e020f0ffde092cb14b' self.cache = { self.cached_token_key: { 'accessToken': self.access_token, 'expiresAt': self.expires_at.strftime('%Y-%m-%dT%H:%M:%S%Z'), } } self._mock_load_config = partial(_mock_load_config, self) self._add_get_role_credentials_response = partial( _add_get_role_credentials_response, self ) self.provider = AioSSOProvider( load_config=self._mock_load_config, client_creator=self.mock_session.create_client, profile_name=self.profile_name, cache=self.cache, token_cache=self.cache, ) self.expected_get_role_credentials_params = { 'roleName': self.role_name, 'accountId': self.account_id, 'accessToken': self.access_token, } expiration = datetime2timestamp(self.expires_at) self.expected_get_role_credentials_response = { 'roleCredentials': { 'accessKeyId': 'foo', 'secretAccessKey': 'bar', 'sessionToken': 'baz', 'expiration': int(expiration * 1000), } } tc = TestCase() self.assertEqual = tc.assertEqual self.assertRaises = tc.assertRaises yield self def _mock_load_config(self): return { 'profiles': { self.profile_name: self.config, } } def _add_get_role_credentials_response(self): self.stubber.add_response( 'get_role_credentials', self.expected_get_role_credentials_response, self.expected_get_role_credentials_params, ) async def test_load_sso_credentials_without_cache(sso_provider_setup): self = sso_provider_setup _add_get_role_credentials_response(self) with self.stubber: credentials = await self.provider.load() credentials = await credentials.get_frozen_credentials() self.assertEqual(credentials.access_key, 'foo') self.assertEqual(credentials.secret_key, 'bar') self.assertEqual(credentials.token, 'baz') async def test_load_sso_credentials_with_cache(sso_provider_setup): self = sso_provider_setup cached_creds = { 'Credentials': { 'AccessKeyId': 'cached-akid', 'SecretAccessKey': 'cached-sak', 'SessionToken': 'cached-st', 'Expiration': self.expires_at.strftime('%Y-%m-%dT%H:%M:%S%Z'), } } self.cache[self.cached_creds_key] = cached_creds credentials = await self.provider.load() credentials = await credentials.get_frozen_credentials() self.assertEqual(credentials.access_key, 'cached-akid') self.assertEqual(credentials.secret_key, 'cached-sak') self.assertEqual(credentials.token, 'cached-st') async def test_load_sso_credentials_with_cache_expired(sso_provider_setup): self = sso_provider_setup cached_creds = { 'Credentials': { 'AccessKeyId': 'expired-akid', 'SecretAccessKey': 'expired-sak', 'SessionToken': 'expired-st', 'Expiration': '2002-10-22T20:52:11UTC', } } self.cache[self.cached_creds_key] = cached_creds self._add_get_role_credentials_response() with self.stubber: credentials = await self.provider.load() credentials = await credentials.get_frozen_credentials() self.assertEqual(credentials.access_key, 'foo') self.assertEqual(credentials.secret_key, 'bar') self.assertEqual(credentials.token, 'baz') async def test_required_config_not_set(sso_provider_setup): self = sso_provider_setup del self.config['sso_start_url'] # If any required configuration is missing we should get an error with self.assertRaises(botocore.exceptions.InvalidConfigError): await self.provider.load() async def test_load_sso_credentials_with_account_id(sso_provider_setup): self = sso_provider_setup self._add_get_role_credentials_response() with self.stubber: credentials = await self.provider.load() credentials = await credentials.get_frozen_credentials() self.assertEqual(credentials.access_key, 'foo') self.assertEqual(credentials.secret_key, 'bar') self.assertEqual(credentials.token, 'baz') self.assertEqual(credentials.account_id, '1234567890') # From class TestProcessProvider(BaseEnvVar): @pytest.fixture() def process_provider(): def _f(profile_name='default', loaded_config=None, invoked_process=None): load_config = mock.Mock(return_value=loaded_config) popen_mock = mock.Mock( return_value=invoked_process or mock.Mock(), spec=asyncio.create_subprocess_exec, ) return popen_mock, credentials.AioProcessProvider( profile_name, load_config, popen=popen_mock ) return _f async def test_processprovider_can_retrieve_account_id(process_provider): config = {'profiles': {'default': {'credential_process': 'my-process'}}} invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 1, 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2999-01-01T00:00:00Z', 'AccountId': '123456789012', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) creds = await provider.load() assert isinstance(creds, credentials.AioRefreshableCredentials) assert creds is not None assert creds.method == 'custom-process' creds = await creds.get_frozen_credentials() assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' assert creds.account_id == '123456789012' popen_mock.assert_called_with( 'my-process', stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) async def test_processprovider_can_retrieve_account_id_via_profile_config( process_provider, ): config = { 'profiles': { 'default': { 'credential_process': 'my-process', 'aws_account_id': '123456789012', } } } invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 1, 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2999-01-01T00:00:00Z', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) creds = await provider.load() assert isinstance(creds, credentials.AioRefreshableCredentials) assert creds is not None assert creds.method == 'custom-process' creds = await creds.get_frozen_credentials() assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' assert creds.account_id == '123456789012' popen_mock.assert_called_with( 'my-process', stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) @pytest.fixture def mock_base_assume_role_credential_fetcher(): return credentials.AioBaseAssumeRoleCredentialFetcher( client_creator=mock.Mock(), role_arn='arn:aws:iam::123456789012:role/RoleA', ) def test_add_account_id_to_response_with_valid_arn( mock_base_assume_role_credential_fetcher, ): response = { 'Credentials': {}, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'arn:aws:iam::123456789012:role/RoleA', }, } mock_base_assume_role_credential_fetcher._add_account_id_to_response( response ) assert 'AccountId' in response['Credentials'] assert response['Credentials']['AccountId'] == '123456789012' def test_add_account_id_to_response_with_invalid_arn( mock_base_assume_role_credential_fetcher, caplog ): response = { 'Credentials': {}, 'AssumedRoleUser': { 'AssumedRoleId': 'myroleid', 'Arn': 'invalid-arn', }, } with caplog.at_level(logging.DEBUG): mock_base_assume_role_credential_fetcher._add_account_id_to_response( response ) assert 'AccountId' not in response['Credentials'] assert 'Unable to extract account ID from Arn' in caplog.text # From class TestRefreshableCredentials(TestCredentials): @pytest.fixture def refreshable_creds(): def _f(mock_time_return_value=None, refresher_return_value='METADATA'): refresher = mock.AsyncMock() future_time = datetime.now(tzlocal()) + timedelta(hours=24) expiry_time = datetime.now(tzlocal()) - timedelta(minutes=30) metadata = { 'access_key': 'NEW-ACCESS', 'secret_key': 'NEW-SECRET', 'token': 'NEW-TOKEN', 'expiry_time': future_time.isoformat(), 'role_name': 'rolename', } refresher.return_value = ( metadata if refresher_return_value == 'METADATA' else refresher_return_value ) mock_time = mock.Mock() mock_time.return_value = mock_time_return_value creds = credentials.AioRefreshableCredentials( 'ORIGINAL-ACCESS', 'ORIGINAL-SECRET', 'ORIGINAL-TOKEN', expiry_time, refresher, 'iam-role', time_fetcher=mock_time, ) return creds return _f # From class TestDeferredRefreshableCredentials(unittest.TestCase): @pytest.fixture def deferrable_creds(): def _f(mock_time_return_value=None, refresher_return_value='METADATA'): refresher = mock.AsyncMock() future_time = datetime.now(tzlocal()) + timedelta(hours=24) metadata = { 'access_key': 'NEW-ACCESS', 'secret_key': 'NEW-SECRET', 'token': 'NEW-TOKEN', 'expiry_time': future_time.isoformat(), 'role_name': 'rolename', } refresher.return_value = ( metadata if refresher_return_value == 'METADATA' else refresher_return_value ) mock_time = mock.Mock() mock_time.return_value = mock_time_return_value or datetime.now( tzlocal() ) creds = credentials.AioDeferredRefreshableCredentials( refresher, 'iam-role', time_fetcher=mock_time ) return creds return _f async def test_refreshablecredentials_get_credentials_set(refreshable_creds): creds = refreshable_creds( mock_time_return_value=( datetime.now(tzlocal()) - timedelta(minutes=60) ) ) assert not creds.refresh_needed() credentials_set = await creds.get_frozen_credentials() assert isinstance(credentials_set, credentials.ReadOnlyCredentials) assert credentials_set.access_key == 'ORIGINAL-ACCESS' assert credentials_set.secret_key == 'ORIGINAL-SECRET' assert credentials_set.token == 'ORIGINAL-TOKEN' async def test_refreshablecredentials_refresh_returns_empty_dict( refreshable_creds, ): creds = refreshable_creds( mock_time_return_value=datetime.now(tzlocal()), refresher_return_value={}, ) assert creds.refresh_needed() with pytest.raises(botocore.exceptions.CredentialRetrievalError): await creds.get_frozen_credentials() async def test_refreshablecredentials_refresh_returns_none(refreshable_creds): creds = refreshable_creds( mock_time_return_value=datetime.now(tzlocal()), refresher_return_value=None, ) assert creds.refresh_needed() with pytest.raises(botocore.exceptions.CredentialRetrievalError): await creds.get_frozen_credentials() async def test_refreshablecredentials_refresh_returns_partial( refreshable_creds, ): creds = refreshable_creds( mock_time_return_value=datetime.now(tzlocal()), refresher_return_value={'access_key': 'akid'}, ) assert creds.refresh_needed() with pytest.raises(botocore.exceptions.CredentialRetrievalError): await creds.get_frozen_credentials() async def test_deferrablecredentials_get_credentials_set(deferrable_creds): creds = deferrable_creds() creds._refresh_using.assert_not_called() await creds.get_frozen_credentials() assert creds._refresh_using.call_count == 1 async def test_deferrablecredentials_refresh_only_called_once( deferrable_creds, ): creds = deferrable_creds() creds._refresh_using.assert_not_called() for _ in range(5): await creds.get_frozen_credentials() assert creds._refresh_using.call_count == 1 # From class TestInstanceMetadataProvider(BaseEnvVar): async def test_instancemetadata_load(): timeobj = datetime.now(tzlocal()) timestamp = (timeobj + timedelta(hours=24)).isoformat() fetcher = mock.AsyncMock() fetcher.retrieve_iam_role_credentials = mock.AsyncMock( return_value={ 'access_key': 'a', 'secret_key': 'b', 'token': 'c', 'expiry_time': timestamp, 'role_name': 'myrole', } ) provider = credentials.AioInstanceMetadataProvider( iam_role_fetcher=fetcher ) creds = await provider.load() assert creds is not None assert creds.method == 'iam-role' creds = await creds.get_frozen_credentials() assert creds.access_key == 'a' assert creds.secret_key == 'b' assert creds.token == 'c' async def test_containerprovider_assume_role_no_cache(): environ = { 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI': '/latest/credentials?id=foo' } fetcher = mock.AsyncMock() fetcher.full_url = full_url timeobj = datetime.now(tzlocal()) timestamp = (timeobj + timedelta(hours=24)).isoformat() fetcher.retrieve_full_uri.return_value = { "AccessKeyId": "access_key", "SecretAccessKey": "secret_key", "Token": "token", "Expiration": timestamp, } provider = credentials.AioContainerProvider(environ, fetcher) # Will return refreshable credentials creds = await provider.load() url = full_url('/latest/credentials?id=foo') fetcher.retrieve_full_uri.assert_called_with(url, headers=None) assert creds.method == 'container-role' creds = await creds.get_frozen_credentials() assert creds.access_key == 'access_key' assert creds.secret_key == 'secret_key' assert creds.token == 'token' async def test_processprovider_retrieve_refereshable_creds(process_provider): config = { 'profiles': {'default': {'credential_process': 'my-process /somefile'}} } invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 1, 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2999-01-01T00:00:00Z', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) creds = await provider.load() assert isinstance(creds, credentials.AioRefreshableCredentials) assert creds is not None assert creds.method == 'custom-process' creds = await creds.get_frozen_credentials() assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' popen_mock.assert_called_with( 'my-process', '/somefile', stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) async def test_processprovider_retrieve_creds(process_provider): config = {'profiles': {'default': {'credential_process': 'my-process'}}} invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 1, 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) creds = await provider.load() assert isinstance(creds, credentials.AioCredentials) assert creds is not None assert creds.access_key == 'foo' assert creds.secret_key == 'bar' assert creds.token == 'baz' assert creds.method == 'custom-process' async def test_processprovider_bad_version(process_provider): config = {'profiles': {'default': {'credential_process': 'my-process'}}} invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 2, 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2999-01-01T00:00:00Z', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) with pytest.raises(botocore.exceptions.CredentialRetrievalError): await provider.load() async def test_processprovider_missing_field(process_provider): config = {'profiles': {'default': {'credential_process': 'my-process'}}} invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 1, 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2999-01-01T00:00:00Z', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) with pytest.raises(botocore.exceptions.CredentialRetrievalError): await provider.load() async def test_processprovider_bad_exitcode(process_provider): config = {'profiles': {'default': {'credential_process': 'my-process'}}} invoked_process = mock.AsyncMock() stdout = 'lah' invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 1 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) with pytest.raises(botocore.exceptions.CredentialRetrievalError): await provider.load() async def test_processprovider_bad_config(process_provider): config = {'profiles': {'default': {'credential_process': None}}} invoked_process = mock.AsyncMock() stdout = json.dumps( { 'Version': 2, 'AccessKeyId': 'foo', 'SecretAccessKey': 'bar', 'SessionToken': 'baz', 'Expiration': '2999-01-01T00:00:00Z', } ) invoked_process.communicate.return_value = (stdout.encode('utf-8'), b'') invoked_process.returncode = 0 popen_mock, provider = process_provider( loaded_config=config, invoked_process=invoked_process ) creds = await provider.load() assert creds is None async def test_session_credentials(): with mock.patch( 'aiobotocore.credentials.AioCredentialResolver.load_credentials' ) as mock_obj: mock_obj.return_value = 'somecreds' session = AioSession() creds = await session.get_credentials() assert creds == 'somecreds' aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_eventstream.py000066400000000000000000000403011510465277300272730ustar00rootroot00000000000000# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Unit tests for the binary event stream decoder.""" from unittest import mock import pytest from botocore.eventstream import ( ChecksumMismatch, DecodeUtils, DuplicateHeader, EventStreamBuffer, EventStreamHeaderParser, EventStreamMessage, InvalidHeadersLength, MessagePrelude, NoInitialResponseError, ) from botocore.exceptions import EventStreamError from aiobotocore.eventstream import AioEventStream from aiobotocore.parsers import AioEventStreamXMLParser EMPTY_MESSAGE = ( b'\x00\x00\x00\x10\x00\x00\x00\x00\x05\xc2H\xeb}\x98\xc8\xff', EventStreamMessage( prelude=MessagePrelude( total_length=0x10, headers_length=0, crc=0x05C248EB, ), headers={}, payload=b'', crc=0x7D98C8FF, ), ) INT8_HEADER = ( ( b"\x00\x00\x00\x17\x00\x00\x00\x07)\x86\x01X\x04" b"byte\x02\xff\xc2\xf8i\xdc" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x17, headers_length=0x7, crc=0x29860158, ), headers={'byte': -1}, payload=b'', crc=0xC2F869DC, ), ) INT16_HEADER = ( ( b"\x00\x00\x00\x19\x00\x00\x00\tq\x0e\x92>\x05" b"short\x03\xff\xff\xb2|\xb6\xcc" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x19, headers_length=0x9, crc=0x710E923E, ), headers={'short': -1}, payload=b'', crc=0xB27CB6CC, ), ) INT32_HEADER = ( ( b"\x00\x00\x00\x1d\x00\x00\x00\r\x83\xe3\xf0\xe7\x07" b"integer\x04\xff\xff\xff\xff\x8b\x8e\x12\xeb" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x1D, headers_length=0xD, crc=0x83E3F0E7, ), headers={'integer': -1}, payload=b'', crc=0x8B8E12EB, ), ) INT64_HEADER = ( ( b"\x00\x00\x00\x1e\x00\x00\x00\x0e]J\xdb\x8d\x04" b"long\x05\xff\xff\xff\xff\xff\xff\xff\xffK\xc22\xda" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x1E, headers_length=0xE, crc=0x5D4ADB8D, ), headers={'long': -1}, payload=b'', crc=0x4BC232DA, ), ) PAYLOAD_NO_HEADERS = ( b"\x00\x00\x00\x1d\x00\x00\x00\x00\xfdR\x8cZ{'foo':'bar'}\xc3e96", EventStreamMessage( prelude=MessagePrelude( total_length=0x1D, headers_length=0, crc=0xFD528C5A, ), headers={}, payload=b"{'foo':'bar'}", crc=0xC3653936, ), ) PAYLOAD_ONE_STR_HEADER = ( ( b"\x00\x00\x00=\x00\x00\x00 \x07\xfd\x83\x96\x0ccontent-type\x07\x00\x10" b"application/json{'foo':'bar'}\x8d\x9c\x08\xb1" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x3D, headers_length=0x20, crc=0x07FD8396, ), headers={'content-type': 'application/json'}, payload=b"{'foo':'bar'}", crc=0x8D9C08B1, ), ) ALL_HEADERS_TYPES = ( ( b"\x00\x00\x00\x62\x00\x00\x00\x52\x03\xb5\xcb\x9c" b"\x010\x00\x011\x01\x012\x02\x02\x013\x03\x00\x03" b"\x014\x04\x00\x00\x00\x04\x015\x05\x00\x00\x00\x00\x00\x00\x00\x05" b"\x016\x06\x00\x05bytes\x017\x07\x00\x04utf8" b"\x018\x08\x00\x00\x00\x00\x00\x00\x00\x08\x019\x090123456789abcdef" b"\x63\x35\x36\x71" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x62, headers_length=0x52, crc=0x03B5CB9C, ), headers={ '0': True, '1': False, '2': 0x02, '3': 0x03, '4': 0x04, '5': 0x05, '6': b'bytes', '7': 'utf8', '8': 0x08, '9': b'0123456789abcdef', }, payload=b"", crc=0x63353671, ), ) ERROR_EVENT_MESSAGE = ( ( b"\x00\x00\x00\x52\x00\x00\x00\x42\xbf\x23\x63\x7e" b"\x0d:message-type\x07\x00\x05error" b"\x0b:error-code\x07\x00\x04code" b"\x0e:error-message\x07\x00\x07message" b"\x6b\x6c\xea\x3d" ), EventStreamMessage( prelude=MessagePrelude( total_length=0x52, headers_length=0x42, crc=0xBF23637E, ), headers={ ':message-type': 'error', ':error-code': 'code', ':error-message': 'message', }, payload=b'', crc=0x6B6CEA3D, ), ) # Tuples of encoded messages and their expected decoded output POSITIVE_CASES = [ EMPTY_MESSAGE, INT8_HEADER, INT16_HEADER, INT32_HEADER, INT64_HEADER, PAYLOAD_NO_HEADERS, PAYLOAD_ONE_STR_HEADER, ALL_HEADERS_TYPES, ERROR_EVENT_MESSAGE, ] CORRUPTED_HEADER_LENGTH = ( ( b"\x00\x00\x00=\xff\x00\x01\x02\x07\xfd\x83\x96\x0ccontent-type\x07\x00" b"\x10application/json{'foo':'bar'}\x8d\x9c\x08\xb1" ), ChecksumMismatch, ) CORRUPTED_HEADERS = ( ( b"\x00\x00\x00=\x00\x00\x00 \x07\xfd\x83\x96\x0ccontent+type\x07\x00\x10" b"application/json{'foo':'bar'}\x8d\x9c\x08\xb1" ), ChecksumMismatch, ) CORRUPTED_LENGTH = ( b"\x01\x00\x00\x1d\x00\x00\x00\x00\xfdR\x8cZ{'foo':'bar'}\xc3e96", ChecksumMismatch, ) CORRUPTED_PAYLOAD = ( b"\x00\x00\x00\x1d\x00\x00\x00\x00\xfdR\x8cZ{'foo':'bar'\x8d\xc3e96", ChecksumMismatch, ) DUPLICATE_HEADER = ( ( b"\x00\x00\x00\x24\x00\x00\x00\x14\x4b\xb9\x82\xd0" b"\x04test\x04asdf\x04test\x04asdf\xf3\xf4\x75\x63" ), DuplicateHeader, ) # In contrast to the CORRUPTED_HEADERS case, this message is otherwise # well-formed - the checksums match. INVALID_HEADERS_LENGTH = ( ( b"\x00\x00\x00\x3d" # total length b"\xff\x00\x01\x02" # headers length b"\x15\x83\xf5\xc2" # prelude crc b"\x0ccontent-type\x07\x00\x10application/json" # headers b"{'foo':'bar'}" # payload b"\x2f\x37\x7f\x5d" # message crc ), InvalidHeadersLength, ) # Tuples of encoded messages and their expected exception NEGATIVE_CASES = [ CORRUPTED_LENGTH, CORRUPTED_PAYLOAD, CORRUPTED_HEADERS, CORRUPTED_HEADER_LENGTH, DUPLICATE_HEADER, INVALID_HEADERS_LENGTH, ] def assert_message_equal(message_a, message_b): """Asserts all fields for two messages are equal.""" assert message_a.prelude.total_length == message_b.prelude.total_length assert message_a.prelude.headers_length == message_b.prelude.headers_length assert message_a.prelude.crc == message_b.prelude.crc assert message_a.headers == message_b.headers assert message_a.payload == message_b.payload assert message_a.crc == message_b.crc def test_partial_message(): """Ensure that we can receive partial payloads.""" data = EMPTY_MESSAGE[0] event_buffer = EventStreamBuffer() # This mid point is an arbitrary break in the middle of the headers mid_point = 15 event_buffer.add_data(data[:mid_point]) messages = list(event_buffer) assert messages == [] event_buffer.add_data(data[mid_point : len(data)]) for message in event_buffer: assert_message_equal(message, EMPTY_MESSAGE[1]) def check_message_decodes(encoded, decoded): """Ensure the message decodes to what we expect.""" event_buffer = EventStreamBuffer() event_buffer.add_data(encoded) messages = list(event_buffer) assert len(messages) == 1 assert_message_equal(messages[0], decoded) @pytest.mark.parametrize("encoded, decoded", POSITIVE_CASES) def test_positive_cases(encoded, decoded): """Test that all positive cases decode how we expect.""" check_message_decodes(encoded, decoded) def test_all_positive_cases(): """Test all positive cases can be decoded on the same buffer.""" event_buffer = EventStreamBuffer() # add all positive test cases to the same buffer for encoded, _ in POSITIVE_CASES: event_buffer.add_data(encoded) # collect all of the expected messages expected_messages = [decoded for (_, decoded) in POSITIVE_CASES] # collect all of the decoded messages decoded_messages = list(event_buffer) # assert all messages match what we expect for expected, decoded in zip(expected_messages, decoded_messages): assert_message_equal(expected, decoded) @pytest.mark.parametrize( "encoded, exception", NEGATIVE_CASES, ids=[ "corrupted-length", "corrupted-payload", "corrupted-headers", "corrupted-headers-length", "duplicate-headers", "invalid-headers-length", ], ) def test_negative_cases(encoded, exception): """Test that all negative cases raise the expected exception.""" with pytest.raises(exception): check_message_decodes(encoded, None) def test_header_parser(): """Test that the header parser supports all header types.""" headers_data = ( b"\x010\x00\x011\x01\x012\x02\x02\x013\x03\x00\x03" b"\x014\x04\x00\x00\x00\x04\x015\x05\x00\x00\x00\x00\x00\x00\x00\x05" b"\x016\x06\x00\x05bytes\x017\x07\x00\x04utf8" b"\x018\x08\x00\x00\x00\x00\x00\x00\x00\x08\x019\x090123456789abcdef" ) expected_headers = { '0': True, '1': False, '2': 0x02, '3': 0x03, '4': 0x04, '5': 0x05, '6': b'bytes', '7': 'utf8', '8': 0x08, '9': b'0123456789abcdef', } parser = EventStreamHeaderParser() headers = parser.parse(headers_data) assert headers == expected_headers def test_message_prelude_properties(): """Test that calculated properties from the payload are correct.""" # Total length: 40, Headers Length: 15, random crc prelude = MessagePrelude(40, 15, 0x00000000) assert prelude.payload_length == 9 assert prelude.headers_end == 27 assert prelude.payload_end == 36 def test_message_to_response_dict(): response_dict = PAYLOAD_ONE_STR_HEADER[1].to_response_dict() assert response_dict['status_code'] == 200 expected_headers = {'content-type': 'application/json'} assert response_dict['headers'] == expected_headers assert response_dict['body'] == b"{'foo':'bar'}" def test_message_to_response_dict_error(): response_dict = ERROR_EVENT_MESSAGE[1].to_response_dict() assert response_dict['status_code'] == 400 headers = { ':message-type': 'error', ':error-code': 'code', ':error-message': 'message', } assert response_dict['headers'] == headers assert response_dict['body'] == b'' def test_unpack_uint8(): (value, bytes_consumed) = DecodeUtils.unpack_uint8(b'\xde') assert bytes_consumed == 1 assert value == 0xDE def test_unpack_uint32(): (value, bytes_consumed) = DecodeUtils.unpack_uint32(b'\xde\xad\xbe\xef') assert bytes_consumed == 4 assert value == 0xDEADBEEF def test_unpack_int8(): (value, bytes_consumed) = DecodeUtils.unpack_int8(b'\xfe') assert bytes_consumed == 1 assert value == -2 def test_unpack_int16(): (value, bytes_consumed) = DecodeUtils.unpack_int16(b'\xff\xfe') assert bytes_consumed == 2 assert value == -2 def test_unpack_int32(): (value, bytes_consumed) = DecodeUtils.unpack_int32(b'\xff\xff\xff\xfe') assert bytes_consumed == 4 assert value == -2 def test_unpack_int64(): test_bytes = b'\xff\xff\xff\xff\xff\xff\xff\xfe' (value, bytes_consumed) = DecodeUtils.unpack_int64(test_bytes) assert bytes_consumed == 8 assert value == -2 def test_unpack_array_short(): test_bytes = b'\x00\x10application/json' (value, bytes_consumed) = DecodeUtils.unpack_byte_array(test_bytes) assert bytes_consumed == 18 assert value == b'application/json' def test_unpack_byte_array_int(): (value, array_bytes_consumed) = DecodeUtils.unpack_byte_array( b'\x00\x00\x00\x10application/json', length_byte_size=4 ) assert array_bytes_consumed == 20 assert value == b'application/json' def test_unpack_utf8_string(): length = b'\x00\x09' utf8_string = b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e' encoded = length + utf8_string (value, bytes_consumed) = DecodeUtils.unpack_utf8_string(encoded) assert bytes_consumed == 11 assert value == utf8_string.decode('utf-8') def test_unpack_prelude(): data = b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03' prelude = DecodeUtils.unpack_prelude(data) assert prelude == ((1, 2, 3), 12) def create_mock_raw_stream(*data: bytes): data = list(data) raw_stream = mock.Mock() class generator: def __aiter__(self): return self async def __anext__(self): nonlocal data if not data: raise StopAsyncIteration chunk = data.pop(0) return chunk, True raw_stream.content.iter_chunks = generator return raw_stream async def test_event_stream_wrapper_iteration(): raw_stream = create_mock_raw_stream( b"\x00\x00\x00+\x00\x00\x00\x0e4\x8b\xec{\x08event-id\x04\x00", b"\x00\xa0\x0c{'foo':'bar'}\xd3\x89\x02\x85", ) parser = mock.Mock(spec=AioEventStreamXMLParser) output_shape = mock.Mock() event_stream = AioEventStream(raw_stream, output_shape, parser, '') events = [e async for e in event_stream] assert len(events) == 1 response_dict = { 'headers': {'event-id': 0x0000A00C}, 'body': b"{'foo':'bar'}", 'status_code': 200, } parser.parse.assert_called_with(response_dict, output_shape) async def test_eventstream_wrapper_iteration_error(): raw_stream = create_mock_raw_stream(ERROR_EVENT_MESSAGE[0]) parser = mock.Mock(spec=AioEventStreamXMLParser) parser.parse.return_value = {} output_shape = mock.Mock() event_stream = AioEventStream(raw_stream, output_shape, parser, '') with pytest.raises(EventStreamError): async for _ in event_stream: pass def test_event_stream_wrapper_close(): raw_stream = mock.Mock() event_stream = AioEventStream(raw_stream, None, None, '') event_stream.close() raw_stream.close.assert_called_once_with() async def test_event_stream_initial_response(): raw_stream = create_mock_raw_stream( b'\x00\x00\x00~\x00\x00\x00O\xc5\xa3\xdd\xc6\r:message-type\x07\x00', b'\x05event\x0b:event-type\x07\x00\x10initial-response\r:content-type', b'\x07\x00\ttext/json{"InitialResponse": "sometext"}\xf6\x98$\x83', ) parser = mock.Mock(spec=AioEventStreamXMLParser) output_shape = mock.Mock() event_stream = AioEventStream(raw_stream, output_shape, parser, '') event = await event_stream.get_initial_response() headers = { ':message-type': 'event', ':event-type': 'initial-response', ':content-type': 'text/json', } payload = b'{"InitialResponse": "sometext"}' assert event.headers == headers assert event.payload == payload async def test_event_stream_initial_response_wrong_type(): raw_stream = create_mock_raw_stream( b"\x00\x00\x00+\x00\x00\x00\x0e4\x8b\xec{\x08event-id\x04\x00", b"\x00\xa0\x0c{'foo':'bar'}\xd3\x89\x02\x85", ) parser = mock.Mock(spec=AioEventStreamXMLParser) output_shape = mock.Mock() event_stream = AioEventStream(raw_stream, output_shape, parser, '') with pytest.raises(NoInitialResponseError): await event_stream.get_initial_response() async def test_event_stream_initial_response_no_event(): raw_stream = create_mock_raw_stream(b'') parser = mock.Mock(spec=AioEventStreamXMLParser) output_shape = mock.Mock() event_stream = AioEventStream(raw_stream, output_shape, parser, '') with pytest.raises(NoInitialResponseError): await event_stream.get_initial_response() aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_handlers.py000066400000000000000000000035521510465277300265450ustar00rootroot00000000000000# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from unittest import mock from aiobotocore import handlers class TestHandlers: async def test_500_status_code_set_for_200_response(self): http_response = mock.Mock() http_response.status_code = 200 async def content(): return """ AccessDenied Access Denied id hostid """ http_response.content = content() await handlers.check_for_200_error((http_response, {})) assert http_response.status_code == 500 async def test_200_response_with_no_error_left_untouched(self): http_response = mock.Mock() http_response.status_code = 200 async def content(): return "" http_response.content = content() await handlers.check_for_200_error((http_response, {})) # We don't touch the status code since there are no errors present. assert http_response.status_code == 200 async def test_500_response_can_be_none(self): # A 500 response can raise an exception, which means the response # object is None. We need to handle this case. await handlers.check_for_200_error(None) aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_httpchecksum.py000066400000000000000000000535041510465277300274510ustar00rootroot00000000000000# Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from io import BytesIO from unittest import mock import pytest from botocore.exceptions import AwsChunkedWrapperError, FlexibleChecksumError from botocore.httpchecksum import Crc32Checksum from botocore.model import OperationModel, StringShape, StructureShape from aiobotocore.awsrequest import AioAWSResponse from aiobotocore.config import AioConfig from aiobotocore.httpchecksum import ( AioAwsChunkedWrapper, StreamingChecksumBody, apply_request_checksum, handle_checksum_body, ) from aiobotocore.response import StreamingBody from tests.test_response import AsyncBytesIO class TestHttpChecksumHandlers: def _make_operation_model( self, http_checksum=None, streaming_output=False, streaming_input=False, required=False, ): operation = mock.Mock(spec=OperationModel) if http_checksum is None: http_checksum = {} operation.http_checksum = http_checksum operation.http_checksum_required = required operation.has_streaming_output = streaming_output operation.has_streaming_input = streaming_input if http_checksum and "requestAlgorithmMember" in http_checksum: shape = mock.Mock(spec=StringShape) shape.serialization = {"name": "x-amz-request-algorithm"} operation.input_shape = mock.Mock(spec=StructureShape) operation.input_shape.members = { http_checksum["requestAlgorithmMember"]: shape } return operation def _make_http_response( self, body, headers=None, context=None, streaming=False, ): if context is None: context = {} if headers is None: headers = {} http_response = mock.Mock(spec=AioAWSResponse) http_response.raw = AsyncBytesIO(body) async def _content_prop(): return body type(http_response).content = mock.PropertyMock( side_effect=_content_prop ) http_response.status_code = 200 http_response.headers = headers response_dict = { "headers": http_response.headers, "status_code": http_response.status_code, "context": context, } if streaming: response_dict["body"] = StreamingBody( http_response.raw, response_dict["headers"].get("content-length"), ) else: response_dict["body"] = body return http_response, response_dict def _build_request(self, body): request = { "headers": {}, "body": body, "context": { "client_config": AioConfig( request_checksum_calculation="when_supported", ) }, "url": "https://example.com", } return request def test_apply_request_checksum_handles_no_checksum_context(self): request = self._build_request(b"") apply_request_checksum(request) # Build another request and assert the original request is the same expected_request = self._build_request(b"") assert request["headers"] == expected_request["headers"] assert request["body"] == expected_request["body"] assert request["url"] == expected_request["url"] def test_apply_request_checksum_handles_invalid_context(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "http-trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } with pytest.raises(FlexibleChecksumError): apply_request_checksum(request) def test_apply_request_checksum_flex_header_bytes(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "header", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } apply_request_checksum(request) assert "x-amz-checksum-crc32" in request["headers"] def test_apply_request_checksum_flex_header_readable(self): request = self._build_request(BytesIO(b"")) request["context"]["checksum"] = { "request_algorithm": { "in": "header", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } apply_request_checksum(request) assert "x-amz-checksum-crc32" in request["headers"] def test_apply_request_checksum_flex_header_explicit_digest(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "header", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } request["headers"]["x-amz-checksum-crc32"] = "foo" apply_request_checksum(request) # The checksum should not have been modified assert request["headers"]["x-amz-checksum-crc32"] == "foo" def test_apply_request_checksum_flex_trailer_bytes(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } apply_request_checksum(request) assert "x-amz-checksum-crc32" not in request["headers"] assert isinstance(request["body"], AioAwsChunkedWrapper) def test_apply_request_checksum_flex_trailer_readable(self): request = self._build_request(BytesIO(b"")) request["context"]["checksum"] = { "request_algorithm": { "in": "trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } apply_request_checksum(request) assert "x-amz-checksum-crc32" not in request["headers"] assert isinstance(request["body"], AioAwsChunkedWrapper) def test_apply_request_checksum_flex_header_trailer_explicit_digest(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } request["headers"]["x-amz-checksum-crc32"] = "foo" apply_request_checksum(request) # The checksum should not have been modified assert request["headers"]["x-amz-checksum-crc32"] == "foo" # The body should not have been wrapped assert isinstance(request["body"], bytes) def test_apply_request_checksum_content_encoding_preset(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } request["headers"]["Content-Encoding"] = "foo" apply_request_checksum(request) # The content encoding should only have been appended assert request["headers"]["Content-Encoding"] == "foo,aws-chunked" def test_apply_request_checksum_content_encoding_default(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", } } apply_request_checksum(request) assert request["headers"]["Content-Encoding"] == "aws-chunked" def test_apply_request_checksum_extra_headers(self): request = self._build_request(b"") request["context"]["checksum"] = { "request_algorithm": { "in": "trailer", "algorithm": "crc32", "name": "x-amz-checksum-crc32", }, "request_algorithm_header": { "name": "foo", "value": "bar", }, } apply_request_checksum(request) assert request["headers"]["foo"] == "bar" async def test_handle_checksum_body_checksum(self): context = {"checksum": {"response_algorithms": ["sha1", "crc32"]}} headers = {"x-amz-checksum-crc32": "DUoRhQ=="} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, ) operation_model = self._make_operation_model() await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] assert body == b"hello world" algorithm = response_dict["context"]["checksum"]["response_algorithm"] assert algorithm == "crc32" headers = {"x-amz-checksum-crc32": "WrOonG=="} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, ) with pytest.raises(FlexibleChecksumError): await handle_checksum_body( http_response, response_dict, context, operation_model, ) # This header should not be checked, we won't calculate a checksum # but a proper body should still come out at the end headers = {"x-amz-checksum-foo": "FOO=="} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, ) await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] assert body == b"hello world" algorithm = response_dict["context"]["checksum"]["response_algorithm"] assert algorithm == "crc32" async def test_handle_checksum_body_checksum_streaming(self): context = {"checksum": {"response_algorithms": ["sha1", "crc32"]}} headers = {"x-amz-checksum-crc32": "DUoRhQ=="} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, streaming=True, ) operation_model = self._make_operation_model(streaming_output=True) await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] assert await body.read() == b"hello world" algorithm = response_dict["context"]["checksum"]["response_algorithm"] assert algorithm == "crc32" headers = {"x-amz-checksum-crc32": "WrOonG=="} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, streaming=True, ) await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] with pytest.raises(FlexibleChecksumError): await body.read() # This header should not be checked, we won't calculate a checksum # but a proper body should still come out at the end headers = {"x-amz-checksum-foo": "FOOO=="} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, streaming=True, ) await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] assert await body.read() == b"hello world" algorithm = response_dict["context"]["checksum"]["response_algorithm"] assert algorithm == "crc32" async def test_handle_checksum_body_checksum_skip_non_streaming(self): context = {"checksum": {"response_algorithms": ["sha1", "crc32"]}} # S3 will return checksums over the checksums of parts which are a # special case that end with -#. These cannot be validated and are # instead skipped headers = {"x-amz-checksum-crc32": "FOOO==-123"} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, ) operation_model = self._make_operation_model() await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] assert body == b"hello world" async def test_handle_checksum_body_checksum_skip_streaming(self): context = {"checksum": {"response_algorithms": ["sha1", "crc32"]}} # S3 will return checksums over the checksums of parts which are a # special case that end with -#. These cannot be validated and are # instead skipped headers = {"x-amz-checksum-crc32": "FOOO==-123"} http_response, response_dict = self._make_http_response( b"hello world", headers=headers, context=context, streaming=True, ) operation_model = self._make_operation_model(streaming_output=True) await handle_checksum_body( http_response, response_dict, context, operation_model, ) body = response_dict["body"] assert await body.read() == b"hello world" class TestAwsChunkedWrapper: async def test_single_chunk_body(self): # Test a small body that fits in a single chunk bytes = BytesIO(b"abcdefghijklmnopqrstuvwxyz") wrapper = AioAwsChunkedWrapper(bytes) body = await wrapper.read() expected = b"1a\r\nabcdefghijklmnopqrstuvwxyz\r\n0\r\n\r\n" assert body == expected async def test_multi_chunk_body(self): # Test a body that requires multiple chunks bytes = BytesIO(b"abcdefghijklmnopqrstuvwxyz") wrapper = AioAwsChunkedWrapper(bytes, chunk_size=10) body = await wrapper.read() expected = ( b"a\r\n" b"abcdefghij\r\n" b"a\r\n" b"klmnopqrst\r\n" b"6\r\n" b"uvwxyz\r\n" b"0\r\n\r\n" ) # fmt: skip assert body == expected async def test_read_returns_less_data(self): class OneLessBytesIO(BytesIO): def read(self, size=-1): # Return 1 less byte than was asked for return super().read(size - 1) bytes = OneLessBytesIO(b"abcdefghijklmnopqrstuvwxyz") wrapper = AioAwsChunkedWrapper(bytes, chunk_size=10) body = await wrapper.read() # NOTE: This particular body is not important, but it is important that # the actual size of the chunk matches the length sent which may not # always be the configured chunk_size if the read does not return that # much data. expected = ( b"9\r\n" b"abcdefghi\r\n" b"9\r\n" b"jklmnopqr\r\n" b"8\r\n" b"stuvwxyz\r\n" b"0\r\n\r\n" ) # fmt: skip assert body == expected async def test_single_chunk_body_with_checksum(self): wrapper = AioAwsChunkedWrapper( BytesIO(b"hello world"), checksum_cls=Crc32Checksum, checksum_name="checksum", ) body = await wrapper.read() expected = b"b\r\nhello world\r\n0\r\nchecksum:DUoRhQ==\r\n\r\n" assert body == expected async def test_multi_chunk_body_with_checksum(self): wrapper = AioAwsChunkedWrapper( BytesIO(b"hello world"), chunk_size=5, checksum_cls=Crc32Checksum, checksum_name="checksum", ) body = await wrapper.read() expected = ( b"5\r\n" b"hello\r\n" b"5\r\n" b" worl\r\n" b"1\r\n" b"d\r\n" b"0\r\n" b"checksum:DUoRhQ==\r\n\r\n" ) assert body == expected async def test_multi_chunk_body_with_checksum_iter(self): wrapper = AioAwsChunkedWrapper( BytesIO(b"hello world"), chunk_size=5, checksum_cls=Crc32Checksum, checksum_name="checksum", ) expected_chunks = [ b"5\r\nhello\r\n", b"5\r\n worl\r\n", b"1\r\nd\r\n", b"0\r\nchecksum:DUoRhQ==\r\n\r\n", ] assert expected_chunks == [chunk async for chunk in wrapper] async def test_wrapper_can_be_reset(self): wrapper = AioAwsChunkedWrapper( BytesIO(b"hello world"), chunk_size=5, checksum_cls=Crc32Checksum, checksum_name="checksum", ) first_read = await wrapper.read() assert b"" == await wrapper.read() wrapper.seek(0) second_read = await wrapper.read() assert first_read == second_read assert b"checksum:DUoRhQ==" in first_read def test_wrapper_can_only_seek_to_start(self): wrapper = AioAwsChunkedWrapper(BytesIO()) with pytest.raises(AwsChunkedWrapperError): wrapper.seek(1) with pytest.raises(AwsChunkedWrapperError): wrapper.seek(0, whence=1) with pytest.raises(AwsChunkedWrapperError): wrapper.seek(1, whence=2) class TestStreamingChecksumBody: @pytest.fixture(scope="session") def raw_bytes(self): return b"hello world" @pytest.fixture def fake_body(self, raw_bytes): return AsyncBytesIO(raw_bytes) @pytest.fixture def make_wrapper(self, fake_body): def make_wrapper(checksum): return StreamingChecksumBody( fake_body, None, Crc32Checksum(), checksum, ) return make_wrapper @pytest.fixture def wrapper(self, make_wrapper): return make_wrapper("DUoRhQ==") async def test_basic_read_good(self, raw_bytes, wrapper): actual = await wrapper.read() assert actual == raw_bytes async def test_many_reads_good(self, raw_bytes, wrapper): actual = b"" actual += await wrapper.read(5) actual += await wrapper.read(5) actual += await wrapper.read(1) assert actual == raw_bytes async def test_basic_read_bad(self, make_wrapper): wrapper = make_wrapper("duorhq==") with pytest.raises(FlexibleChecksumError): await wrapper.read() async def test_many_reads_bad(self, make_wrapper): wrapper = make_wrapper("duorhq==") await wrapper.read(5) await wrapper.read(6) # Whole body has been read, next read signals the end of the stream and # validates the checksum of the body contents read with pytest.raises(FlexibleChecksumError): await wrapper.read(1) async def test_handles_variable_padding(self, raw_bytes, make_wrapper): # This digest is equivalent but with more padding wrapper = make_wrapper("DUoRhQ=====") actual = await wrapper.read() assert actual == raw_bytes async def test_iter_raises_error(self, make_wrapper): wrapper = make_wrapper("duorhq==") with pytest.raises(FlexibleChecksumError): async for chunk in wrapper: pass async def test_readinto_good(self, wrapper): chunk = bytearray(6) assert 6 == await wrapper.readinto(chunk) assert chunk == bytearray(b"hello ") assert 5 == await wrapper.readinto(chunk) # Note the trailing space here comes from the fact we've only got 5 # bytes left to read from the stream into a 6 byte buffer, so it leaves # the last byte untouched, which is the space character from the # previous read. assert chunk == bytearray(b"world ") # Whole body has been read, next read signals the end of the stream and # validates the checksum of the body contents read await wrapper.readinto(chunk) async def test_readinto_bad(self, make_wrapper): wrapper = make_wrapper("duorhq==") chunk = bytearray(6) assert 6 == await wrapper.readinto(chunk) assert chunk == bytearray(b"hello ") assert 5 == await wrapper.readinto(chunk) assert chunk == bytearray(b"world ") # Whole body has been read, next read signals the end of the stream and # validates the checksum of the body contents read with pytest.raises(FlexibleChecksumError): await wrapper.readinto(chunk) async def test_readinto_zero_bytes(self, wrapper): # Test that readinto returns 0 when 0 bytes are requested chunk = bytearray(0) assert 0 == await wrapper.readinto(chunk) assert chunk == bytearray(b"") # Whole body has been read, next read signals the end of the stream and # validates the checksum of the body contents read await wrapper.readinto(chunk) aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_protocols.py000066400000000000000000000403431510465277300267700ustar00rootroot00000000000000#!/usr/bin/env python # Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. """Test runner for the JSON models compliance tests This is a test runner for all the output JSON tests defined in ``tests/unit/protocols/``. You can use the normal ``python -m pytest tests/unit/test_protocols.py`` to run this test. In addition, there are several env vars you can use during development. Tests are broken down by filename, test suite, testcase. When a test fails you'll see the protocol (filename), test suite, and test case number of the failed test. :: Description : Scalar members (0:0) <--- (suite_id:test_id) Protocol: : ec2 <--- test file (ec2.json) Given : ... Response : ... Expected serialization: ... Actual serialization : ... Assertion message : ... To run tests from only a single file, you can set the BOTOCORE_TEST env var:: BOTOCORE_TEST=tests/unit/protocols/input/json.json pytest tests/unit/test_protocols.py To run a single test suite you can set the BOTOCORE_TEST_ID env var: BOTOCORE_TEST=tests/unit/protocols/input/json.json BOTOCORE_TEST_ID=5 \ pytest tests/unit/test_protocols.py To run a single test case in a suite (useful when debugging a single test), you can set the BOTOCORE_TEST_ID env var with the ``suite_id:test_id`` syntax. BOTOCORE_TEST_ID=5:1 pytest tests/unit/test_protocols.py """ import base64 import copy import os from enum import Enum import pytest from botocore.awsrequest import HeadersDict from botocore.compat import OrderedDict, json from botocore.model import NoShapeFoundError, OperationModel, ServiceModel from botocore.utils import parse_timestamp from dateutil.tz import tzutc from aiobotocore.eventstream import AioEventStream from aiobotocore.parsers import ( AioEC2QueryParser, AioJSONParser, AioQueryParser, AioRestJSONParser, AioRestXMLParser, AioRpcV2CBORParser, ) TEST_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'protocols' ) PROTOCOL_PARSERS = { 'ec2': AioEC2QueryParser, 'query': AioQueryParser, 'json': AioJSONParser, 'rest-json': AioRestJSONParser, 'rest-xml': AioRestXMLParser, 'smithy-rpc-v2-cbor': AioRpcV2CBORParser, } IGNORE_LIST_FILENAME = "protocol-tests-ignore-list.json" class TestType(Enum): # Tell test runner to ignore this class __test__ = False INPUT = "input" OUTPUT = "output" def get_protocol_test_ignore_list(): ignore_list_path = os.path.join(TEST_DIR, IGNORE_LIST_FILENAME) with open(ignore_list_path) as f: return json.load(f) def _compliance_tests(test_type=None): inp = test_type is None or test_type is TestType.INPUT out = test_type is None or test_type is TestType.OUTPUT for full_path in _walk_files(): if full_path.endswith('.json'): for model, case, basename in _load_cases(full_path): protocol = basename.replace('.json', '') if _should_ignore_test( protocol, "input" if inp else "output", model['description'], case.get('id'), ): continue if ('params' in case and inp) or ('response' in case and out): yield model, case, basename class FakeStreamReader: class ChunkedIterator: def __init__(self, chunks): self.iter = iter((chunks,)) def __aiter__(self): return self async def __anext__(self): try: result = next(self.iter) return result, True except StopIteration: raise StopAsyncIteration() def __init__(self, chunks): self.chunks = base64.b64decode(chunks) self.content = self def iter_chunks(self): return self.ChunkedIterator(self.chunks) @pytest.mark.parametrize( "json_description, case, basename", _compliance_tests(TestType.OUTPUT) ) async def test_output_compliance(json_description, case, basename): service_description = copy.deepcopy(json_description) case = copy.deepcopy(case) operation_name = case.get('given', {}).get('name', 'OperationName') service_description['operations'] = { operation_name: case, } case['response']['context'] = {'operation_name': operation_name} try: model = ServiceModel(service_description) operation_model = OperationModel(case['given'], model) protocol = model.metadata['protocol'] parser = PROTOCOL_PARSERS[protocol]( timestamp_parser=_compliance_timestamp_parser ) # We load the json as utf-8, but the response parser is at the # botocore boundary, so it expects to work with bytes. # If a test case doesn't define a response body, set it to `None`. if 'body' in case['response']: body_bytes = case['response']['body'].encode('utf-8') case['response']['body'] = body_bytes else: case['response']['body'] = ( b'' if protocol != "query" else b'' ) # We need the headers to be case insensitive # If a test case doesn't define response headers, set it to an empty `HeadersDict`. case['response']['headers'] = HeadersDict( case['response'].get('headers', {}) ) # If this is an event stream fake the raw streamed response if operation_model.has_event_stream_output: case['response']['body'] = FakeStreamReader(body_bytes) if 'error' in case: output_shape = operation_model.output_shape if protocol == 'smithy-rpc-v2-cbor': case['response']['body'] = base64.b64decode( case['response']['body'] ) parsed = await parser.parse(case['response'], output_shape) try: error_code = parsed.get("Error", {}).get("Code") error_shape = model.shape_for_error_code(error_code) except NoShapeFoundError: error_shape = None if error_shape is not None: error_parse = await parser.parse(case['response'], error_shape) parsed.update(error_parse) else: output_shape = operation_model.output_shape if protocol == 'query' and output_shape and output_shape.members: output_shape.serialization['resultWrapper'] = ( f'{operation_name}Result' ) elif protocol == 'smithy-rpc-v2-cbor': case['response']['body'] = base64.b64decode( case['response']['body'] ) parsed = await parser.parse(case['response'], output_shape) parsed = await _fixup_parsed_result(parsed) except Exception as e: # pragma: no cover msg = ( "\nFailed to run test : {}\n" "Protocol : {}\n" "Description : {} ({}:{})\n".format( e, model.metadata['protocol'], case['description'], case['suite_id'], case['test_id'], ) ) raise AssertionError(msg) try: if 'error' in case: expected_result = { 'Error': { 'Code': case.get('errorCode', ''), 'Message': case.get('errorMessage', ''), } } expected_result.update(case['error']) else: expected_result = case['result'] assert_equal(parsed, expected_result, "Body") except Exception as e: # pragma: no cover _output_failure_message( model.metadata['protocol'], case, parsed, expected_result, e ) async def _fixup_parsed_result(parsed): # This function contains all the transformation we need # to do from the response _our_ response parsers give # vs. the expected responses in the protocol tests. # These are implementation specific changes, not any # "we're not following the spec"-type changes. # 1. RequestMetadata. We parse this onto the returned dict, but compliance # tests don't have any specs for how to deal with request metadata. if 'ResponseMetadata' in parsed: del parsed['ResponseMetadata'] # 2. Binary blob types. In the protocol test, blob types, when base64 # decoded, always decode to something that can be expressed via utf-8. # This is not always the case. In python3, the blob type is designed to # return a bytes (not str) object. However, for these tests we'll work for # any bytes type, and decode it as utf-8 because we know that's safe for # the compliance tests. parsed = _convert_bytes_to_str(parsed) # 3. We need to expand the event stream object into the list of events for key, value in parsed.items(): if isinstance(value, AioEventStream): parsed[key] = _convert_bytes_to_str([v async for v in value]) break # 4. We parse the entire error body into the "Error" field for rest-xml # which causes some modeled fields in the response to be placed under the # error key. We don't have enough information in the test suite to assert # these properly, and they probably shouldn't be there in the first place. if 'Error' in parsed: error_keys = list(parsed['Error'].keys()) for key in error_keys: if key not in ['Code', 'Message']: del parsed['Error'][key] # 5. Special float types. In the protocol test suite, certain special float # types are represented as strings: "Infinity", "-Infinity", and "NaN". # However, we parse these values as actual floats types, so we need to convert # them back to their string representation. parsed = _convert_special_floats_to_string(parsed) return parsed def _convert_bytes_to_str(parsed): if isinstance(parsed, dict): new_dict = {} for key, value in parsed.items(): new_dict[key] = _convert_bytes_to_str(value) return new_dict elif isinstance(parsed, bytes): return parsed.decode('utf-8') elif isinstance(parsed, list): new_list = [] for item in parsed: new_list.append(_convert_bytes_to_str(item)) return new_list else: return parsed def _convert_special_floats_to_string(parsed): for key, value in parsed.items(): if value == float('Infinity'): parsed[key] = 'Infinity' elif value == float('-Infinity'): parsed[key] = '-Infinity' elif value != value: parsed[key] = 'NaN' return parsed def _compliance_timestamp_parser(value): datetime = parse_timestamp(value) # Convert from our time zone to UTC datetime = datetime.astimezone(tzutc()) # Convert to epoch. return datetime.timestamp() def _output_failure_message( protocol_type, case, actual_parsed, expected_result, error ): # pragma: no cover j = _try_json_dump error_message = ( "\nDescription : {} ({}:{})\n" "Protocol: : {}\n" "Given : {}\n" "Response : {}\n" "Expected serialization: {}\n" "Actual serialization : {}\n" "Assertion message : {}\n".format( case['description'], case['suite_id'], case['test_id'], protocol_type, j(case['given']), j(case['response']), j(expected_result), j(actual_parsed), error, ) ) raise AssertionError(error_message) def _try_json_dump(obj): # pragma: no cover try: return json.dumps(obj) except (ValueError, TypeError): return str(obj) def assert_equal(first, second, prefix): # A better assert equals. It allows you to just provide # prefix instead of the entire message. try: assert first == second except Exception: # pragma: no cover try: better = f"{prefix} (actual != expected)\n{json.dumps(first, indent=2)} !=\n{json.dumps(second, indent=2)}" except (ValueError, TypeError): better = f"{prefix} (actual != expected)\n{first} !=\n{second}" raise AssertionError(better) def _walk_files(): # Check for a shortcut when running the tests interactively. # If a BOTOCORE_TEST env var is defined, that file is used as the # only test to run. Useful when doing feature development. single_file = os.environ.get('BOTOCORE_TEST') if single_file is not None: yield os.path.abspath(single_file) else: for root, _, filenames in os.walk(TEST_DIR): for filename in filenames: if filename == IGNORE_LIST_FILENAME: continue yield os.path.join(root, filename) def _load_cases(full_path): # During development, you can set the BOTOCORE_TEST_ID # to run a specific test suite or even a specific test case. # The format is BOTOCORE_TEST_ID=suite_id:test_id or # BOTOCORE_TEST_ID=suite_id suite_id, test_id = _get_suite_test_id() all_test_data = json.load( open(full_path, encoding='utf-8'), object_pairs_hook=OrderedDict ) basename = os.path.basename(full_path) for i, test_data in enumerate(all_test_data): if suite_id is not None and i != suite_id: continue cases = test_data.pop('cases') description = test_data['description'] for j, case in enumerate(cases): if test_id is not None and j != test_id: continue case['description'] = description case['suite_id'] = i case['test_id'] = j yield (test_data, case, basename) def _get_suite_test_id(): # pragma: no cover if 'BOTOCORE_TEST_ID' not in os.environ: return None, None test_id = None suite_id = None split = os.environ['BOTOCORE_TEST_ID'].split(':') try: if len(split) == 2: suite_id, test_id = int(split[0]), int(split[1]) else: suite_id = int(split[0]) except TypeError: # Same exception, just give a better error message. raise TypeError( "Invalid format for BOTOCORE_TEST_ID, should be " "suite_id[:test_id], and both values should be " "integers." ) return suite_id, test_id def _should_ignore_test(protocol, test_type, suite, case): """ Determines if a protocol test should be ignored. :type protocol: str :param protocol: The protocol name as represented by its corresponding protocol test file name (without the .json extension). :type test_type: str :param test_type: The protocol test type ("input" or "output"). :type suite: str :param suite: The "description" attribute of a protocol test suite. :type case: str :param case: The "id" attribute of a specific protocol test case. :return: True if the protocol test should be ignored, False otherwise. :rtype: bool """ # Get test suites and cases to ignore for all protocols. ignore_list = get_protocol_test_ignore_list() general_ignore_list = ignore_list.get('general', {}).get(test_type, {}) general_suites = general_ignore_list.get('suites', []) general_cases = general_ignore_list.get('cases', []) if suite in general_suites or case in general_cases: return True # Get test suites and cases to ignore for a specific protocol. protocol_ignore_list = ( ignore_list.get('protocols', {}).get(protocol, {}).get(test_type, {}) ) protocol_suites = protocol_ignore_list.get('suites', []) protocol_cases = protocol_ignore_list.get('cases', []) return suite in protocol_suites or case in protocol_cases aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_response.py000066400000000000000000000146111510465277300266010ustar00rootroot00000000000000# Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import datetime from io import BytesIO from dateutil.tz import tzutc from aiobotocore import response from aiobotocore.awsrequest import AioAWSResponse from tests.botocore_tests.unit import BaseResponseTest XMLBODY1 = ( b'' b'AccessDenied' b'Access Denied' b'XXXXXXXXXXXXXXXX' b'AAAAAAAAAAAAAAAAAAA' b'' ) XMLBODY2 = ( b'' b'' b'mybucket' b'1000false' b'test.png2014-03-01T17:06:40.000Z' b'"00000000000000000000000000000000"6702' b'AAAAAAAAAAAAAAAAAAA' b'dummy' b'STANDARD' ) class FakeRawResponse(BytesIO): async def read(self, amt=-1): if amt == -1: # aiohttp to regular response amt = None return super().read(amt) class TestGetResponse(BaseResponseTest): maxDiff = None async def test_get_response_streaming_ok(self, session): headers = { 'content-type': 'image/png', 'server': 'AmazonS3', 'AcceptRanges': 'bytes', 'transfer-encoding': 'chunked', 'ETag': '"00000000000000000000000000000000"', } raw = FakeRawResponse(b'\x89PNG\r\n\x1a\n\x00\x00') http_response = AioAWSResponse(None, 200, headers, raw) service_model = await session.get_service_model('s3') operation_model = service_model.operation_model('GetObject') res = await response.get_response(operation_model, http_response) assert isinstance(res[1]['Body'], response.StreamingBody) assert res[1]['ETag'] == '"00000000000000000000000000000000"' async def test_get_response_streaming_ng(self, session): headers = { 'content-type': 'application/xml', 'date': 'Sat, 08 Mar 2014 12:05:44 GMT', 'server': 'AmazonS3', 'transfer-encoding': 'chunked', 'x-amz-id-2': 'AAAAAAAAAAAAAAAAAAA', 'x-amz-request-id': 'XXXXXXXXXXXXXXXX', } raw = FakeRawResponse(XMLBODY1) http_response = AioAWSResponse(None, 403, headers, raw) service_model = await session.get_service_model('s3') operation_model = service_model.operation_model('GetObject') self.assert_response_with_subset_metadata( (await response.get_response(operation_model, http_response))[1], { 'Error': {'Message': 'Access Denied', 'Code': 'AccessDenied'}, 'ResponseMetadata': { 'HostId': 'AAAAAAAAAAAAAAAAAAA', 'RequestId': 'XXXXXXXXXXXXXXXX', 'HTTPStatusCode': 403, }, }, ) async def test_get_response_nonstreaming_ok(self, session): headers = { 'content-type': 'application/xml', 'date': 'Sun, 09 Mar 2014 02:55:43 GMT', 'server': 'AmazonS3', 'transfer-encoding': 'chunked', 'x-amz-id-2': 'AAAAAAAAAAAAAAAAAAA', 'x-amz-request-id': 'XXXXXXXXXXXXXXXX', } raw = FakeRawResponse(XMLBODY1) http_response = AioAWSResponse(None, 403, headers, raw) service_model = await session.get_service_model('s3') operation_model = service_model.operation_model('ListObjects') self.assert_response_with_subset_metadata( (await response.get_response(operation_model, http_response))[1], { 'ResponseMetadata': { 'RequestId': 'XXXXXXXXXXXXXXXX', 'HostId': 'AAAAAAAAAAAAAAAAAAA', 'HTTPStatusCode': 403, }, 'Error': {'Message': 'Access Denied', 'Code': 'AccessDenied'}, }, ) async def test_get_response_nonstreaming_ng(self, session): headers = { 'content-type': 'application/xml', 'date': 'Sat, 08 Mar 2014 12:05:44 GMT', 'server': 'AmazonS3', 'transfer-encoding': 'chunked', 'x-amz-id-2': 'AAAAAAAAAAAAAAAAAAA', 'x-amz-request-id': 'XXXXXXXXXXXXXXXX', } raw = FakeRawResponse(XMLBODY2) http_response = AioAWSResponse(None, 200, headers, raw) service_model = await session.get_service_model('s3') operation_model = service_model.operation_model('ListObjects') self.assert_response_with_subset_metadata( (await response.get_response(operation_model, http_response))[1], { 'Contents': [ { 'ETag': '"00000000000000000000000000000000"', 'Key': 'test.png', 'LastModified': datetime.datetime( 2014, 3, 1, 17, 6, 40, tzinfo=tzutc() ), 'Owner': { 'DisplayName': 'dummy', 'ID': 'AAAAAAAAAAAAAAAAAAA', }, 'Size': 6702, 'StorageClass': 'STANDARD', } ], 'IsTruncated': False, 'Marker': "", 'MaxKeys': 1000, 'Name': 'mybucket', 'Prefix': "", 'ResponseMetadata': { 'RequestId': 'XXXXXXXXXXXXXXXX', 'HostId': 'AAAAAAAAAAAAAAAAAAA', 'HTTPStatusCode': 200, }, }, ) aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_session.py000066400000000000000000000533541510465277300264350ustar00rootroot00000000000000#!/usr/bin/env # Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/ # Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import logging import os import botocore.exceptions import pytest from _pytest.logging import LogCaptureFixture from botocore import UNSIGNED from botocore.configprovider import ConfigChainFactory from botocore.model import ServiceModel import aiobotocore.config from aiobotocore import client from aiobotocore.session import ClientCreatorContext from tests.botocore_tests import create_session, mock, temporary_file class BaseSessionTest: @pytest.fixture def environ(self): environ = {} environ['FOO_PROFILE'] = 'foo' environ['FOO_REGION'] = 'us-west-11' data_path = os.path.join(os.path.dirname(__file__), 'data') environ['FOO_DATA_PATH'] = data_path config_path = os.path.join( os.path.dirname(__file__), 'cfg', 'foo_config' ) environ['FOO_CONFIG_FILE'] = config_path return environ @pytest.fixture def environ_patch(self, environ): with mock.patch('os.environ', environ) as environ_patch: yield environ_patch @pytest.fixture def session(self, environ, environ_patch): session = create_session() config_chain_builder = ConfigChainFactory( session=session, environ=environ, ) config_store = session.get_component('config_store') config_updates = { 'profile': config_chain_builder.create_config_chain( instance_name='profile', env_var_names='FOO_PROFILE', ), 'region': config_chain_builder.create_config_chain( instance_name='region', env_var_names='FOO_REGION', config_property_names='foo_region', ), 'data_path': config_chain_builder.create_config_chain( instance_name='data_path', env_var_names='FOO_DATA_PATH', config_property_names='data_path', ), 'config_file': config_chain_builder.create_config_chain( instance_name='config_file', env_var_names='FOO_CONFIG_FILE', ), 'credentials_file': config_chain_builder.create_config_chain( instance_name='credentials_file', default='/tmp/nowhere', ), 'ca_bundle': config_chain_builder.create_config_chain( instance_name='ca_bundle', env_var_names='FOO_AWS_CA_BUNDLE', config_property_names='foo_ca_bundle', ), 'api_versions': config_chain_builder.create_config_chain( instance_name='api_versions', config_property_names='foo_api_versions', default={}, ), } for name, provider in config_updates.items(): config_store.set_config_provider(name, provider) return session class TestSessionPartitionFiles(BaseSessionTest): def test_lists_partitions_on_disk(self, session): mock_resolver = mock.Mock() mock_resolver.get_available_partitions.return_value = ['foo'] session._register_internal_component( 'endpoint_resolver', mock_resolver ) assert ['foo'] == session.get_available_partitions() async def test_proxies_list_endpoints_to_resolver(self, session): resolver = mock.Mock() resolver.get_available_endpoints.return_value = ['a', 'b'] session._register_internal_component('endpoint_resolver', resolver) await session.get_available_regions('foo', 'bar', True) async def test_provides_empty_list_for_unknown_service_regions( self, session ): regions = await session.get_available_regions('__foo__') assert [] == regions def test_provides_correct_partition_for_region(self, session): partition = session.get_partition_for_region('us-west-2') assert partition == 'aws' def test_provides_correct_partition_for_region_regex(self, session): partition = session.get_partition_for_region('af-south-99') assert partition == 'aws' def test_provides_correct_partition_for_region_non_default(self, session): partition = session.get_partition_for_region('cn-north-1') assert partition == 'aws-cn' def test_raises_exception_for_invalid_region(self, session): with pytest.raises(botocore.exceptions.UnknownRegionError): session.get_partition_for_region('no-good-1') class TestGetServiceModel(BaseSessionTest): async def test_get_service_model(self, session): loader = mock.Mock() loader.load_service_model.return_value = { 'metadata': {'serviceId': 'foo'} } session.register_component('data_loader', loader) model = await session.get_service_model('made_up') assert isinstance(model, ServiceModel) assert model.service_name == 'made_up' class TestCreateClient(BaseSessionTest): async def test_can_create_client(self, session): sts_client_context = session.create_client('sts', 'us-west-2') assert isinstance(sts_client_context, ClientCreatorContext) async with sts_client_context as sts_client: assert isinstance(sts_client, client.AioBaseClient) async def test_credential_provider_not_called_when_creds_provided( self, session ): cred_provider = mock.Mock() session.register_component('credential_provider', cred_provider) async with session.create_client( 'sts', 'us-west-2', aws_access_key_id='foo', aws_secret_access_key='bar', aws_session_token='baz', ): assert not cred_provider.load_credentials.called async def test_cred_provider_called_when_partial_creds_provided( self, session ): with pytest.raises(botocore.exceptions.PartialCredentialsError): async with session.create_client( 'sts', 'us-west-2', aws_access_key_id='foo', aws_secret_access_key=None, ): pass # pragma: no cover with pytest.raises(botocore.exceptions.PartialCredentialsError): async with session.create_client( 'sts', 'us-west-2', aws_access_key_id=None, aws_secret_access_key='foo', ): pass # pragma: no cover async def test_cred_provider_not_called_on_unsigned_client(self, session): cred_provider = mock.Mock() session.register_component('credential_provider', cred_provider) config = aiobotocore.config.AioConfig(signature_version=UNSIGNED) async with session.create_client('sts', 'us-west-2', config=config): assert not cred_provider.load_credentials.called @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_config_passed_to_client_creator( self, client_creator, session ): # Make sure there is no default set assert session.get_default_client_config() is None # The config passed to the client should be the one that is used # in creating the client. config = aiobotocore.config.AioConfig(region_name='us-west-2') async with session.create_client('sts', config=config): client_creator.return_value.create_client.assert_called_with( service_name=mock.ANY, region_name=mock.ANY, is_secure=mock.ANY, endpoint_url=mock.ANY, verify=mock.ANY, credentials=mock.ANY, scoped_config=mock.ANY, client_config=config, api_version=mock.ANY, auth_token=mock.ANY, ) @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_default_client_config( self, client_creator, session ): config = aiobotocore.config.AioConfig() session.set_default_client_config(config) async with session.create_client('sts'): client_creator.return_value.create_client.assert_called_with( service_name=mock.ANY, region_name=mock.ANY, is_secure=mock.ANY, endpoint_url=mock.ANY, verify=mock.ANY, credentials=mock.ANY, scoped_config=mock.ANY, client_config=config, api_version=mock.ANY, auth_token=mock.ANY, ) @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_merging_client_configs( self, client_creator, session ): config = aiobotocore.config.AioConfig(region_name='us-west-2') other_config = aiobotocore.config.AioConfig(region_name='us-east-1') session.set_default_client_config(config) async with session.create_client('sts', config=other_config): # Grab the client config used in creating the client used_client_config = ( client_creator.return_value.create_client.call_args[1][ 'client_config' ] ) # Check that the client configs were merged assert used_client_config.region_name == 'us-east-1' # Make sure that the client config used is not the default client # config or the one passed in. It should be a new config. assert used_client_config is not config assert used_client_config is not other_config async def test_create_client_with_region(self, session): async with session.create_client('ec2', 'us-west-2') as ec2_client: assert ec2_client.meta.region_name == 'us-west-2' async def test_create_client_with_region_and_client_config(self, session): config = aiobotocore.config.AioConfig() # Use a client config with no region configured. async with session.create_client( 'ec2', region_name='us-west-2', config=config ) as ec2_client: assert ec2_client.meta.region_name == 'us-west-2' # If the region name is changed, it should not change the # region of the client config.region_name = 'us-east-1' assert ec2_client.meta.region_name == 'us-west-2' # Now make a new client with the updated client config. async with session.create_client('ec2', config=config) as ec2_client: assert ec2_client.meta.region_name == 'us-east-1' async def test_create_client_no_region_and_no_client_config(self, session): async with session.create_client('ec2') as ec2_client: assert ec2_client.meta.region_name == 'us-west-11' @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_ca_bundle_from_config( self, client_creator, environ, session ): with temporary_file('w') as f: del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write('foo_ca_bundle=config-certs.pem\n') f.flush() async with session.create_client('ec2', 'us-west-2'): call_kwargs = ( client_creator.return_value.create_client.call_args[1] ) assert call_kwargs['verify'] == 'config-certs.pem' @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_ca_bundle_from_env_var( self, client_creator, environ, session ): environ['FOO_AWS_CA_BUNDLE'] = 'env-certs.pem' async with session.create_client('ec2', 'us-west-2'): call_kwargs = client_creator.return_value.create_client.call_args[ 1 ] assert call_kwargs['verify'] == 'env-certs.pem' @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_verify_param( self, client_creator, session ): async with session.create_client( 'ec2', 'us-west-2', verify='verify-certs.pem' ): call_kwargs = client_creator.return_value.create_client.call_args[ 1 ] assert call_kwargs['verify'] == 'verify-certs.pem' @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_verify_param_overrides_all( self, client_creator, environ, session ): with temporary_file('w') as f: # Set the ca cert using the config file del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write('foo_ca_bundle=config-certs.pem\n') f.flush() # Set the ca cert with an environment variable environ['FOO_AWS_CA_BUNDLE'] = 'env-certs.pem' # Set the ca cert using the verify parameter async with session.create_client( 'ec2', 'us-west-2', verify='verify-certs.pem' ): call_kwargs = ( client_creator.return_value.create_client.call_args[1] ) # The verify parameter should override all the other # configurations assert call_kwargs['verify'] == 'verify-certs.pem' @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_use_no_api_version_by_default( self, client_creator, session ): async with session.create_client('myservice', 'us-west-2'): call_kwargs = client_creator.return_value.create_client.call_args[ 1 ] assert call_kwargs['api_version'] is None @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_uses_api_version_from_config( self, client_creator, environ, session ): config_api_version = '2012-01-01' with temporary_file('w') as f: del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write( 'foo_api_versions =\n' f' myservice = {config_api_version}\n' ) # fmt: skip f.flush() async with session.create_client('myservice', 'us-west-2'): call_kwargs = ( client_creator.return_value.create_client.call_args[1] ) assert call_kwargs['api_version'] == config_api_version @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_can_specify_multiple_versions_from_config( self, client_creator, environ, session ): config_api_version = '2012-01-01' second_config_api_version = '2013-01-01' with temporary_file('w') as f: del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write( f'foo_api_versions =\n' f' myservice = {config_api_version}\n' f' myservice2 = {second_config_api_version}\n' ) f.flush() async with session.create_client('myservice', 'us-west-2'): call_kwargs = ( client_creator.return_value.create_client.call_args[1] ) assert call_kwargs['api_version'] == config_api_version async with session.create_client('myservice2', 'us-west-2'): call_kwargs = ( client_creator.return_value.create_client.call_args[1] ) assert call_kwargs['api_version'] == second_config_api_version @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_param_api_version_overrides_config_value( self, client_creator, environ, session ): config_api_version = '2012-01-01' override_api_version = '2014-01-01' with temporary_file('w') as f: del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write( 'foo_api_versions =\n' f' myservice = {config_api_version}\n' ) # fmt: skip f.flush() async with session.create_client( 'myservice', 'us-west-2', api_version=override_api_version ): call_kwargs = ( client_creator.return_value.create_client.call_args[1] ) assert call_kwargs['api_version'] == override_api_version @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_credentials( self, client_creator, session ): async with session.create_client( 'sts', 'us-west-2', aws_access_key_id='foo', aws_secret_access_key='bar', aws_session_token='baz', aws_account_id='bin', ): credentials = ( client_creator.return_value.create_client.call_args.kwargs[ 'credentials' ] ) assert credentials.access_key == 'foo' assert credentials.secret_key == 'bar' assert credentials.token == 'baz' assert credentials.account_id == 'bin' @mock.patch('aiobotocore.session.AioClientCreator', autospec=True) async def test_create_client_with_ignored_credentials( self, client_creator, session, caplog: LogCaptureFixture ): caplog.set_level(logging.DEBUG, 'botocore.session') async with session.create_client( 'sts', 'us-west-2', aws_account_id='foo', ): credentials = ( client_creator.return_value.create_client.call_args.kwargs[ 'credentials' ] ) assert ( 'Ignoring the following credential-related values' in caplog.text ) assert 'aws_account_id' in caplog.text assert credentials.account_id is None class TestClientMonitoring(BaseSessionTest): async def assert_created_client_is_monitored(self, session): with mock.patch( 'botocore.monitoring.Monitor', spec=True ) as mock_monitor: async with session.create_client('ec2', 'us-west-2') as client: mock_monitor.return_value.register.assert_called_with( client.meta.events ) async def assert_monitoring_host_and_port(self, session, host, port): with mock.patch( 'botocore.monitoring.SocketPublisher', spec=True ) as mock_publisher: async with session.create_client('ec2', 'us-west-2'): assert mock_publisher.call_count == 1 _, args, kwargs = mock_publisher.mock_calls[0] assert kwargs.get('host') == host assert kwargs.get('port') == port async def assert_created_client_is_not_monitored(self, session): with mock.patch( 'botocore.session.monitoring.Monitor', spec=True ) as mock_monitor: async with session.create_client('ec2', 'us-west-2'): mock_monitor.return_value.register.assert_not_called() async def test_with_csm_enabled_from_config(self, environ, session): with temporary_file('w') as f: del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write('csm_enabled=true\n') f.flush() await self.assert_created_client_is_monitored(session) async def test_with_csm_enabled_from_env(self, environ, session): environ['AWS_CSM_ENABLED'] = 'true' await self.assert_created_client_is_monitored(session) async def test_with_csm_host(self, environ, session): custom_host = '10.13.37.1' environ['AWS_CSM_ENABLED'] = 'true' environ['AWS_CSM_HOST'] = custom_host await self.assert_monitoring_host_and_port(session, custom_host, 31000) async def test_with_csm_port(self, environ, session): custom_port = '1234' environ['AWS_CSM_ENABLED'] = 'true' environ['AWS_CSM_PORT'] = custom_port await self.assert_monitoring_host_and_port( session, '127.0.0.1', int(custom_port), ) async def test_with_csm_disabled_from_config(self, environ, session): with temporary_file('w') as f: del environ['FOO_PROFILE'] environ['FOO_CONFIG_FILE'] = f.name f.write('[default]\n') f.write('csm_enabled=false\n') f.flush() await self.assert_created_client_is_not_monitored(session) async def test_with_csm_disabled_from_env(self, environ, session): environ['AWS_CSM_ENABLED'] = 'false' await self.assert_created_client_is_not_monitored(session) async def test_csm_not_configured(self, session): await self.assert_created_client_is_not_monitored(session) aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_signers.py000066400000000000000000000324161510465277300264200ustar00rootroot00000000000000# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import datetime from datetime import timezone from unittest import mock import botocore.auth import pytest from botocore.awsrequest import AWSRequest from botocore.exceptions import ( NoRegionError, ParamValidationError, UnknownClientMethodError, UnknownSignatureVersionError, ) from botocore.model import ServiceId import aiobotocore.credentials import aiobotocore.session import aiobotocore.signers from tests.botocore_tests import assert_url_equal DATE = datetime.datetime(2024, 11, 7, 17, 39, 33, tzinfo=timezone.utc) @pytest.mark.parametrize( 'aws_auth', [{'aws_secret_access_key': 'skid', 'aws_access_key_id': 'akid'}], ) async def test_signers_generate_db_auth_token(rds_client): hostname = 'prod-instance.us-east-1.rds.amazonaws.com' port = 3306 username = 'someusername' clock = datetime.datetime(2016, 11, 7, 17, 39, 33, tzinfo=timezone.utc) with mock.patch('datetime.datetime') as dt: dt.now.return_value = clock result = await aiobotocore.signers.generate_db_auth_token( rds_client, hostname, port, username ) result2 = await rds_client.generate_db_auth_token( hostname, port, username ) expected_result = ( 'prod-instance.us-east-1.rds.amazonaws.com:3306/?Action=connect' '&DBUser=someusername&X-Amz-Algorithm=AWS4-HMAC-SHA256' '&X-Amz-Date=20161107T173933Z&X-Amz-SignedHeaders=host' '&X-Amz-Expires=900&X-Amz-Credential=akid%2F20161107%2F' 'us-east-1%2Frds-db%2Faws4_request&X-Amz-Signature' '=d1138cdbc0ca63eec012ec0fc6c2267e03642168f5884a7795320d4c18374c61' ) assert_url_equal('http://' + result, 'http://' + expected_result) assert result2 == result class TestDSQLGenerateDBAuthToken: @pytest.fixture(scope="session") def hostname(self): return 'test.dsql.us-east-1.on.aws' @pytest.fixture(scope="session") def action(self): return 'DbConnect' @pytest.fixture async def client(self, session): async with session.create_client( 'dsql', region_name='us-east-1', aws_access_key_id='ACCESS_KEY', aws_secret_access_key='SECRET_KEY', aws_session_token="SESSION_TOKEN", ) as client: yield client async def test_dsql_generate_db_auth_token( self, client, hostname, action, time_machine ): time_machine.move_to(DATE, tick=False) result = await aiobotocore.signers._dsql_generate_db_auth_token( client, hostname, action ) expected_result = ( 'test.dsql.us-east-1.on.aws/?Action=DbConnect' '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=' 'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request' '&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host' '&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature=' '57fe03e060348aaa21405c239bf02572bbc911076e94dcd65c12ae569dd8fcf4' ) # A scheme needs to be appended to the beginning or urlsplit may fail # on certain systems. assert_url_equal('https://' + result, 'https://' + expected_result) async def test_dsql_generate_db_connect_auth_token( self, client, hostname, time_machine ): time_machine.move_to(DATE, tick=False) result = await aiobotocore.signers.dsql_generate_db_connect_auth_token( client, hostname ) expected_result = ( 'test.dsql.us-east-1.on.aws/?Action=DbConnect' '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=' 'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request' '&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host' '&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature=' '57fe03e060348aaa21405c239bf02572bbc911076e94dcd65c12ae569dd8fcf4' ) # A scheme needs to be appended to the beginning or urlsplit may fail # on certain systems. assert_url_equal('https://' + result, 'https://' + expected_result) async def test_dsql_generate_db_connect_admin_auth_token( self, client, hostname, time_machine ): time_machine.move_to(DATE, tick=False) result = await aiobotocore.signers.dsql_generate_db_connect_admin_auth_token( client, hostname ) expected_result = ( 'test.dsql.us-east-1.on.aws/?Action=DbConnectAdmin' '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=' 'ACCESS_KEY%2F20241107%2Fus-east-1%2Fdsql%2Faws4_request' '&X-Amz-Date=20241107T173933Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host' '&X-Amz-Security-Token=SESSION_TOKEN&X-Amz-Signature=' '5ac084bc7cabccc19a52a5d1b5c24b50d3ce143f43b659bd484c91aaf555e190' ) # A scheme needs to be appended to the beginning or urlsplit may fail # on certain systems. assert_url_equal('https://' + result, 'https://' + expected_result) async def test_dsql_generate_db_auth_token_invalid_action( self, client, hostname ): with pytest.raises(ParamValidationError): await aiobotocore.signers._dsql_generate_db_auth_token( client, hostname, "FooBar" ) # From class TestSigner @pytest.fixture async def base_signer_setup() -> dict: emitter = mock.AsyncMock() emitter.emit_until_response.return_value = (None, None) credentials = aiobotocore.credentials.AioCredentials('key', 'secret') signer = aiobotocore.signers.AioRequestSigner( ServiceId('service_name'), 'region_name', 'signing_name', 'v4', credentials, emitter, ) return { 'credentials': credentials, 'emitter': emitter, 'signer': signer, 'fixed_credentials': await credentials.get_frozen_credentials(), 'request': AWSRequest(), } @pytest.fixture async def base_signer_setup_s3v4() -> dict: emitter = mock.AsyncMock() emitter.emit_until_response.return_value = (None, None) credentials = aiobotocore.credentials.AioCredentials('key', 'secret') request_signer = aiobotocore.signers.AioRequestSigner( ServiceId('service_name'), 'region_name', 'signing_name', 's3v4', credentials, emitter, ) signer = aiobotocore.signers.AioS3PostPresigner(request_signer) return { 'credentials': credentials, 'emitter': emitter, 'signer': signer, 'fixed_credentials': await credentials.get_frozen_credentials(), 'request': AWSRequest(), } # From class TestGenerateUrl async def test_signers_generate_presigned_urls(): with mock.patch( 'aiobotocore.signers.AioRequestSigner.generate_presigned_url' ) as cls_gen_presigned_url_mock: session = aiobotocore.session.get_session() async with session.create_client( 's3', region_name='us-east-1', aws_access_key_id='lalala', aws_secret_access_key='lalala', aws_session_token='lalala', ) as client: # Uses HEAD as it covers more lines :) await client.generate_presigned_url( 'get_object', Params={'Bucket': 'mybucket', 'Key': 'mykey'}, HttpMethod='HEAD', ) ref_request_dict = { 'body': b'', 'url': 'https://mybucket.s3.amazonaws.com/mykey', 'headers': {}, 'query_string': {}, 'url_path': '/mykey', 'method': 'HEAD', 'context': mock.ANY, 'auth_path': '/mybucket/mykey', } cls_gen_presigned_url_mock.assert_called_with( request_dict=ref_request_dict, expires_in=3600, operation_name='GetObject', ) cls_gen_presigned_url_mock.reset_mock() with pytest.raises(UnknownClientMethodError): await client.generate_presigned_url('lalala') async def test_signers_generate_presigned_post(): with mock.patch( 'aiobotocore.signers.AioS3PostPresigner.generate_presigned_post' ) as cls_gen_presigned_url_mock: session = aiobotocore.session.get_session() async with session.create_client( 's3', region_name='us-east-1', aws_access_key_id='lalala', aws_secret_access_key='lalala', aws_session_token='lalala', ) as client: await client.generate_presigned_post( 'somebucket', 'someprefix/key' ) cls_gen_presigned_url_mock.assert_called_once() cls_gen_presigned_url_mock.reset_mock() await client.generate_presigned_post( 'somebucket', 'someprefix/${filename}', {'some': 'fields'}, [{'acl': 'public-read'}], ) cls_gen_presigned_url_mock.assert_called_once() cls_gen_presigned_url_mock.reset_mock() with pytest.raises(UnknownClientMethodError): await client.generate_presigned_url('lalala') async def test_testsigner_get_auth(base_signer_setup: dict): auth_cls = mock.Mock() with mock.patch.dict(botocore.auth.AUTH_TYPE_MAPS, {'v4': auth_cls}): signer = base_signer_setup['signer'] auth = await signer.get_auth('service_name', 'region_name') assert auth_cls.return_value is auth auth_cls.assert_called_with( credentials=base_signer_setup['fixed_credentials'], service_name='service_name', region_name='region_name', ) async def test_testsigner_region_required_for_sig4(base_signer_setup: dict): signer = aiobotocore.signers.AioRequestSigner( ServiceId('service_name'), None, 'signing_name', 'v4', base_signer_setup['credentials'], base_signer_setup['emitter'], ) with pytest.raises(NoRegionError): await signer.sign('operation_name', base_signer_setup['request']) async def test_testsigner_custom_sign_version(base_signer_setup: dict): signer = base_signer_setup['signer'] with pytest.raises(UnknownSignatureVersionError): await signer.get_auth( 'service_name', 'region_name', signature_version='bad' ) async def test_testsigner_choose_signer_override(base_signer_setup: dict): auth_cls = mock.Mock() auth_cls.REQUIRES_REGION = False base_signer_setup['emitter'].emit_until_response.return_value = ( None, 'custom', ) with mock.patch.dict(botocore.auth.AUTH_TYPE_MAPS, {'custom': auth_cls}): signer = base_signer_setup['signer'] request = base_signer_setup['request'] await signer.sign('operation_name', request) fixed_credentials = base_signer_setup['fixed_credentials'] auth_cls.assert_called_with(credentials=fixed_credentials) auth_cls.return_value.add_auth.assert_called_with(request) async def test_testsigner_generate_presigned_url(base_signer_setup: dict): auth_cls = mock.Mock() auth_cls.REQUIRES_REGION = True request_dict = { 'headers': {}, 'url': 'https://foo.com', 'body': b'', 'url_path': '/', 'method': 'GET', 'context': {}, } with mock.patch.dict(botocore.auth.AUTH_TYPE_MAPS, {'v4-query': auth_cls}): signer = base_signer_setup['signer'] presigned_url = await signer.generate_presigned_url( request_dict, operation_name='operation_name' ) auth_cls.assert_called_with( credentials=base_signer_setup['fixed_credentials'], region_name='region_name', service_name='signing_name', expires=3600, ) assert presigned_url == 'https://foo.com' # From class TestGeneratePresignedPost async def test_testsigner_generate_presigned_post( base_signer_setup_s3v4: dict, ): auth_cls = mock.Mock() auth_cls.REQUIRES_REGION = True request_dict = { 'headers': {}, 'url': 'https://s3.amazonaws.com/mybucket', 'body': b'', 'url_path': '/', 'method': 'POST', 'context': {}, } with mock.patch.dict( botocore.auth.AUTH_TYPE_MAPS, {'s3v4-presign-post': auth_cls} ): signer = base_signer_setup_s3v4['signer'] presigned_url = await signer.generate_presigned_post( request_dict, conditions=[{'acl': 'public-read'}] ) auth_cls.assert_called_with( credentials=base_signer_setup_s3v4['fixed_credentials'], region_name='region_name', service_name='signing_name', ) assert presigned_url['url'] == 'https://s3.amazonaws.com/mybucket' aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_tokens.py000066400000000000000000000277471510465277300262640ustar00rootroot00000000000000# Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from copy import deepcopy from unittest import mock import dateutil.parser import pytest from botocore.exceptions import ( InvalidConfigError, SSOTokenLoadError, TokenRetrievalError, ) from aiobotocore.session import AioSession from aiobotocore.tokens import AioSSOTokenProvider def parametrize(cases): return pytest.mark.parametrize( "test_case", cases, ids=[c["documentation"] for c in cases], ) sso_provider_resolution_cases = [ { "documentation": "Full valid profile", "config": { "profiles": {"test": {"sso_session": "admin"}}, "sso_sessions": { "admin": { "sso_region": "us-east-1", "sso_start_url": "https://d-abc123.awsapps.com/start", } }, }, "resolves": True, }, { "documentation": "Non-SSO profiles are skipped", "config": {"profiles": {"test": {"region": "us-west-2"}}}, "resolves": False, }, { "documentation": "Only start URL is invalid", "config": { "profiles": {"test": {"sso_session": "admin"}}, "sso_sessions": { "admin": { "sso_start_url": "https://d-abc123.awsapps.com/start" } }, }, "resolves": False, "expectedException": InvalidConfigError, }, { "documentation": "Only sso_region is invalid", "config": { "profiles": {"test": {"sso_session": "admin"}}, "sso_sessions": {"admin": {"sso_region": "us-east-1"}}, }, "resolves": False, "expectedException": InvalidConfigError, }, { "documentation": "Specified sso-session must exist", "config": { "profiles": {"test": {"sso_session": "dev"}}, "sso_sessions": {"admin": {"sso_region": "us-east-1"}}, }, "resolves": False, "expectedException": InvalidConfigError, }, { "documentation": "The sso_session must be specified", "config": { "profiles": {"test": {"region": "us-west-2"}}, "sso_sessions": { "admin": { "sso_region": "us-east-1", "sso_start_url": "https://d-abc123.awsapps.com/start", } }, }, "resolves": False, }, ] def _create_mock_session(config): mock_session = mock.Mock(spec=AioSession) mock_session.get_config_variable.return_value = "test" mock_session.full_config = config return mock_session def _run_token_provider_test_case(provider, test_case): expected_exception = test_case.get("expectedException") if expected_exception is not None: with pytest.raises(expected_exception): auth_token = provider.load_token() return auth_token = provider.load_token() if test_case["resolves"]: assert auth_token is not None else: assert auth_token is None @parametrize(sso_provider_resolution_cases) async def test_sso_token_provider_resolution(test_case): mock_session = _create_mock_session(test_case["config"]) resolver = AioSSOTokenProvider(mock_session) _run_token_provider_test_case(resolver, test_case) @parametrize(sso_provider_resolution_cases) async def test_sso_token_provider_profile_name_overrides_session_profile( test_case, ): mock_session = _create_mock_session(test_case["config"]) mock_session.get_config_variable.return_value = "default" resolver = AioSSOTokenProvider(mock_session, profile_name='test') _run_token_provider_test_case(resolver, test_case) sso_provider_refresh_cases = [ { "documentation": "Valid token with all fields", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": { "startUrl": "https://d-123.awsapps.com/start", "region": "us-west-2", "accessToken": "cachedtoken", "expiresAt": "2021-12-25T21:30:00Z", "clientId": "clientid", "clientSecret": "YSBzZWNyZXQ=", "registrationExpiresAt": "2022-12-25T13:30:00Z", "refreshToken": "cachedrefreshtoken", }, "expectedToken": { "token": "cachedtoken", "expiration": "2021-12-25T21:30:00Z", }, }, { "documentation": "Minimal valid cached token", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": { "accessToken": "cachedtoken", "expiresAt": "2021-12-25T21:30:00Z", }, "expectedToken": { "token": "cachedtoken", "expiration": "2021-12-25T21:30:00Z", }, }, { "documentation": "Minimal expired cached token", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": { "accessToken": "cachedtoken", "expiresAt": "2021-12-25T13:00:00Z", }, "expectedException": TokenRetrievalError, }, { "documentation": "Token missing the expiresAt field", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": {"accessToken": "cachedtoken"}, "expectedException": SSOTokenLoadError, }, { "documentation": "Token missing the accessToken field", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": {"expiresAt": "2021-12-25T13:00:00Z"}, "expectedException": SSOTokenLoadError, }, { "documentation": "Expired token refresh with refresh token", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": { "startUrl": "https://d-123.awsapps.com/start", "region": "us-west-2", "accessToken": "cachedtoken", "expiresAt": "2021-12-25T13:00:00Z", "clientId": "clientid", "clientSecret": "YSBzZWNyZXQ=", "registrationExpiresAt": "2022-12-25T13:30:00Z", "refreshToken": "cachedrefreshtoken", }, "refreshResponse": { "tokenType": "Bearer", "accessToken": "newtoken", "expiresIn": 28800, "refreshToken": "newrefreshtoken", }, "expectedTokenWriteback": { "startUrl": "https://d-123.awsapps.com/start", "region": "us-west-2", "accessToken": "newtoken", "expiresAt": "2021-12-25T21:30:00Z", "clientId": "clientid", "clientSecret": "YSBzZWNyZXQ=", "registrationExpiresAt": "2022-12-25T13:30:00Z", "refreshToken": "newrefreshtoken", }, "expectedToken": { "token": "newtoken", "expiration": "2021-12-25T21:30:00Z", }, }, { "documentation": "Expired token refresh without new refresh token", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": { "startUrl": "https://d-123.awsapps.com/start", "region": "us-west-2", "accessToken": "cachedtoken", "expiresAt": "2021-12-25T13:00:00Z", "clientId": "clientid", "clientSecret": "YSBzZWNyZXQ=", "registrationExpiresAt": "2022-12-25T13:30:00Z", "refreshToken": "cachedrefreshtoken", }, "refreshResponse": { "tokenType": "Bearer", "accessToken": "newtoken", "expiresIn": 28800, }, "expectedTokenWriteback": { "startUrl": "https://d-123.awsapps.com/start", "region": "us-west-2", "accessToken": "newtoken", "expiresAt": "2021-12-25T21:30:00Z", "clientId": "clientid", "clientSecret": "YSBzZWNyZXQ=", "registrationExpiresAt": "2022-12-25T13:30:00Z", }, "expectedToken": { "token": "newtoken", "expiration": "2021-12-25T21:30:00Z", }, }, { "documentation": "Expired token and expired client registration", "currentTime": "2021-12-25T13:30:00Z", "cachedToken": { "startUrl": "https://d-123.awsapps.com/start", "region": "us-west-2", "accessToken": "cachedtoken", "expiresAt": "2021-10-25T13:00:00Z", "clientId": "clientid", "clientSecret": "YSBzZWNyZXQ=", "registrationExpiresAt": "2021-11-25T13:30:00Z", "refreshToken": "cachedrefreshtoken", }, "expectedException": TokenRetrievalError, }, ] @parametrize(sso_provider_refresh_cases) async def test_sso_token_provider_refresh(test_case): config = { "profiles": {"test": {"sso_session": "admin"}}, "sso_sessions": { "admin": { "sso_region": "us-west-2", "sso_start_url": "https://d-123.awsapps.com/start", } }, } cache_key = "d033e22ae348aeb5660fc2140aec35850c4da997" token_cache = {} # deepcopy the test case so the test can be parametrized against the same # test case w/ aiohttp & httpx test_case = deepcopy(test_case) # Prepopulate the token cache cached_token = test_case.pop("cachedToken", None) if cached_token: token_cache[cache_key] = cached_token mock_session = _create_mock_session(config) mock_sso_oidc = mock.AsyncMock() mock_sso_oidc.__aenter__.return_value = mock_sso_oidc mock_sso_oidc.__aexit__.return_value = None mock_session.create_client.return_value = mock_sso_oidc refresh_response = test_case.pop("refreshResponse", None) mock_sso_oidc.create_token = mock.AsyncMock(return_value=refresh_response) current_time = dateutil.parser.parse(test_case.pop("currentTime")) def _time_fetcher(): return current_time resolver = AioSSOTokenProvider( mock_session, token_cache, time_fetcher=_time_fetcher, ) auth_token = resolver.load_token() actual_exception = None try: actual_token = await auth_token.get_frozen_token() except Exception as e: actual_exception = e expected_exception = test_case.pop("expectedException", None) if expected_exception is not None: assert isinstance(actual_exception, expected_exception) elif actual_exception is not None: raise actual_exception expected_token = test_case.pop("expectedToken", {}) raw_token = expected_token.get("token") if raw_token is not None: assert actual_token.token == raw_token raw_expiration = expected_token.get("expiration") if raw_expiration is not None: expected_expiration = dateutil.parser.parse(raw_expiration) assert actual_token.expiration == expected_expiration expected_token_write_back = test_case.pop("expectedTokenWriteback", None) if expected_token_write_back: mock_sso_oidc.create_token.assert_called_with( grantType="refresh_token", clientId=cached_token["clientId"], clientSecret=cached_token["clientSecret"], refreshToken=cached_token["refreshToken"], ) raw_expiration = expected_token_write_back["expiresAt"] # The in-memory cache doesn't serialize to JSON so expect a datetime expected_expiration = dateutil.parser.parse(raw_expiration) expected_token_write_back["expiresAt"] = expected_expiration assert expected_token_write_back == token_cache[cache_key] # Pop the documentation to ensure all test fields are handled test_case.pop("documentation") assert not test_case.keys(), "All fields of test case should be handled" aio-libs-aiobotocore-e39c8b3/tests/botocore_tests/unit/test_utils.py000066400000000000000000000457311510465277300261120ustar00rootroot00000000000000from __future__ import annotations import itertools import json import unittest from collections.abc import Iterator from contextlib import asynccontextmanager from typing import Union from unittest import mock import pytest from botocore.exceptions import ( ConnectionClosedError, ConnectTimeoutError, ReadTimeoutError, ) from botocore.utils import BadIMDSRequestError, MetadataRetrievalError from aiobotocore import utils from aiobotocore.awsrequest import AioAWSResponse from aiobotocore.utils import AioInstanceMetadataFetcher from tests.test_response import AsyncBytesIO # TypeAlias (requires typing_extensions or >=3.10 to annotate) Response = tuple[Union[str, object], int] # From class TestContainerMetadataFetcher def fake_aiohttp_session(responses: list[Response] | Response): """ Dodgy shim class """ if isinstance(responses, tuple): data: Iterator[Response] = itertools.cycle([responses]) else: data = iter(responses) class FakeAioHttpSession: @asynccontextmanager async def acquire(self): yield self class FakeResponse: def __init__(self, request, *args, **kwargs): self.request = request self.url = request.url self._body, self.status_code = next(data) self.content = self._content() self.text = self._text() if not isinstance(self._body, str): raise self._body async def _content(self): return self._body.encode('utf-8') async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): pass async def _text(self): return self._body async def json(self): return json.loads(self._body) def __init__(self, *args, **kwargs): pass async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): pass async def send(self, request): return self.FakeResponse(request) return FakeAioHttpSession() async def test_idmsfetcher_disabled(): env = {'AWS_EC2_METADATA_DISABLED': 'true'} fetcher = utils.AioIMDSFetcher(env=env) with pytest.raises(fetcher._RETRIES_EXCEEDED_ERROR_CLS): await fetcher._get_request('path', None) async def test_idmsfetcher_get_token_success(): session = fake_aiohttp_session( ('blah', 200), ) fetcher = utils.AioIMDSFetcher( num_attempts=2, session=session, user_agent='test' ) response = await fetcher._fetch_metadata_token() assert response == 'blah' async def test_idmsfetcher_get_token_not_found(): session = fake_aiohttp_session( ('blah', 404), ) fetcher = utils.AioIMDSFetcher( num_attempts=2, session=session, user_agent='test' ) response = await fetcher._fetch_metadata_token() assert response is None async def test_idmsfetcher_get_token_bad_request(): session = fake_aiohttp_session( ('blah', 400), ) fetcher = utils.AioIMDSFetcher( num_attempts=2, session=session, user_agent='test' ) with pytest.raises(BadIMDSRequestError): await fetcher._fetch_metadata_token() async def test_idmsfetcher_get_token_timeout(): session = fake_aiohttp_session( [ (ReadTimeoutError(endpoint_url='aaa'), 500), ] ) fetcher = utils.AioIMDSFetcher(num_attempts=2, session=session) response = await fetcher._fetch_metadata_token() assert response is None async def test_idmsfetcher_get_token_retry(): session = fake_aiohttp_session( [ ('blah', 500), ('blah', 500), ('token', 200), ] ) fetcher = utils.AioIMDSFetcher(num_attempts=3, session=session) response = await fetcher._fetch_metadata_token() assert response == 'token' async def test_idmsfetcher_retry(): session = fake_aiohttp_session( [ ('blah', 500), ('data', 200), ] ) fetcher = utils.AioIMDSFetcher( num_attempts=2, session=session, user_agent='test' ) response = await fetcher._get_request('path', None, 'some_token') assert await response.text == 'data' session = fake_aiohttp_session( [ ('blah', 500), ('data', 200), ] ) fetcher = utils.AioIMDSFetcher(num_attempts=1, session=session) with pytest.raises(fetcher._RETRIES_EXCEEDED_ERROR_CLS): await fetcher._get_request('path', None) async def test_idmsfetcher_timeout(): session = fake_aiohttp_session( [ (ReadTimeoutError(endpoint_url='url'), 500), ] ) fetcher = utils.AioIMDSFetcher(num_attempts=1, session=session) with pytest.raises(fetcher._RETRIES_EXCEEDED_ERROR_CLS): await fetcher._get_request('path', None) class TestInstanceMetadataFetcher(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): urllib3_session_send = 'aiobotocore.httpsession.AIOHTTPSession.send' self._urllib3_patch = mock.patch(urllib3_session_send) self._send = self._urllib3_patch.start() self._imds_responses = [] self._send.side_effect = self.get_imds_response self._role_name = 'role-name' self._creds = { 'AccessKeyId': 'spam', 'SecretAccessKey': 'eggs', 'Token': 'spam-token', 'Expiration': 'something', } self._expected_creds = { 'access_key': self._creds['AccessKeyId'], 'secret_key': self._creds['SecretAccessKey'], 'token': self._creds['Token'], 'expiry_time': self._creds['Expiration'], 'role_name': self._role_name, } async def asyncTearDown(self): self._urllib3_patch.stop() def add_imds_response(self, body, status_code=200): response = AioAWSResponse( url='http://169.254.169.254/', status_code=status_code, headers={}, raw=AsyncBytesIO(body), ) self._imds_responses.append(response) def add_get_role_name_imds_response(self, role_name=None): if role_name is None: role_name = self._role_name self.add_imds_response(body=role_name.encode('utf-8')) def add_get_credentials_imds_response(self, creds=None): if creds is None: creds = self._creds self.add_imds_response(body=json.dumps(creds).encode('utf-8')) def add_get_token_imds_response(self, token, status_code=200): self.add_imds_response( body=token.encode('utf-8'), status_code=status_code ) def add_metadata_token_not_supported_response(self): self.add_imds_response(b'', status_code=404) def add_imds_connection_error(self, exception): self._imds_responses.append(exception) def get_imds_response(self, *args, **kwargs): response = self._imds_responses.pop(0) if isinstance(response, Exception): raise response return response async def test_disabled_by_environment(self): env = {'AWS_EC2_METADATA_DISABLED': 'true'} fetcher = AioInstanceMetadataFetcher(env=env) result = await fetcher.retrieve_iam_role_credentials() self.assertEqual(result, {}) self._send.assert_not_called() async def test_disabled_by_environment_mixed_case(self): env = {'AWS_EC2_METADATA_DISABLED': 'tRuE'} fetcher = AioInstanceMetadataFetcher(env=env) result = await fetcher.retrieve_iam_role_credentials() self.assertEqual(result, {}) self._send.assert_not_called() async def test_disabling_env_var_not_true(self): url = 'https://example.com/' env = {'AWS_EC2_METADATA_DISABLED': 'false'} self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() fetcher = AioInstanceMetadataFetcher(base_url=url, env=env) result = await fetcher.retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_includes_user_agent_header(self): user_agent = 'my-user-agent' self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() self.assertEqual(self._send.call_count, 3) for call in self._send.calls: self.assertTrue(call[0][0].headers['User-Agent'], user_agent) async def test_non_200_response_for_role_name_is_retried(self): # Response for role name that have a non 200 status code should # be retried. self.add_get_token_imds_response(token='token') self.add_imds_response( status_code=429, body=b'{"message": "Slow down"}' ) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_http_connection_error_for_role_name_is_retried(self): # Connection related errors should be retried self.add_get_token_imds_response(token='token') self.add_imds_connection_error(ConnectionClosedError(endpoint_url='')) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_empty_response_for_role_name_is_retried(self): # Response for role name that have a non 200 status code should # be retried. self.add_get_token_imds_response(token='token') self.add_imds_response(body=b'') self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_non_200_response_is_retried(self): self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() # Response for creds that has a 200 status code but has an empty # body should be retried. self.add_imds_response( status_code=429, body=b'{"message": "Slow down"}' ) self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_http_connection_errors_is_retried(self): self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() # Connection related errors should be retried self.add_imds_connection_error(ConnectionClosedError(endpoint_url='')) self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_empty_response_is_retried(self): self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() # Response for creds that has a 200 status code but is empty. # This should be retried. self.add_imds_response(body=b'') self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_invalid_json_is_retried(self): self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() # Response for creds that has a 200 status code but is invalid JSON. # This should be retried. self.add_imds_response(body=b'{"AccessKey":') self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( num_attempts=2 ).retrieve_iam_role_credentials() self.assertEqual(result, self._expected_creds) async def test_exhaust_retries_on_role_name_request(self): self.add_get_token_imds_response(token='token') self.add_imds_response(status_code=400, body=b'') result = await AioInstanceMetadataFetcher( num_attempts=1 ).retrieve_iam_role_credentials() self.assertEqual(result, {}) async def test_exhaust_retries_on_credentials_request(self): self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() self.add_imds_response(status_code=400, body=b'') result = await AioInstanceMetadataFetcher( num_attempts=1 ).retrieve_iam_role_credentials() self.assertEqual(result, {}) async def test_missing_fields_in_credentials_response(self): self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() # Response for creds that has a 200 status code and a JSON body # representing an error. We do not necessarily want to retry this. self.add_imds_response( body=b'{"Code":"AssumeRoleUnauthorizedAccess","Message":"error"}' ) result = ( await AioInstanceMetadataFetcher().retrieve_iam_role_credentials() ) self.assertEqual(result, {}) async def test_token_is_included(self): user_agent = 'my-user-agent' self.add_get_token_imds_response(token='token') self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() # Check that subsequent calls after getting the token include the token. self.assertEqual(self._send.call_count, 3) for call in self._send.call_args_list[1:]: self.assertEqual( call[0][0].headers['x-aws-ec2-metadata-token'], 'token' ) self.assertEqual(result, self._expected_creds) async def test_metadata_token_not_supported_404(self): user_agent = 'my-user-agent' self.add_imds_response(b'', status_code=404) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() for call in self._send.call_args_list[1:]: self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) self.assertEqual(result, self._expected_creds) async def test_metadata_token_not_supported_403(self): user_agent = 'my-user-agent' self.add_imds_response(b'', status_code=403) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() for call in self._send.call_args_list[1:]: self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) self.assertEqual(result, self._expected_creds) async def test_metadata_token_not_supported_405(self): user_agent = 'my-user-agent' self.add_imds_response(b'', status_code=405) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() for call in self._send.call_args_list[1:]: self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) self.assertEqual(result, self._expected_creds) async def test_metadata_token_not_supported_timeout(self): user_agent = 'my-user-agent' self.add_imds_connection_error(ReadTimeoutError(endpoint_url='url')) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() for call in self._send.call_args_list[1:]: self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) self.assertEqual(result, self._expected_creds) async def test_token_not_supported_exhaust_retries(self): user_agent = 'my-user-agent' self.add_imds_connection_error(ConnectTimeoutError(endpoint_url='url')) self.add_get_role_name_imds_response() self.add_get_credentials_imds_response() result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() for call in self._send.call_args_list[1:]: self.assertNotIn('x-aws-ec2-metadata-token', call[0][0].headers) self.assertEqual(result, self._expected_creds) async def test_metadata_token_bad_request_yields_no_credentials(self): user_agent = 'my-user-agent' self.add_imds_response(b'', status_code=400) result = await AioInstanceMetadataFetcher( user_agent=user_agent ).retrieve_iam_role_credentials() self.assertEqual(result, {}) async def test_containermetadatafetcher_retrieve_url(): json_body = json.dumps( { "AccessKeyId": "a", "SecretAccessKey": "b", "Token": "c", "Expiration": "d", } ) sleep = mock.AsyncMock() http = fake_aiohttp_session((json_body, 200)) fetcher = utils.AioContainerMetadataFetcher(http, sleep) resp = await fetcher.retrieve_uri('/foo?id=1') assert resp['AccessKeyId'] == 'a' assert resp['SecretAccessKey'] == 'b' assert resp['Token'] == 'c' assert resp['Expiration'] == 'd' resp = await fetcher.retrieve_full_uri( 'http://localhost/foo?id=1', {'extra': 'header'} ) assert resp['AccessKeyId'] == 'a' assert resp['SecretAccessKey'] == 'b' assert resp['Token'] == 'c' assert resp['Expiration'] == 'd' async def test_containermetadatafetcher_retrieve_url_bad_status(): json_body = "not json" sleep = mock.AsyncMock() http = fake_aiohttp_session((json_body, 500)) fetcher = utils.AioContainerMetadataFetcher(http, sleep) with pytest.raises(MetadataRetrievalError): await fetcher.retrieve_uri('/foo?id=1') async def test_containermetadatafetcher_retrieve_url_not_json(): json_body = "not json" sleep = mock.AsyncMock() http = fake_aiohttp_session((json_body, 200)) fetcher = utils.AioContainerMetadataFetcher(http, sleep) with pytest.raises(MetadataRetrievalError): await fetcher.retrieve_uri('/foo?id=1') aio-libs-aiobotocore-e39c8b3/tests/conftest.py000066400000000000000000000504021510465277300215120ustar00rootroot00000000000000from __future__ import annotations import multiprocessing import os import random import re import string import tempfile from contextlib import AsyncExitStack, ExitStack from itertools import chain from typing import TYPE_CHECKING, Literal from unittest.mock import patch # Third Party import aiohttp import anyio import pytest try: import httpx except ImportError: http = None import aiobotocore.session from aiobotocore.config import AioConfig from aiobotocore.httpsession import AIOHTTPSession from aiobotocore.httpxsession import HttpxSession if TYPE_CHECKING: from _pytest.nodes import Node host = '127.0.0.1' _PYCHARM_HOSTED = os.environ.get('PYCHARM_HOSTED') == '1' @pytest.fixture(scope="session", autouse=True) def always_spawn(): # enforce multiprocessing start method `spawn` to prevent deadlocks in the child multiprocessing.set_start_method("spawn", force=True) @pytest.fixture( scope="session", params=[True, False], ids=['debug[true]', 'debug[false]'] ) def debug(request): return request.param @pytest.fixture def anyio_backend(): return 'asyncio' def random_bucketname(): # 63 is the max bucket length. return random_name() def random_tablename(): return random_name() def random_name(): """Return a string with presumably unique contents The string contains only symbols allowed for s3 buckets (alphanumeric, dot and hyphen). """ return ''.join(random.sample(string.ascii_lowercase, k=26)) def assert_status_code(response, status_code): assert response['ResponseMetadata']['HTTPStatusCode'] == status_code async def assert_num_uploads_found( s3_client, bucket_name, operation, num_uploads, *, max_items=None, num_attempts=5, ): paginator = s3_client.get_paginator(operation) for _ in range(num_attempts): pages = paginator.paginate( Bucket=bucket_name, PaginationConfig={'MaxItems': max_items} ) responses = [] async for page in pages: responses.append(page) # It sometimes takes a while for all the uploads to show up, # especially if the upload was just created. If we don't # see the expected amount, we retry up to num_attempts time # before failing. amount_seen = len(responses[0]['Uploads']) if amount_seen == num_uploads: # Test passed. return else: # Sleep and try again. await anyio.sleep(2) pytest.fail( f"Expected to see {num_uploads} uploads, instead saw: {amount_seen}" ) # Used by test_fail_proxy_request as it will fail during setup, so needs to # be skipped before `skipif` would be able to skip the test. @pytest.fixture def skip_httpx(current_http_backend: str) -> None: if current_http_backend == 'httpx': pytest.skip('proxy support not implemented for httpx') @pytest.fixture def aa_fail_proxy_config(monkeypatch): # NOTE: name of this fixture must be alphabetically first to run first monkeypatch.setenv('HTTP_PROXY', f'http://{host}:54321') monkeypatch.setenv('HTTPS_PROXY', f'http://{host}:54321') @pytest.fixture def aa_succeed_proxy_config(monkeypatch): # NOTE: name of this fixture must be alphabetically first to run first monkeypatch.setenv('HTTP_PROXY', f'http://{host}:54321') monkeypatch.setenv('HTTPS_PROXY', f'http://{host}:54321') # this will cause us to skip proxying monkeypatch.setenv('NO_PROXY', 'amazonaws.com') @pytest.fixture def session() -> aiobotocore.session.AioSession: session = aiobotocore.session.AioSession() return session @pytest.fixture def region(): return 'us-east-1' @pytest.fixture def alternative_region(): return 'us-west-2' @pytest.fixture def signature_version(): return 'v4' @pytest.fixture def server_scheme(): return 'http' @pytest.fixture def s3_verify(): return None @pytest.fixture def current_http_backend(request) -> Literal['httpx', 'aiohttp']: for mark in request.node.iter_markers("config_kwargs"): assert len(mark.args) == 1 assert isinstance(mark.args[0], dict) http_session_cls = mark.args[0].get('http_session_cls') if http_session_cls is HttpxSession: return 'httpx' # since aiohttp is default we don't test explicitly setting it elif http_session_cls is AIOHTTPSession: # pragma: no cover return 'aiohttp' return 'aiohttp' def read_kwargs(node: Node) -> dict[str, object]: config_kwargs: dict[str, object] = {} for mark in node.iter_markers("config_kwargs"): assert not mark.kwargs, config_kwargs assert len(mark.args) == 1 assert isinstance(mark.args[0], dict) config_kwargs.update(mark.args[0]) return config_kwargs @pytest.fixture def config(request, region, signature_version): config_kwargs = read_kwargs(request.node) connect_timeout = read_timout = 5 if _PYCHARM_HOSTED: connect_timeout = read_timout = 180 return AioConfig( region_name=region, signature_version=signature_version, read_timeout=read_timout, connect_timeout=connect_timeout, **config_kwargs, ) @pytest.fixture def aws_auth(): return {'aws_secret_access_key': 'xxx', 'aws_access_key_id': 'xxx'} @pytest.fixture def mocking_test(): # change this flag for test with real aws # TODO: this should be merged with pytest.mark.moto return True @pytest.fixture def patch_attributes(request): """Call unittest.mock.patch on arguments passed through a pytest mark. This fixture looks at the @pytest.mark.patch_attributes mark. This mark is a list of arguments to be passed to unittest.mock.patch (see example below). This fixture returns the list of mock objects, one per element in the input list. Why do we need this? In some cases, we want to perform the patching before other fixtures are run. For instance, the `s3_client` fixture creates an aiobotocore client. During the client creation process, some event listeners are registered. When we want to patch the target of these event listeners, we must do so before the `s3_client` fixture is executed. Otherwise, the aiobotocore client will store references to the unpatched targets. In such situations, make sure that subsequent fixtures explicitly depends on `patch_attribute` to enforce the ordering between fixtures. Example: @pytest.mark.patch_attributes([ dict( target="aiobotocore.retries.adaptive.AsyncClientRateLimiter.on_sending_request", side_effect=aiobotocore.retries.adaptive.AsyncClientRateLimiter.on_sending_request, autospec=True ) ]) async def test_client_rate_limiter_called(s3_client, patch_attributes): await s3_client.get_object(Bucket="bucket", Key="key") # Just for illustration (this test doesn't pass). # mock_attributes is a list of 1 element, since we passed a list of 1 element # to the patch_attributes marker. mock_attributes[0].assert_called_once() """ marker = request.node.get_closest_marker("patch_attributes") if marker is None: yield else: with ExitStack() as stack: yield [ stack.enter_context(patch(**kwargs)) for kwargs in marker.args[0] ] @pytest.fixture async def s3_client( session, region, config, moto_server, mocking_test, s3_verify, patch_attributes, aws_auth, ): # This depends on mock_attributes because we may want to test event listeners. # See the documentation of `mock_attributes` for details. kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 's3', region_name=region, config=config, verify=s3_verify, **kw ) as client: yield client @pytest.fixture async def alternative_s3_client( session, alternative_region, signature_version, moto_server, mocking_test, aws_auth, request, ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} kwargs = read_kwargs(request.node) config = AioConfig( region_name=alternative_region, signature_version=signature_version, read_timeout=5, connect_timeout=5, **kwargs, ) async with session.create_client( 's3', region_name=alternative_region, config=config, **kw ) as client: yield client @pytest.fixture async def dynamodb_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'dynamodb', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def cloudformation_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'cloudformation', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def sns_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'sns', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def sqs_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'sqs', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def batch_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'batch', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def lambda_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'lambda', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def iam_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'iam', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def rds_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'rds', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def ec2_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'ec2', region_name=region, config=config, **kw ) as client: yield client @pytest.fixture async def kinesis_client( session, region, config, moto_server, mocking_test, aws_auth ): kw = {'endpoint_url': moto_server, **aws_auth} if mocking_test else {} async with session.create_client( 'kinesis', region_name=region, config=config, **kw ) as client: yield client async def recursive_delete(s3_client, bucket_name): # Recursively deletes a bucket and all of its contents. paginator = s3_client.get_paginator('list_object_versions') async for n in paginator.paginate(Bucket=bucket_name, Prefix=''): for obj in chain( n.get('Versions', []), n.get('DeleteMarkers', []), n.get('Contents', []), n.get('CommonPrefixes', []), ): kwargs = dict(Bucket=bucket_name, Key=obj['Key']) if 'VersionId' in obj: kwargs['VersionId'] = obj['VersionId'] resp = await s3_client.delete_object(**kwargs) assert_status_code(resp, 204) resp = await s3_client.delete_bucket(Bucket=bucket_name) assert_status_code(resp, 204) @pytest.fixture async def bucket_name(region, create_bucket): name = await create_bucket(region) yield name @pytest.fixture async def table_name(create_table): name = await create_table() yield name @pytest.fixture async def create_bucket(s3_client): _bucket_name = None async def _f(region_name, bucket_name=None): nonlocal _bucket_name if bucket_name is None: bucket_name = random_bucketname() _bucket_name = bucket_name bucket_kwargs = {'Bucket': bucket_name} if region_name != 'us-east-1': bucket_kwargs['CreateBucketConfiguration'] = { 'LocationConstraint': region_name, } response = await s3_client.create_bucket(**bucket_kwargs) assert_status_code(response, 200) await s3_client.put_bucket_versioning( Bucket=bucket_name, VersioningConfiguration={'Status': 'Enabled'} ) return bucket_name try: yield _f finally: await recursive_delete(s3_client, _bucket_name) @pytest.fixture async def create_stream(kinesis_client): _stream_name = None async def _f(stream_name=None, **kwargs): nonlocal _stream_name if stream_name is None: stream_name = random_name() _stream_name = stream_name stream_kwargs = { 'StreamName': _stream_name, **kwargs, } response = await kinesis_client.create_stream(**stream_kwargs) assert_status_code(response, 200) while ( describe_response := ( await kinesis_client.describe_stream( # noqa: E231, E999, E251, E501 StreamName=stream_name ) ) ) and describe_response['StreamDescription'][ 'StreamStatus' ] != 'ACTIVE': print("Waiting for stream creation") await anyio.sleep(1) return stream_name try: yield _f finally: await delete_stream(kinesis_client, _stream_name) async def delete_stream(kinesis_client, stream_name): response = await kinesis_client.delete_stream(StreamName=stream_name) assert_status_code(response, 200) @pytest.fixture async def create_table(dynamodb_client): _table_name = None async def _is_table_ready(table_name): response = await dynamodb_client.describe_table(TableName=table_name) return response['Table']['TableStatus'] == 'ACTIVE' async def _f(table_name=None): nonlocal _table_name if table_name is None: table_name = random_tablename() _table_name = table_name table_kwargs = { 'TableName': table_name, 'AttributeDefinitions': [ {'AttributeName': 'testKey', 'AttributeType': 'S'}, ], 'KeySchema': [ {'AttributeName': 'testKey', 'KeyType': 'HASH'}, ], 'ProvisionedThroughput': { 'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1, }, } response = await dynamodb_client.create_table(**table_kwargs) while not (await _is_table_ready(table_name)): pass assert_status_code(response, 200) return table_name try: yield _f finally: await delete_table(dynamodb_client, _table_name) async def delete_table(dynamodb_client, table_name): response = await dynamodb_client.delete_table(TableName=table_name) assert_status_code(response, 200) @pytest.fixture def tempdir(): with tempfile.TemporaryDirectory() as td: yield td @pytest.fixture def create_object(s3_client, bucket_name: str): async def _f(key_name: str, body='foo', **kwargs): r = await s3_client.put_object( Bucket=bucket_name, Key=key_name, Body=body ) assert_status_code(r, 200) return r return _f @pytest.fixture async def create_multipart_upload(request, s3_client, bucket_name): _key_name = None upload_id = None async def _f(key_name): nonlocal _key_name nonlocal upload_id _key_name = key_name parsed = await s3_client.create_multipart_upload( Bucket=bucket_name, Key=key_name ) upload_id = parsed['UploadId'] return upload_id yield _f await s3_client.abort_multipart_upload( UploadId=upload_id, Bucket=bucket_name, Key=_key_name ) @pytest.fixture async def aio_session(current_http_backend: Literal['httpx', 'aiohttp']): if current_http_backend == 'httpx': assert httpx is not None async with httpx.AsyncClient() as client: yield client elif current_http_backend == 'aiohttp': async with aiohttp.ClientSession() as session: yield session else: # pragma: no cover raise AssertionError("unknown http backend") def pytest_configure(): class AIOUtils: def __init__(self): self.assert_status_code = assert_status_code self.assert_num_uploads_found = assert_num_uploads_found pytest.aio = AIOUtils() @pytest.fixture def dynamodb_put_item(dynamodb_client, table_name): async def _f(key_string_value): response = await dynamodb_client.put_item( TableName=table_name, Item={'testKey': {'S': key_string_value}}, ) assert_status_code(response, 200) return _f @pytest.fixture async def topic_arn(region, create_topic, sns_client): return await create_topic() async def delete_topic(sns_client, topic_arn): response = await sns_client.delete_topic(TopicArn=topic_arn) assert_status_code(response, 200) @pytest.fixture async def create_topic(request, sns_client): _topic_arn = None async def _f(): nonlocal _topic_arn response = await sns_client.create_topic(Name=random_name()) _topic_arn = response['TopicArn'] assert_status_code(response, 200) return _topic_arn yield _f await delete_topic(sns_client, _topic_arn) @pytest.fixture async def sqs_queue_url(sqs_client): response = await sqs_client.create_queue(QueueName=random_name()) queue_url = response['QueueUrl'] assert_status_code(response, 200) try: yield queue_url finally: response = await sqs_client.delete_queue(QueueUrl=queue_url) assert_status_code(response, 200) @pytest.fixture async def exit_stack(): async with AsyncExitStack() as es: yield es def pytest_addoption(parser: pytest.Parser): parser.addoption( "--http-backend", default='aiohttp', choices=['aiohttp', 'httpx', 'all'], required=False, help='Specify http backend to run tests against.', ) def pytest_generate_tests(metafunc): """Parametrize all tests to run with both aiohttp and httpx as backend. This is not a super clean solution, as some tests will not differ at all with different http backends.""" metafunc.parametrize( '', [ pytest.param(id='aiohttp'), pytest.param( id='httpx', marks=pytest.mark.config_kwargs( {'http_session_cls': HttpxSession} ), ), ], ) def pytest_collection_modifyitems(config: pytest.Config, items): """Mark parametrized tests for skipping in case the corresponding backend is not enabled.""" http_backend = config.getoption("--http-backend") if http_backend == 'all': return if http_backend == 'aiohttp': ignore_backend = 'httpx' else: assert httpx is not None, ( "Cannot run httpx as backend if it's not installed." ) ignore_backend = 'aiohttp' backend_skip = pytest.mark.skip( reason='Selected not to run with --http-backend' ) for item in items: if re.match(rf'.*\[.*{ignore_backend}.*\]', item.name): item.add_marker(backend_skip) pytest_plugins = ['tests.mock_server'] aio-libs-aiobotocore-e39c8b3/tests/mock_server.py000066400000000000000000000070241510465277300222060ustar00rootroot00000000000000import asyncio import multiprocessing import socket # Third Party import aiohttp import aiohttp.web import pytest from aiohttp.web import StreamResponse from moto.server import ThreadedMotoServer _proxy_bypass = { "http": None, "https": None, } host = '127.0.0.1' def get_free_tcp_port(release_socket: bool = False): sckt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sckt.bind((host, 0)) addr, port = sckt.getsockname() if release_socket: sckt.close() return port return sckt, port # This runs in a subprocess for a variety of reasons # 1) early versions of python 3.5 did not correctly set one thread per run loop # 2) aiohttp uses get_event_loop instead of using the passed in run loop # 3) aiohttp shutdown can be hairy class AIOServer(multiprocessing.Process): """ This is a mock AWS service which will 5 seconds before returning a response to test socket timeouts. """ def __init__(self): super().__init__(target=self._run) self._loop = None self._port = get_free_tcp_port(True) self.endpoint_url = f'http://{host}:{self._port}' self.daemon = True # die when parent dies def _run(self): asyncio.set_event_loop(asyncio.new_event_loop()) app = aiohttp.web.Application() app.router.add_route('*', '/ok', self.ok) app.router.add_route('*', '/{anything:.*}', self.stream_handler) try: aiohttp.web.run_app( app, host=host, port=self._port, handle_signals=False ) except BaseException: pytest.fail('unable to start and connect to aiohttp server') raise async def __aenter__(self): self.start() await self._wait_until_up() return self async def __aexit__(self, exc_type, exc_val, exc_tb): try: self.terminate() except BaseException: pytest.fail("Unable to shut down server") raise @staticmethod async def ok(request): return aiohttp.web.Response() async def stream_handler(self, request): # Without the Content-Type, most (all?) browsers will not render # partially downloaded content. Note, the response type is # StreamResponse not Response. resp = StreamResponse( status=200, reason='OK', headers={'Content-Type': 'text/html'} ) await resp.prepare(request) await asyncio.sleep(5) await resp.drain() return resp async def _wait_until_up(self): async with aiohttp.ClientSession() as session: for i in range(0, 30): if self.exitcode is not None: pytest.fail('unable to start/connect to aiohttp server') return try: # we need to bypass the proxies due to monkey patches await session.get(self.endpoint_url + '/ok', timeout=0.5) return except (aiohttp.ClientConnectionError, asyncio.TimeoutError): await asyncio.sleep(0.5) except BaseException: pytest.fail('unable to start/connect to aiohttp server') raise pytest.fail('unable to start and connect to aiohttp server') @pytest.fixture async def moto_server(server_scheme): server = ThreadedMotoServer(port=0) try: server.start() host, port = server.get_host_and_port() yield f'http://{host}:{port}' finally: server.stop() aio-libs-aiobotocore-e39c8b3/tests/test_adaptive.py000066400000000000000000000173321510465277300225260ustar00rootroot00000000000000# The following tests are the adaptation of the unit tests for the original (sync) # ClientRateLimiter and TokenBucket in botocore. # see: https://github.com/boto/botocore: # `/tests/unit/retries/test_bucket.py` and `/tests/unit/retries/test_adaptive.py`. from unittest import mock import pytest from botocore.exceptions import CapacityNotAvailableError from botocore.retries import standard, throttling from aiobotocore.retries import adaptive, bucket class _SleepMethodCalled(Exception): """Raised to explicitly fail a test for calling the blocking `sleep` method.""" pass class FakeClock(bucket.Clock): def __init__(self, timestamp_sequences): self.timestamp_sequences = timestamp_sequences self.sleep_call_amounts = [] def sleep(self, amount): raise _SleepMethodCalled( "sleep method should never be called, non-blocking behavior expected" ) def current_time(self): return self.timestamp_sequences.pop(0) class TestAsyncClientRateLimiter: @pytest.fixture(autouse=True) def _setup(self): self.timestamp_sequences = [0] self.clock = FakeClock(self.timestamp_sequences) self.token_bucket = mock.Mock(spec=bucket.AsyncTokenBucket) self.rate_adjustor = mock.Mock(spec=throttling.CubicCalculator) self.rate_clocker = mock.Mock(spec=adaptive.RateClocker) self.throttling_detector = mock.Mock( spec=standard.ThrottlingErrorDetector ) def create_client_limiter(self): rate_limiter = adaptive.AsyncClientRateLimiter( rate_adjustor=self.rate_adjustor, rate_clocker=self.rate_clocker, token_bucket=self.token_bucket, throttling_detector=self.throttling_detector, clock=self.clock, ) return rate_limiter async def test_bucket_bucket_acquisition_only_if_enabled(self): rate_limiter = self.create_client_limiter() await rate_limiter.on_sending_request(request=mock.sentinel.request) assert not self.token_bucket.acquire.called async def test_token_bucket_enabled_on_throttling_error(self): rate_limiter = self.create_client_limiter() self.throttling_detector.is_throttling_error.return_value = True self.rate_clocker.record.return_value = 21 self.rate_adjustor.error_received.return_value = 17 await rate_limiter.on_receiving_response() # Now if we call on_receiving_response we should try to acquire # token. self.timestamp_sequences.append(1) await rate_limiter.on_sending_request(request=mock.sentinel.request) assert self.token_bucket.acquire.called async def test_max_rate_updated_on_success_response(self): rate_limiter = self.create_client_limiter() self.throttling_detector.is_throttling_error.return_value = False self.rate_adjustor.success_received.return_value = 20 self.rate_clocker.record.return_value = 21 await rate_limiter.on_receiving_response() self.token_bucket.set_max_rate.assert_called_with(20) async def test_max_rate_cant_exceed_20_percent_max(self): rate_limiter = self.create_client_limiter() self.throttling_detector.is_throttling_error.return_value = False # So if our actual measured sending rate is 20 TPS self.rate_clocker.record.return_value = 20 # But the rate adjustor is telling us to go up to 100 TPS self.rate_adjustor.success_received.return_value = 100 # The most we should go up is 2.0 * 20 await rate_limiter.on_receiving_response() self.token_bucket.set_max_rate.assert_called_with(2.0 * 20) class TestAsyncTokenBucket: @pytest.fixture(autouse=True) def _setup(self): self.timestamp_sequences = [0] self.clock = FakeClock(self.timestamp_sequences) def create_token_bucket(self, max_rate=10, min_rate=0.1): return bucket.AsyncTokenBucket( max_rate=max_rate, clock=self.clock, min_rate=min_rate ) async def test_can_acquire_amount(self): self.timestamp_sequences.extend( [ # Requests tokens every second, which is well below our # 10 TPS fill rate. 1, 2, 3, 4, 5, ] ) token_bucket = self.create_token_bucket(max_rate=10) for _ in range(5): assert await token_bucket.acquire(1, block=False) async def test_can_change_max_capacity_lower(self): # Requests at 1 TPS. self.timestamp_sequences.extend([1, 2, 3, 4, 5]) token_bucket = self.create_token_bucket(max_rate=10) # Request the first 5 tokens with max_rate=10 for _ in range(5): assert await token_bucket.acquire(1, block=False) # Now scale the max_rate down to 1 on the 5th second. self.timestamp_sequences.append(5) await token_bucket.set_max_rate(1) # And then from seconds 6-10 we request at one per second. self.timestamp_sequences.extend([6, 7, 8, 9, 10]) for _ in range(5): assert await token_bucket.acquire(1, block=False) async def test_max_capacity_is_at_least_one(self): token_bucket = self.create_token_bucket() self.timestamp_sequences.append(1) await token_bucket.set_max_rate(0.5) assert token_bucket._fill_rate == 0.5 assert token_bucket._max_capacity == 1 async def test_acquire_fails_on_non_block_mode_returns_false(self): self.timestamp_sequences.extend( [ # Initial creation time. 0, # Requests a token 1 second later. 1, ] ) token_bucket = self.create_token_bucket(max_rate=10) with pytest.raises(CapacityNotAvailableError): await token_bucket.acquire(100, block=False) async def test_can_retrieve_at_max_send_rate(self): self.timestamp_sequences.extend( [ # Request a new token every 100ms (10 TPS) for 2 seconds. 1 + 0.1 * i for i in range(20) ] ) token_bucket = self.create_token_bucket(max_rate=10) for _ in range(20): assert await token_bucket.acquire(1, block=False) async def test_acquiring_blocks_when_capacity_reached(self): # This is 1 token every 0.1 seconds. token_bucket = self.create_token_bucket(max_rate=10) self.timestamp_sequences.extend( [ # The first acquire() happens after .1 seconds. 0.1, # The second acquire() will fail because we get tokens at # 1 per 0.1 seconds. We will then sleep for 0.05 seconds until we # get a new token. 0.15, # And at 0.2 seconds we get our token. 0.2, # And at 0.3 seconds we have no issues getting a token. # Because we're using such small units (to avoid bloating the # test run time), we have to go slightly over 0.3 seconds here. 0.300001, ] ) assert await token_bucket.acquire(1, block=False) assert token_bucket._current_capacity == 0 assert await token_bucket.acquire(1, block=True) assert token_bucket._current_capacity == 0 assert await token_bucket.acquire(1, block=False) async def test_rate_cant_go_below_min(self): token_bucket = self.create_token_bucket(max_rate=1, min_rate=0.2) self.timestamp_sequences.append(1) await token_bucket.set_max_rate(0.1) assert token_bucket._fill_rate == 0.2 assert token_bucket._current_capacity == 1 aio-libs-aiobotocore-e39c8b3/tests/test_basic_s3.py000066400000000000000000000543461510465277300224250ustar00rootroot00000000000000import base64 import hashlib from collections import defaultdict from inspect import iscoroutine from typing import Callable import aioitertools import botocore.retries.adaptive try: import httpx except ImportError: httpx = None import pytest import aiobotocore.retries.adaptive from aiobotocore import httpsession from aiobotocore.response import StreamingBody async def fetch_all(pages): responses = [] async for n in pages: responses.append(n) return responses async def test_can_make_request(s3_client): # Basic smoke test to ensure we can talk to s3. result = await s3_client.list_buckets() # Can't really assume anything about whether or not they have buckets, # but we can assume something about the structure of the response. actual_keys = sorted(list(result.keys())) assert actual_keys == ['Buckets', 'Owner', 'ResponseMetadata'] @pytest.mark.parametrize('s3_verify', [False]) async def test_can_make_request_no_verify(s3_client): # Basic smoke test to ensure we can talk to s3. result = await s3_client.list_buckets() # Can't really assume anything about whether or not they have buckets, # but we can assume something about the structure of the response. actual_keys = sorted(list(result.keys())) assert actual_keys == ['Buckets', 'Owner', 'ResponseMetadata'] async def test_fail_proxy_request( skip_httpx, aa_fail_proxy_config, s3_client, monkeypatch ): # based on test_can_make_request with pytest.raises(httpsession.ProxyConnectionError): await s3_client.list_buckets() @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_succeed_proxy_request(aa_succeed_proxy_config, s3_client): result = await s3_client.list_buckets() actual_keys = sorted(list(result.keys())) assert actual_keys == ['Buckets', 'Owner', 'ResponseMetadata'] async def test_can_get_bucket_location(s3_client, bucket_name): result = await s3_client.get_bucket_location(Bucket=bucket_name) assert 'LocationConstraint' in result # For buckets in us-east-1 (US Classic Region) this will be None # TODO fix this assert result['LocationConstraint'] in [None, 'us-west-2', 'us-east-1'] async def test_can_delete_urlencoded_object( s3_client, bucket_name, create_object ): key_name = 'a+b/foo' await create_object(key_name=key_name) resp = await s3_client.list_objects(Bucket=bucket_name) bucket_contents = resp['Contents'] assert len(bucket_contents) == 1 assert bucket_contents[-1]['Key'] == 'a+b/foo' # TODO: unfortunately this is broken now: https://github.com/spulec/moto/issues/5030 # resp = await s3_client.list_objects(Bucket=bucket_name, Prefix='a+b') # subdir_contents = resp['Contents'] # assert len(subdir_contents) == 1 # assert subdir_contents[0]['Key'] == 'a+b/foo' response = await s3_client.delete_object(Bucket=bucket_name, Key=key_name) pytest.aio.assert_status_code(response, 204) async def test_can_paginate(s3_client, bucket_name, create_object): for i in range(5): key_name = f'key{i}' await create_object(key_name) paginator = s3_client.get_paginator('list_objects') pages = paginator.paginate(MaxKeys=1, Bucket=bucket_name) responses = await fetch_all(pages) assert len(responses) == 5, responses key_names = [el['Contents'][0]['Key'] for el in responses] assert key_names == ['key0', 'key1', 'key2', 'key3', 'key4'] async def test_can_paginate_with_page_size( s3_client, bucket_name, create_object ): for i in range(5): key_name = f'key{i}' await create_object(key_name) paginator = s3_client.get_paginator('list_objects') pages = paginator.paginate( PaginationConfig={'PageSize': 1}, Bucket=bucket_name ) responses = await fetch_all(pages) assert len(responses) == 5, responses data = [r for r in responses] key_names = [el['Contents'][0]['Key'] for el in data] assert key_names == ['key0', 'key1', 'key2', 'key3', 'key4'] async def test_can_search_paginate(s3_client, bucket_name, create_object): keys = [] for i in range(5): key_name = f'key{i}' keys.append(key_name) await create_object(key_name) paginator = s3_client.get_paginator('list_objects') page_iter = paginator.paginate(Bucket=bucket_name) async for key_name in page_iter.search('Contents[*].Key'): assert key_name in keys async def test_can_paginate_iterator(s3_client, bucket_name, create_object): for i in range(5): key_name = f'key{i}' await create_object(key_name) paginator = s3_client.get_paginator('list_objects') responses = [] async for page in paginator.paginate( PaginationConfig={'PageSize': 1}, Bucket=bucket_name ): assert not iscoroutine(page) responses.append(page) assert len(responses) == 5, responses data = [r for r in responses] key_names = [el['Contents'][0]['Key'] for el in data] assert key_names == ['key0', 'key1', 'key2', 'key3', 'key4'] async def test_result_key_iters(s3_client, bucket_name, create_object): for i in range(5): key_name = f'key/{i}/{i}' await create_object(key_name) key_name2 = f'key/{i}' await create_object(key_name2) paginator = s3_client.get_paginator('list_objects') generator = paginator.paginate( MaxKeys=2, Prefix='key/', Delimiter='/', Bucket=bucket_name ) iterators = generator.result_key_iters() response = defaultdict(list) key_names = [i.result_key for i in iterators] # adapt to aioitertools ideas iterators = [itr.__aiter__() for itr in iterators] async for vals in aioitertools.zip_longest(*iterators): pass for k, val in zip(key_names, vals): response.setdefault(k.expression, []) response[k.expression].append(val) assert 'Contents' in response assert 'CommonPrefixes' in response async def test_can_get_and_put_object( s3_client: aiobotocore.client.AioBaseClient, create_object: Callable, bucket_name: str, ): key_name = 'foobarbaz' await create_object(key_name, body='body contents') resp = await s3_client.get_object(Bucket=bucket_name, Key=key_name) data = await resp['Body'].read() # TODO: think about better api and make behavior like in aiohttp if isinstance(resp['Body'], StreamingBody): resp['Body'].close() else: await resp['Body'].aclose() assert data == b'body contents' # now test checksum'd file key_name = 'foobarbaz_cs' await create_object(key_name, b'abcd', ChecksumAlgorithm="sha1") resp = await s3_client.get_object( Bucket=bucket_name, Key=key_name, ChecksumMode="ENABLED" ) data = await resp['Body'].read() assert data == b'abcd' @pytest.mark.patch_attributes( [ dict( target="aiobotocore.retries.adaptive.AsyncClientRateLimiter.on_sending_request", side_effect=aiobotocore.retries.adaptive.AsyncClientRateLimiter.on_sending_request, autospec=True, ), dict( target="aiobotocore.retries.adaptive.AsyncClientRateLimiter.on_receiving_response", side_effect=aiobotocore.retries.adaptive.AsyncClientRateLimiter.on_receiving_response, autospec=True, ), dict( target="botocore.retries.adaptive.ClientRateLimiter.on_sending_request", side_effect=botocore.retries.adaptive.ClientRateLimiter.on_sending_request, autospec=True, ), dict( target="botocore.retries.adaptive.ClientRateLimiter.on_receiving_response", side_effect=botocore.retries.adaptive.ClientRateLimiter.on_receiving_response, autospec=True, ), ] ) @pytest.mark.config_kwargs( dict(retries={"max_attempts": 5, "mode": "adaptive"}) ) async def test_adaptive_retry( s3_client, config, create_object, bucket_name, patch_attributes ): await create_object('foobarbaz', body='body contents') # Check that our async implementations were correctly called. # We need to patch event listeners before the S3 client is created (see # documentation for `patch_attributes`), but as a result, other calls may be # performed during the setup of other fixtures. Thus, we can't rely on the total # number of calls, we just inspect the last one. assert len(patch_attributes[0].mock_calls) > 0 # on_sending_request _, _, call_args = patch_attributes[0].mock_calls[-1] assert call_args["event_name"] == "before-send.s3.PutObject" assert call_args["request"].url.endswith("foobarbaz") assert len(patch_attributes[1].mock_calls) > 0 # on_receiving_response _, _, call_args = patch_attributes[1].mock_calls[-1] assert call_args["event_name"] == "needs-retry.s3.PutObject" # Check that we did not call any blocking method. # Unfortunately can't directly patch threading.Lock.__enter__. patch_attributes[2].assert_not_called() patch_attributes[3].assert_not_called() async def test_get_object_stream_wrapper( s3_client, create_object, bucket_name ): await create_object('foobarbaz', body='body contents') response = await s3_client.get_object(Bucket=bucket_name, Key='foobarbaz') body = response['Body'] if httpx and isinstance(body, httpx.Response): # httpx does not support `.aread(1)` byte_iterator = body.aiter_raw(1) chunk1 = await byte_iterator.__anext__() chunk2 = b"" async for b in byte_iterator: chunk2 += b else: chunk1 = await body.read(1) chunk2 = await body.read() assert chunk1 == b'b' assert chunk2 == b'ody contents' if isinstance(body, StreamingBody): body.close() else: await body.aclose() async def test_get_object_stream_context( s3_client, create_object, bucket_name ): await create_object('foobarbaz', body='body contents') response = await s3_client.get_object(Bucket=bucket_name, Key='foobarbaz') async with response['Body'] as stream: data = await stream.read() assert data == b'body contents' async def test_paginate_max_items( s3_client, create_multipart_upload, bucket_name ): await create_multipart_upload('foo/key1') await create_multipart_upload('foo/key1') await create_multipart_upload('foo/key1') await create_multipart_upload('foo/key2') await create_multipart_upload('foobar/key1') await create_multipart_upload('foobar/key2') await create_multipart_upload('bar/key1') await create_multipart_upload('bar/key2') # Verify when we have MaxItems=None, we get back all 8 uploads. await pytest.aio.assert_num_uploads_found( s3_client, bucket_name, 'list_multipart_uploads', max_items=None, num_uploads=8, ) # Verify when we have MaxItems=1, we get back 1 upload. await pytest.aio.assert_num_uploads_found( s3_client, bucket_name, 'list_multipart_uploads', max_items=1, num_uploads=1, ) paginator = s3_client.get_paginator('list_multipart_uploads') # Works similar with build_full_result() pages = paginator.paginate( PaginationConfig={'MaxItems': 1}, Bucket=bucket_name ) full_result = await pages.build_full_result() assert len(full_result['Uploads']) == 1 async def test_paginate_within_page_boundaries( s3_client, create_object, bucket_name ): await create_object('a') await create_object('b') await create_object('c') await create_object('d') paginator = s3_client.get_paginator('list_objects') # First do it without a max keys so we're operating on a single page of # results. pages = paginator.paginate( PaginationConfig={'MaxItems': 1}, Bucket=bucket_name ) first = await pages.build_full_result() t1 = first['NextToken'] pages = paginator.paginate( PaginationConfig={'MaxItems': 1, 'StartingToken': t1}, Bucket=bucket_name, ) second = await pages.build_full_result() t2 = second['NextToken'] pages = paginator.paginate( PaginationConfig={'MaxItems': 1, 'StartingToken': t2}, Bucket=bucket_name, ) third = await pages.build_full_result() t3 = third['NextToken'] pages = paginator.paginate( PaginationConfig={'MaxItems': 1, 'StartingToken': t3}, Bucket=bucket_name, ) fourth = await pages.build_full_result() assert first['Contents'][-1]['Key'] == 'a' assert second['Contents'][-1]['Key'] == 'b' assert third['Contents'][-1]['Key'] == 'c' assert fourth['Contents'][-1]['Key'] == 'd' @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_unicode_key_put_list(s3_client, bucket_name, create_object): # Verify we can upload a key with a unicode char and list it as well. key_name = '\u2713' await create_object(key_name) parsed = await s3_client.list_objects(Bucket=bucket_name) assert len(parsed['Contents']) == 1 assert parsed['Contents'][0]['Key'] == key_name parsed = await s3_client.get_object(Bucket=bucket_name, Key=key_name) data = await parsed['Body'].read() await parsed['Body'].aclose() assert data == b'foo' @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_unicode_system_character(s3_client, bucket_name, create_object): # Verify we can use a unicode system character which would normally # break the xml parser key_name = 'foo\x08' await create_object(key_name) parsed = await s3_client.list_objects(Bucket=bucket_name) assert len(parsed['Contents']) == 1 assert parsed['Contents'][0]['Key'] == key_name parsed = await s3_client.list_objects( Bucket=bucket_name, EncodingType='url' ) assert len(parsed['Contents']) == 1 assert parsed['Contents'][0]['Key'] == 'foo%08' async def test_non_normalized_key_paths(s3_client, bucket_name, create_object): # The create_object method has assertEqual checks for 200 status. await create_object('key./././name') bucket = await s3_client.list_objects(Bucket=bucket_name) bucket_contents = bucket['Contents'] assert len(bucket_contents) == 1 assert bucket_contents[0]['Key'] == 'key./././name' @pytest.mark.skipif(True, reason='Not supported') @pytest.mark.localonly async def test_reset_stream_on_redirects(region, create_bucket): # Create a bucket in a non classic region. bucket_name = await create_bucket(region) # Then try to put a file like object to this location. assert bucket_name async def test_copy_with_quoted_char(s3_client, create_object, bucket_name): key_name = 'a+b/foo' await create_object(key_name=key_name) key_name2 = key_name + 'bar' source = f'{bucket_name}/{key_name}' await s3_client.copy_object( Bucket=bucket_name, Key=key_name2, CopySource=source ) # Now verify we can retrieve the copied object. resp = await s3_client.get_object(Bucket=bucket_name, Key=key_name2) data = await resp['Body'].read() if isinstance(resp['Body'], StreamingBody): resp['Body'].close() else: await resp['Body'].aclose() assert data == b'foo' async def test_copy_with_query_string(s3_client, create_object, bucket_name): key_name = 'a+b/foo?notVersionid=bar' await create_object(key_name=key_name) key_name2 = key_name + 'bar' await s3_client.copy_object( Bucket=bucket_name, Key=key_name2, CopySource=f'{bucket_name}/{key_name}', ) # Now verify we can retrieve the copied object. resp = await s3_client.get_object(Bucket=bucket_name, Key=key_name2) data = await resp['Body'].read() if isinstance(resp['Body'], StreamingBody): resp['Body'].close() else: await resp['Body'].aclose() assert data == b'foo' async def test_can_copy_with_dict_form(s3_client, create_object, bucket_name): key_name = 'a+b/foo?versionId=abcd' await create_object(key_name=key_name) key_name2 = key_name + 'bar' await s3_client.copy_object( Bucket=bucket_name, Key=key_name2, CopySource={'Bucket': bucket_name, 'Key': key_name}, ) # Now verify we can retrieve the copied object. resp = await s3_client.get_object(Bucket=bucket_name, Key=key_name2) data = await resp['Body'].read() if isinstance(resp['Body'], StreamingBody): resp['Body'].close() else: await resp['Body'].aclose() assert data == b'foo' async def test_can_copy_with_dict_form_with_version( s3_client, create_object, bucket_name ): key_name = 'a+b/foo?versionId=abcd' response = await create_object(key_name=key_name) key_name2 = key_name + 'bar' await s3_client.copy_object( Bucket=bucket_name, Key=key_name2, CopySource={ 'Bucket': bucket_name, 'Key': key_name, 'VersionId': response["VersionId"], }, ) # Now verify we can retrieve the copied object. resp = await s3_client.get_object(Bucket=bucket_name, Key=key_name2) data = await resp['Body'].read() if isinstance(resp['Body'], StreamingBody): resp['Body'].close() else: await resp['Body'].aclose() assert data == b'foo' async def test_copy_with_s3_metadata(s3_client, create_object, bucket_name): key_name = 'foo.txt' await create_object(key_name=key_name) copied_key = 'copied.txt' parsed = await s3_client.copy_object( Bucket=bucket_name, Key=copied_key, CopySource=f'{bucket_name}/{key_name}', MetadataDirective='REPLACE', Metadata={"mykey": "myvalue", "mykey2": "myvalue2"}, ) pytest.aio.assert_status_code(parsed, 200) @pytest.mark.parametrize('region', ['us-east-1']) @pytest.mark.parametrize('signature_version', ['s3']) # 'Content-Disposition' not supported by moto yet @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_presign_with_existing_query_string_values( s3_client, bucket_name, aio_session, create_object ): key_name = 'foo.txt' await create_object(key_name=key_name) content_disposition = 'attachment; filename=foo.txt;' params = { 'Bucket': bucket_name, 'Key': key_name, 'ResponseContentDisposition': content_disposition, } presigned_url = await s3_client.generate_presigned_url( 'get_object', Params=params ) # Try to retrieve the object using the presigned url. # TODO: compatibility layer between httpx.AsyncClient and aiohttp.ClientSession? if httpx and isinstance(aio_session, httpx.AsyncClient): async with aio_session.stream("GET", presigned_url) as resp: data = await resp.aread() headers = resp.headers else: async with aio_session.get(presigned_url) as resp: data = await resp.read() headers = resp.headers assert headers['Content-Disposition'] == content_disposition assert data == b'foo' @pytest.mark.parametrize('region', ['us-east-1']) @pytest.mark.parametrize('signature_version', ['s3v4']) # moto host will be localhost @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_presign_sigv4( s3_client, bucket_name, aio_session, create_object ): key = 'myobject' await create_object(key_name=key) presigned_url = await s3_client.generate_presigned_url( 'get_object', Params={'Bucket': bucket_name, 'Key': key} ) msg = ( "Host was suppose to be the us-east-1 endpoint, " f"instead got: {presigned_url}" ) assert presigned_url.startswith( f'https://{bucket_name}.s3.amazonaws.com/{key}' ), msg # Try to retrieve the object using the presigned url. if httpx and isinstance(aio_session, httpx.AsyncClient): async with aio_session.stream("GET", presigned_url) as resp: data = await resp.aread() else: async with aio_session.get(presigned_url) as resp: data = await resp.read() assert data == b'foo' @pytest.mark.parametrize('signature_version', ['s3v4']) @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_can_follow_signed_url_redirect( alternative_s3_client, create_object, bucket_name ): await create_object('foobarbaz') # Simulate redirection by provide wrong endpoint intentionally resp = await alternative_s3_client.get_object( Bucket=bucket_name, Key='foobarbaz' ) data = await resp['Body'].read() if isinstance(resp['Body'], StreamingBody): resp['Body'].close() else: await resp['Body'].aclose() assert data == b'foo' @pytest.mark.parametrize('region', ['eu-west-1']) @pytest.mark.parametrize('alternative_region', ['us-west-2']) @pytest.mark.parametrize('mocking_test', [False]) @pytest.mark.localonly async def test_bucket_redirect( s3_client, alternative_s3_client, region, create_bucket ): key = 'foobarbaz' # create bucket in alternative region bucket_name = await create_bucket(region) await s3_client.put_object(Bucket=bucket_name, Key=key, Body=b'') await s3_client.get_object(Bucket=bucket_name, Key=key) # This should not raise await alternative_s3_client.put_object( Bucket=bucket_name, Key=key, Body=b'' ) await alternative_s3_client.get_object(Bucket=bucket_name, Key=key) @pytest.mark.parametrize('signature_version', ['s3v4']) async def test_head_object_keys(s3_client, create_object, bucket_name): await create_object('foobarbaz') resp = await s3_client.head_object(Bucket=bucket_name, Key='foobarbaz') # this is to ensure things like: # https://github.com/aio-libs/aiobotocore/issues/131 don't happen again assert set(resp.keys()) == { 'AcceptRanges', 'ETag', 'ContentType', 'Metadata', 'LastModified', 'ResponseMetadata', 'ContentLength', 'VersionId', } @pytest.mark.parametrize('server_scheme', ['https']) @pytest.mark.parametrize('s3_verify', [False]) async def test_put_object_sha256(s3_client, bucket_name): data = b'test1234' digest = hashlib.sha256(data).digest() resp = await s3_client.put_object( Bucket=bucket_name, Key='foobarbaz', Body=data, ChecksumAlgorithm='SHA256', ) sha256_trailer_checksum = base64.b64decode(resp['ChecksumSHA256']) assert digest == sha256_trailer_checksum aio-libs-aiobotocore-e39c8b3/tests/test_batch.py000066400000000000000000000002071510465277300220030ustar00rootroot00000000000000async def test_batch(batch_client): job_queues = await batch_client.describe_job_queues() assert job_queues['jobQueues'] == [] aio-libs-aiobotocore-e39c8b3/tests/test_client.py000066400000000000000000000014461510465277300222060ustar00rootroot00000000000000import pytest from botocore.exceptions import OperationNotPageableError async def test_get_paginator_not_supported_by_service(sns_client): operation_name = 'list_tags_for_resource' with pytest.raises(OperationNotPageableError): sns_client.get_paginator(operation_name) async def test_get_waiter_not_supported_by_service(sns_client): waiter_name = 'sns_does_not_support_waiters' with pytest.raises( ValueError, match=f'Waiter does not exist: {waiter_name}' ): sns_client.get_waiter(waiter_name) async def test_get_waiter_invalid_waiter_name(cloudformation_client): waiter_name = 'this_name_is_invalid' with pytest.raises( ValueError, match=f'Waiter does not exist: {waiter_name}' ): cloudformation_client.get_waiter(waiter_name) aio-libs-aiobotocore-e39c8b3/tests/test_config.py000066400000000000000000000123071510465277300221730ustar00rootroot00000000000000import aiohttp.resolver import anyio import pytest from botocore.config import Config from botocore.exceptions import ParamValidationError, ReadTimeoutError from aiobotocore.config import AioConfig from aiobotocore.httpsession import AIOHTTPSession from aiobotocore.httpxsession import HttpxSession from aiobotocore.session import AioSession, get_session from tests.mock_server import AIOServer async def test_connector_args(current_http_backend: str): with pytest.raises(ParamValidationError): # wrong type connector_args: dict[str, object] = dict(use_dns_cache=1) AioConfig(connector_args) with pytest.raises(ParamValidationError): # wrong type connector_args = dict(ttl_dns_cache="1") AioConfig(connector_args) with pytest.raises(ParamValidationError): # wrong type connector_args = dict(keepalive_timeout="1") AioConfig(connector_args) with pytest.raises(ParamValidationError): # wrong type connector_args = dict(force_close="1") AioConfig(connector_args) with pytest.raises(ParamValidationError): # wrong type connector_args = dict(keepalive_timeout="1") AioConfig(connector_args) with pytest.raises(ParamValidationError): # wrong type connector_args = dict(ssl_context="1") AioConfig(connector_args) with pytest.raises(ParamValidationError): # invalid DNS resolver connector_args = dict(resolver="1") AioConfig(connector_args) with pytest.raises(ParamValidationError): # invalid key connector_args = dict(foo="1") AioConfig(connector_args) with pytest.raises( ParamValidationError, match='Httpx does not support dns caching. https://github.com/encode/httpx/discussions/2211', ): AioConfig({'use_dns_cache': True}, http_session_cls=HttpxSession) with pytest.raises( ParamValidationError, match='Httpx backend does not currently support force_close.', ): AioConfig({'force_close': True}, http_session_cls=HttpxSession) with pytest.raises( ParamValidationError, match='Httpx backend does not support resolver.' ): AioConfig({'resolver': True}, http_session_cls=HttpxSession) # Test valid configs: AioConfig({"ttl_dns_cache": None}) AioConfig({"ttl_dns_cache": 1}) AioConfig({"resolver": aiohttp.resolver.DefaultResolver()}) AioConfig({'keepalive_timeout': None}) # test merge cfg = Config(read_timeout=75) aio_cfg = AioConfig({'keepalive_timeout': 75}) aio_cfg.merge(cfg) assert cfg.read_timeout == 75 assert aio_cfg.connector_args['keepalive_timeout'] == 75 async def test_connector_timeout(): session = AioSession() config = AioConfig( max_pool_connections=1, connect_timeout=1, retries={'max_attempts': 0} ) async with ( AIOServer() as server, session.create_client( 's3', config=config, endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): async def get_and_wait(): await s3_client.get_object(Bucket='foo', Key='bar') await anyio.sleep(100) # second request should not timeout just because there isn't a # connector available with anyio.move_on_after(3): async with anyio.create_task_group() as tg: tg.start_soon(get_and_wait) tg.start_soon(get_and_wait) async def test_connector_timeout2(): session = AioSession() config = AioConfig( max_pool_connections=1, connect_timeout=1, read_timeout=1, retries={'max_attempts': 0}, ) async with ( AIOServer() as server, session.create_client( 's3', config=config, endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): with pytest.raises(ReadTimeoutError): resp = await s3_client.get_object(Bucket='foo', Key='bar') await resp["Body"].read() async def test_get_session(): session = get_session() assert isinstance(session, AioSession) def test_merge(): config = AioConfig() other_config = AioConfig() new_config = config.merge(other_config) assert isinstance(new_config, AioConfig) assert new_config is not config assert new_config is not other_config # Check that it's possible to specify custom http_session_cls async def test_config_http_session_cls(): class SuccessExc(Exception): ... class MyHttpSession(AIOHTTPSession): async def send(self, request): raise SuccessExc config = AioConfig(http_session_cls=MyHttpSession) session = AioSession() async with ( AIOServer() as server, session.create_client( 's3', config=config, endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): with pytest.raises(SuccessExc): await s3_client.get_object(Bucket='foo', Key='bar') aio-libs-aiobotocore-e39c8b3/tests/test_configprovider.py000066400000000000000000000010421510465277300237400ustar00rootroot00000000000000import pytest from aiobotocore.session import AioSession @pytest.mark.parametrize( 'defaults_mode,retry_mode', [('legacy', 'legacy'), ('standard', 'standard'), ('auto', 'standard')], ) async def test_defaults_mode(monkeypatch, defaults_mode, retry_mode): monkeypatch.setenv('AWS_DEFAULTS_MODE', defaults_mode) session = AioSession() assert session.get_config_variable('defaults_mode') == defaults_mode async with session.create_client('s3') as client: assert client.meta.config.retries['mode'] == retry_mode aio-libs-aiobotocore-e39c8b3/tests/test_dynamodb.py000066400000000000000000000101071510465277300225170ustar00rootroot00000000000000import uuid import anyio import pytest from aiobotocore.waiter import WaiterError @pytest.fixture def dynamodb_table_def(): table_name = str(uuid.uuid4()) return dict( TableName=table_name, AttributeDefinitions=[ {'AttributeName': 'testKey', 'AttributeType': 'N'}, ], KeySchema=[ {'AttributeName': 'testKey', 'KeyType': 'HASH'}, ], ProvisionedThroughput={ 'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1, }, ) @pytest.mark.parametrize('signature_version', ['v4']) async def test_get_item(dynamodb_client, table_name, dynamodb_put_item): test_value = 'testValue' await dynamodb_put_item(test_value) response = await dynamodb_client.get_item( TableName=table_name, Key={'testKey': {'S': test_value}} ) pytest.aio.assert_status_code(response, 200) assert response['Item']['testKey'] == {'S': test_value} @pytest.mark.parametrize('signature_version', ['v4']) async def test_create_waiter(dynamodb_client, dynamodb_table_def): table_name = dynamodb_table_def['TableName'] response = await dynamodb_client.create_table(**dynamodb_table_def) pytest.aio.assert_status_code(response, 200) waiter = dynamodb_client.get_waiter('table_exists') await waiter.wait(TableName=table_name) response = await dynamodb_client.describe_table(TableName=table_name) assert response['Table']['TableStatus'] == 'ACTIVE' @pytest.mark.parametrize('signature_version', ['v4']) async def test_batch_write_scan(dynamodb_client, table_name): response = await dynamodb_client.batch_write_item( RequestItems={ table_name: [ { 'PutRequest': { 'Item': { 'testKey': {'S': 'key1'}, 'testKey2': {'S': 'key2'}, } } }, { 'PutRequest': { 'Item': { 'testKey': {'S': 'key3'}, 'testKey2': {'S': 'key4'}, } } }, ] } ) pytest.aio.assert_status_code(response, 200) response = await dynamodb_client.scan(TableName=table_name) test_keys = sorted(item['testKey']['S'] for item in response['Items']) assert response['Count'] == 2 assert test_keys == ['key1', 'key3'] @pytest.mark.parametrize('signature_version', ['v4']) async def test_delete_table(dynamodb_client, dynamodb_table_def): table_name = dynamodb_table_def['TableName'] await dynamodb_client.create_table(**dynamodb_table_def) response = await dynamodb_client.describe_table(TableName=table_name) assert response['Table']['TableStatus'] == 'ACTIVE' response = await dynamodb_client.delete_table(TableName=table_name) pytest.aio.assert_status_code(response, 200) response = await dynamodb_client.list_tables() assert table_name not in response['TableNames'] @pytest.mark.parametrize('signature_version', ['v4']) async def test_waiter_table_exists_failure(dynamodb_client): waiter = dynamodb_client.get_waiter('table_exists') with pytest.raises( WaiterError, match='Waiter TableExists failed: Max attempts exceeded' ): await waiter.wait( TableName='unknown', WaiterConfig=dict(Delay=1, MaxAttempts=1) ) @pytest.mark.parametrize('signature_version', ['v4']) async def test_waiter_table_exists(dynamodb_client, dynamodb_table_def): table_name = dynamodb_table_def['TableName'] done_event = anyio.Event() async def _create_table(): await anyio.sleep(2) await dynamodb_client.create_table(**dynamodb_table_def) done_event.set() async with anyio.create_task_group() as tg: tg.start_soon(_create_table) waiter = dynamodb_client.get_waiter('table_exists') await waiter.wait( TableName=table_name, WaiterConfig=dict(Delay=1, MaxAttempts=5) ) assert done_event.is_set() aio-libs-aiobotocore-e39c8b3/tests/test_ec2.py000066400000000000000000000015351510465277300214000ustar00rootroot00000000000000async def test_ec2_snapshot(ec2_client): # TODO: this needs to somehow validate the presigned url sent because moto is not volume_response = await ec2_client.create_volume( AvailabilityZone="us-east-1", Size=10 ) tag_spec = [ { "ResourceType": "snapshot", "Tags": [{"Key": "key", "Value": "value"}], } ] create_snapshot_response = await ec2_client.create_snapshot( VolumeId=volume_response["VolumeId"], TagSpecifications=tag_spec ) copy_snapshot_response = await ec2_client.copy_snapshot( SourceSnapshotId=create_snapshot_response["SnapshotId"], SourceRegion="us-east-1", DestinationRegion="us-east-1", Encrypted=True, TagSpecifications=tag_spec, KmsKeyId="key-1234", ) assert copy_snapshot_response['SnapshotId'] aio-libs-aiobotocore-e39c8b3/tests/test_endpoint.py000066400000000000000000000006251510465277300225460ustar00rootroot00000000000000import pytest async def test_invalid_endpoint_url(session, region): endpoint_url = 'invalid_url' with pytest.raises(ValueError, match=f'Invalid endpoint: {endpoint_url}'): async with session.create_client( 'ec2', region_name=region, endpoint_url=endpoint_url ): # should not succeed in entering client context assert False # pragma: no cover aio-libs-aiobotocore-e39c8b3/tests/test_eventstreams.py000066400000000000000000000113461510465277300234500ustar00rootroot00000000000000from contextlib import AsyncExitStack import anyio import pytest from aiobotocore.eventstream import AioEventStream from aiobotocore.parsers import AioEventStreamXMLParser # TODO once Moto supports either S3 Select or Kinesis SubscribeToShard then # this can be tested against a real AWS API TEST_STREAM_DATA = ( b'\x00\x00\x00w\x00\x00\x00U5\xd1F\xcd\r:message-type\x07\x00\x05event\x0b:event-' b'type\x07\x00\x07Records\r:content-type\x07\x00\x18application/octet-stream{"hel' b'lo":"world"}\nF\x0e\x9a2', b'\x00\x00\x00\xce\x00\x00\x00C\xdc\xd2\x99\xf9\r:message-type\x07\x00\x05event' b'\x0b:event-type\x07\x00\x05Stats\r:content-type\x07\x00\x08text/xml191918\x92\xd0?\xa5\x00\x00\x008\x00\x00\x00(\xc1' b'\xc6\x84\xd4\r:message-type\x07\x00\x05event\x0b:event-type\x07\x00\x03End\xcf' b'\x97\xd3\x92', ) class FakeStreamReader: class ChunkedIterator: def __init__(self, chunks): self.iter = iter(chunks) def __aiter__(self): return self async def __anext__(self): try: result = next(self.iter) return result, True except StopIteration: raise StopAsyncIteration() def __init__(self, chunks): self.chunks = chunks self.content = self def iter_chunks(self): return self.ChunkedIterator(self.chunks) async def test_eventstream_chunking(s3_client): # These are the options passed to the EventStream class # during a normal run with botocore. operation_name = 'SelectObjectContent' outputshape = s3_client._service_model.operation_model( operation_name ).output_shape.members['Payload'] parser = AioEventStreamXMLParser() sr = FakeStreamReader(TEST_STREAM_DATA) event_stream = AioEventStream(sr, outputshape, parser, operation_name) events = [] # {'Records': {'Payload': b'{"hello":"world"}\n'}} # {'Stats': {'Details': {'BytesScanned': 19, # 'BytesProcessed': 19, # 'BytesReturned': 18}}} # {'End': {}} async for event in event_stream: events.append(event) assert len(events) == 3 event1, event2, event3 = events assert 'Records' in event1 assert 'Stats' in event2 assert 'End' in event3 async def test_eventstream_no_iter(s3_client): # These are the options passed to the EventStream class # during a normal run with botocore. operation_name = 'SelectObjectContent' outputshape = s3_client._service_model.operation_model( operation_name ).output_shape.members['Payload'] parser = AioEventStreamXMLParser() sr = FakeStreamReader(TEST_STREAM_DATA) event_stream = AioEventStream(sr, outputshape, parser, operation_name) with pytest.raises(NotImplementedError): for _ in event_stream: pass @pytest.mark.localonly async def test_kinesis_stream_json_parser( exit_stack: AsyncExitStack, kinesis_client, create_stream ): # unfortunately moto doesn't support kinesis register_stream_consumer + # subscribe_to_shard yet stream_name = await create_stream(ShardCount=1) describe_response = await kinesis_client.describe_stream( StreamName=stream_name ) shard_id = describe_response["StreamDescription"]["Shards"][0]["ShardId"] stream_arn = describe_response["StreamDescription"]["StreamARN"] consumer_arn = None consumer_name = 'consumer' # Create some data keys = [str(i) for i in range(1, 5)] for k in keys: await kinesis_client.put_record( StreamName=stream_name, Data=k, PartitionKey=k ) register_response = await kinesis_client.register_stream_consumer( StreamARN=stream_arn, ConsumerName=consumer_name ) consumer_arn = register_response['Consumer']['ConsumerARN'] while ( describe_response := ( await kinesis_client.describe_stream_consumer( # noqa: E231, E999, E251, E501 StreamARN=stream_arn, ConsumerName=consumer_name, ConsumerARN=consumer_arn, ) ) ) and describe_response['ConsumerDescription'][ 'ConsumerStatus' ] == 'CREATING': print("Waiting for stream consumer creation") await anyio.sleep(1) starting_position = {'Type': 'LATEST'} subscribe_response = await kinesis_client.subscribe_to_shard( ConsumerARN=consumer_arn, ShardId=shard_id, StartingPosition=starting_position, ) async for event in subscribe_response['EventStream']: assert event['SubscribeToShardEvent']['Records'] == [] break aio-libs-aiobotocore-e39c8b3/tests/test_lambda.py000066400000000000000000000042641510465277300221510ustar00rootroot00000000000000import base64 import io import json import zipfile # Third Party import botocore.client try: import httpx except ImportError: httpx = None import pytest async def _get_role_arn(iam_client, role_name: str): try: response = await iam_client.get_role(RoleName=role_name) return response["Role"]["Arn"] except botocore.client.ClientError: response = await iam_client.create_role( RoleName=role_name, AssumeRolePolicyDocument="some policy", Path="/my-path/", ) return response["Role"]["Arn"] def _process_lambda(func_str) -> bytes: zip_output = io.BytesIO() zip_file = zipfile.ZipFile(zip_output, "w", zipfile.ZIP_DEFLATED) zip_file.writestr("lambda_function.py", func_str) zip_file.close() zip_output.seek(0) return zip_output.read() @pytest.fixture def aws_lambda_zip() -> bytes: lambda_src = """ import json def lambda_handler(event, context): print(event) return {"statusCode": 200, "body": event} """ return _process_lambda(lambda_src) async def test_run_lambda( iam_client, lambda_client, aws_lambda_zip, current_http_backend ): function_name = f'test-function-{current_http_backend}' role_arn = await _get_role_arn(iam_client, 'test-iam-role') lambda_response = await lambda_client.create_function( FunctionName=function_name, Runtime='python3.9', Role=role_arn, Handler='lambda_function.lambda_handler', Timeout=10, MemorySize=128, Publish=True, Code={'ZipFile': aws_lambda_zip}, ) assert lambda_response['FunctionName'] == function_name invoke_response = await lambda_client.invoke( FunctionName=function_name, InvocationType="RequestResponse", LogType='Tail', Payload=json.dumps({"hello": "world"}), ) async with invoke_response['Payload'] as stream: data = await stream.read() log_result = base64.b64decode(invoke_response["LogResult"]) assert json.loads(data) == {'statusCode': 200, "body": {"hello": "world"}} assert b"{'hello': 'world'}" in log_result await lambda_client.delete_function(FunctionName=function_name) aio-libs-aiobotocore-e39c8b3/tests/test_monitor.py000066400000000000000000000013411510465277300224110ustar00rootroot00000000000000from aiobotocore.session import AioSession async def test_monitor_response_received(session: AioSession, s3_client): # Basic smoke test to ensure we can talk to s3. handler_kwargs = {} def handler(**kwargs): nonlocal handler_kwargs handler_kwargs = kwargs s3_client.meta.events.register('response-received.s3.ListBuckets', handler) result = await s3_client.list_buckets() # Can't really assume anything about whether or not they have buckets, # but we can assume something about the structure of the response. actual_keys = sorted(list(result.keys())) assert actual_keys == ['Buckets', 'Owner', 'ResponseMetadata'] assert handler_kwargs['response_dict']['status_code'] == 200 aio-libs-aiobotocore-e39c8b3/tests/test_mturk.py000066400000000000000000000023261510465277300220700ustar00rootroot00000000000000import pytest from botocore.stub import ANY, Stubber _mturk_list_hits_response = { 'NumResults': 0, 'HITs': [], 'ResponseMetadata': { 'RequestId': '00000000-4989-4ffc-85cd-aaaaaaaaaaaa', 'HTTPStatusCode': 200, 'HTTPHeaders': { 'x-amzn-requestid': '00000000-4989-4ffc-85cd-aaaaaaaaaaaa', 'content-type': 'application/x-amz-json-1.1', 'content-length': '26', 'date': 'Thu, 04 Jun 2020 00:48:16 GMT', }, 'RetryAttempts': 0, }, } # Unfortunately moto does not support mturk yet # Also looks like we won't be able to support this (see notes from 1.0.6 release) @pytest.mark.localonly async def test_mturk_stubber(session): async with session.create_client( 'mturk', region_name='us-east-1' ) as client: with Stubber(client) as stubber: stubber.add_response( 'list_hits_for_qualification_type', _mturk_list_hits_response, {'QualificationTypeId': ANY}, ) response = await client.list_hits_for_qualification_type( QualificationTypeId='string' ) assert response == _mturk_list_hits_response aio-libs-aiobotocore-e39c8b3/tests/test_patches.py000066400000000000000000001275511510465277300223650ustar00rootroot00000000000000import hashlib import pytest from botocore import retryhandler, stub from botocore.args import ClientArgsCreator from botocore.awsrequest import AWSResponse from botocore.client import BaseClient, ClientCreator, Config from botocore.configprovider import SmartDefaultsConfigStoreFactory from botocore.context import start_as_current_context, with_current_context from botocore.credentials import ( AssumeRoleCredentialFetcher, AssumeRoleProvider, AssumeRoleWithWebIdentityCredentialFetcher, AssumeRoleWithWebIdentityProvider, BotoProvider, CachedCredentialFetcher, CanonicalNameCredentialSourcer, ConfigProvider, ContainerProvider, CredentialResolver, Credentials, DeferredRefreshableCredentials, EnvProvider, InstanceMetadataProvider, OriginalEC2Provider, ProcessProvider, ProfileProviderBuilder, RefreshableCredentials, SharedCredentialProvider, SSOCredentialFetcher, SSOProvider, _get_client_creator, create_credential_resolver, create_mfa_serial_refresher, get_credentials, ) from botocore.discovery import ( EndpointDiscoveryHandler, EndpointDiscoveryManager, ) from botocore.endpoint import ( Endpoint, EndpointCreator, convert_to_response_dict, ) from botocore.eventstream import EventStream from botocore.handlers import ( check_for_200_error, inject_presigned_url_ec2, inject_presigned_url_rds, parse_get_bucket_location, ) from botocore.hooks import EventAliaser, HierarchicalEmitter from botocore.httpchecksum import ( AwsChunkedWrapper, StreamingChecksumBody, _apply_request_trailer_checksum, _handle_bytes_response, _handle_streaming_response, apply_request_checksum, handle_checksum_body, ) from botocore.httpsession import URLLib3Session from botocore.paginate import PageIterator, ResultKeyIterator from botocore.parsers import ( PROTOCOL_PARSERS, BaseRpcV2Parser, EC2QueryParser, JSONParser, QueryParser, ResponseParser, ResponseParserFactory, RestJSONParser, RestXMLParser, RpcV2CBORParser, create_parser, ) from botocore.regions import EndpointRulesetResolver from botocore.response import StreamingBody, get_response from botocore.retries import adaptive, special, standard from botocore.retries.bucket import TokenBucket from botocore.session import Session, get_session from botocore.signers import ( RequestSigner, S3PostPresigner, _dsql_generate_db_auth_token, add_dsql_generate_db_auth_token_methods, add_generate_db_auth_token, add_generate_presigned_post, add_generate_presigned_url, dsql_generate_db_connect_admin_auth_token, dsql_generate_db_connect_auth_token, generate_db_auth_token, generate_presigned_post, generate_presigned_url, ) from botocore.tokens import ( DeferredRefreshableToken, SSOTokenProvider, create_token_resolver, ) from botocore.utils import ( ContainerMetadataFetcher, IdentityCache, IMDSFetcher, IMDSRegionProvider, InstanceMetadataFetcher, InstanceMetadataRegionFetcher, S3ExpressIdentityCache, S3ExpressIdentityResolver, S3RegionRedirector, S3RegionRedirectorv2, create_nested_client, ) from botocore.waiter import ( NormalizedOperationMethod, Waiter, create_waiter_with_client, ) from dill.source import getsource # This file ensures that our private patches will work going forward. If a # method gets updated this will assert and someone will need to validate: # 1) If our code needs to be updated # 2) If our minimum botocore version needs to be updated # 3) If we need to replace the below hash (not backwards compatible) or add # to the set # These are guards to our main patches # !!! README: HOW TO UPDATE THESE !!! # ----------------------------------- # (tests break with new version of aiohttp/botocore) # # 1) Adding support for more versions of aiohttp/botocore # In this scenario you need to ensure that aiobotocore supports the changes # that broke these tests along with the old versions of the libraries # and APPEND to the set of hashes that we support for each object you # validated. # 2) Bumping up the base version of aiohttp/botocore that we support # In this scenario ensure aiobotocore supports the new version of the libs # and REPLACE all entries with the current hashes with the new libs. # REPLACE = backwards incompatible change # APPEND = officially supporting more versions of botocore/aiohttp _PROTOCOL_PARSER_CONTENT = { 'ec2', 'query', 'json', 'rest-json', 'rest-xml', 'smithy-rpc-v2-cbor', } def test_protocol_parsers(): # Check that no new parsers have been added current_parsers = set(PROTOCOL_PARSERS.keys()) assert current_parsers == _PROTOCOL_PARSER_CONTENT @pytest.mark.parametrize( "obj, digests", [ # If you're changing these, most likely need to update dependencies in pyproject.toml as well. # args.py ( ClientArgsCreator.get_client_args, { '3dfe4ed858d06ca35b5033c0f5dc9531e12090c2', }, ), ( ClientArgsCreator._build_endpoint_resolver, { '354033a66f90fc1d939a3309ae7d595d480020a6', }, ), # client.py ( ClientCreator.create_client, { '1c27be7d1b4374887620be970dca01a9fecf9632', }, ), ( ClientCreator._create_client_class, { 'fcecaf8d4f2c1ac3c5d0eb50c573233ef86d641d', '320c74206570605ff55ca32535b3f6108a7d8c1f', }, ), ( ClientCreator._register_endpoint_discovery, { '483c6c8e035810d1b76110fc1956de76943c2f18', }, ), ( ClientCreator._get_client_args, { 'd5e19b1e62f64a745de842963c2472825a66e854', }, ), ( ClientCreator._register_s3express_events, { '716c1549989eef6bbd048bf4f134c1b4659e124a', }, ), ( ClientCreator._register_s3_events, { '4ab15da7cb36fa795f64154581a970b0966fdf50', }, ), ( ClientCreator._register_retries, { 'ceecfc40a4ffa83551d7e6f63fd5d17f820664b0', }, ), ( ClientCreator._register_v2_adaptive_retries, { '665ecd77d36a5abedffb746d83a44bb0a64c660a', }, ), ( ClientCreator._register_v2_standard_retries, { '9ec4ff68599544b4f46067b3783287862d38fb50', }, ), ( ClientCreator._register_legacy_retries, { '000b2f2a122602e2e741ec2e89308dc2e2b67329', '06864c8ecac153fa36d9d57f0057cb95909ee7cd', }, ), ( BaseClient._make_api_call.__wrapped__, { '44a1fbef21dc588a7976510c3540c5a51350aa11', }, ), ( BaseClient._make_request, { 'cfd8bbf19ea132134717cdf9c460694ddacdbf58', 'bdfa0e9843e97bee2da16f17c6a7685ad4a0a44b', }, ), ( BaseClient._convert_to_request_dict, { '5e0a374926b6ee1a8715963ab551e126506e7fc9', }, ), ( BaseClient._emit_api_params, { 'abd67874dae8d5cd2788412e4699398cb915a119', }, ), ( BaseClient._resolve_endpoint_ruleset, { 'f09731451ff6ba0645dc82e5c7948dfbf781e025', }, ), ( BaseClient.get_paginator, { '1c38079de68ccd43a5a06e36b1a47ec62233a7c2', }, ), ( BaseClient.get_waiter, { '4a4aeabe53af25d3737204187a31f930230864b4', '40484242690c0679c3b13fddf0d6c9a26f7b25d2', }, ), ( BaseClient.__getattr__, { '3ec17f468f50789fa633d6041f40b66a2f593e77', }, ), # config.py ( Config.merge, { 'c3dd8c3ffe0da86953ceba4a35267dfb79c6a2c8', }, ), ( Config, { 'da2d8b797568604449695cdcf803e344bdf887eb', 'a3abd6cb69883934f48403acfaa5ef598c548982', }, ), # context.py ( with_current_context, {'674c6120c400c7b4b2eba2aa8167ed90ac884eb2'}, ), ( start_as_current_context.__wrapped__, {'b9e5e8a4e48457f868c4397172fe8202bd918e18'}, ), # credentials.py ( create_mfa_serial_refresher, { '9b5e98782fcacdcea5899a6d0d29d1b9de348bb0', }, ), ( Credentials.get_frozen_credentials, { '87ced1e435eda7738852e0569ddbff3b7a8e977f', }, ), ( RefreshableCredentials.__init__, { '88ab16aa041eb1cad8d666834613cbe5f0cf7514', }, ), # We've overridden some properties ( RefreshableCredentials.__dict__['access_key'].fset, { 'edc4a25baef877a9662f68cd9ccefcd33a81bab7', }, ), ( RefreshableCredentials.__dict__['access_key'].fget, { 'f6c823210099db99dd343d9e1fae6d4eb5aa5fce', }, ), ( RefreshableCredentials.__dict__['secret_key'].fset, { 'b19fe41d66822c72bd6ae2e60de5c5d27367868a', }, ), ( RefreshableCredentials.__dict__['secret_key'].fget, { '3e27331a037549104b8669e225bbbb2c465a16d4', }, ), ( RefreshableCredentials.__dict__['token'].fset, { '1f8a308d4bf21e666f8054a0546e91541661da7b', }, ), ( RefreshableCredentials.__dict__['token'].fget, { '005c1b44b616f37739ce9276352e4e83644d8220', }, ), ( RefreshableCredentials.__dict__['account_id'].fset, { '0d67de1c18dc80429b8537fe94a842175636f850', }, ), ( RefreshableCredentials.__dict__['account_id'].fget, { 'd40a5f4571df3a239500a615ca0cc3fe77ac70ab', }, ), ( RefreshableCredentials._refresh, { 'd5731d01db2812d498df19b4bd5d7c17519241fe', }, ), ( RefreshableCredentials._protected_refresh, { '8b2d523a605a4f4728ad99861c14143838e56176', }, ), ( RefreshableCredentials.get_frozen_credentials, { 'f661c84a8b759786e011f0b1e8a468a0c6294e36', }, ), ( DeferredRefreshableCredentials.__init__, { 'dd5ccda2b047854ae5e88907b654b57d35fe0dfd', }, ), ( SSOCredentialFetcher._get_credentials, { '6fdbe9d6045e98be5125fade72fb1711936666b0', }, ), ( SSOProvider.load, { 'ca09b93ac1aea3df424652536d70f0fc52d86937', }, ), ( CachedCredentialFetcher._get_credentials, { '02a7d13599d972e3f258d2b53f87eeda4cc3e3a4', }, ), ( CachedCredentialFetcher.fetch_credentials, { '0dd2986a4cbb38764ec747075306a33117e86c3d', }, ), ( CachedCredentialFetcher._get_cached_credentials, { 'f578b2527712d501c021f8236bd20e68e99201f5', }, ), ( AssumeRoleCredentialFetcher._get_credentials, { '7216e35a401c7bd95f8d95d872f55590ade8aaa7', }, ), ( AssumeRoleCredentialFetcher._create_client, { '27c76f07bd43e665899ca8d21b6ba2038b276fbb', }, ), # Referenced by AioAssumeRoleWithWebIdentityCredentialFetcher ( AssumeRoleWithWebIdentityCredentialFetcher.__init__, { 'ab270375dfe425c5e21276590dea690fdbfe40a5', }, ), ( AssumeRoleWithWebIdentityCredentialFetcher._get_credentials, { '726f644df3f8e71125c8890193f2fa25bd22e331', }, ), ( AssumeRoleWithWebIdentityCredentialFetcher._assume_role_kwargs, { '8fb4fefe8664b7d82a67e0fd6d6812c1c8d92285', }, ), # Ensure that the load method doesn't do anything we should asyncify ( EnvProvider.load, { 'da0cfb81a7a27ac1159c9b87920908944d1849f2', }, ), ( ContainerProvider.__init__, { 'ea6aafb2e12730066af930fb5a27f7659c1736a1', }, ), ( ContainerProvider.load, { '57c35569050b45c1e9e33fcdb3b49da9e342fdcf', }, ), ( ContainerProvider._retrieve_or_fail, { '057ebdc912f15ec4903ab2e157f339d2ae740615', }, ), ( ContainerProvider._create_fetcher, { 'f70e2b62a83099aa9863ed31fae7416b8a4c8df5', }, ), ( InstanceMetadataProvider.load, { '8b32da46500dd7b7177e1e6f781a7ce962dd3b66', }, ), ( ProfileProviderBuilder._create_process_provider, { 'c5eea47bcfc449a6d73a9892bd0e1897f6be0c20', }, ), ( ProfileProviderBuilder._create_shared_credential_provider, { '33f99c6a0ef71a92b0c52ccc59c8ca7e33fa0890', }, ), ( ProfileProviderBuilder._create_config_provider, { 'f9a40d4211f6e663ba2ae9682fba5306152178c5', }, ), ( ProfileProviderBuilder._create_web_identity_provider, { '478745fa6779a7c69fe9441d89d3e921438e3a59', }, ), ( ProfileProviderBuilder._create_sso_provider, { 'e463160179add7a1a513e46ee848447a216504aa', }, ), ( ConfigProvider.load, { 'af1d9e127b3d40376570cb48e3cbc8d66dff2041', }, ), ( SharedCredentialProvider.load, { 'fddbf6610526a41358187a529dbd2c2b82e23224', }, ), ( ProcessProvider.__init__, { '2e870ec0c6b0bc8483fa9b1159ef68bbd7a12c56', }, ), ( ProcessProvider.load, { 'dd8c5de56d32dd05f4e664faac84979ea84086cc', }, ), ( ProcessProvider._retrieve_credentials_using, { 'd8755c0bb80c4c9852c89276db6ec418f988f6f0', }, ), ( CredentialResolver.load_credentials, { 'ef31ba8817f84c1f61f36259da1cc6e597b8625a', }, ), ( AssumeRoleWithWebIdentityProvider.load, { '8f48f6cadf08a09cf5a22b1cc668e60bc4ea389d', }, ), ( AssumeRoleWithWebIdentityProvider._assume_role_with_web_identity, { '90c6a54d339f439988856f439dbaea9f371202f7', }, ), ( AssumeRoleProvider.load, { 'ee9ddb43e25eb1105185253c0963a2f5add49a95', }, ), ( AssumeRoleProvider._load_creds_via_assume_role, { 'bc9c38d6320b5f9712570ec8570f467e5288340d', }, ), ( AssumeRoleProvider._resolve_source_credentials, { '1c2018d648dc72ab165cd1ffe1adc6b65856aa6b', }, ), ( AssumeRoleProvider._resolve_credentials_from_profile, { 'a5a2808e315b45957cb881b31b73624eba44ade6', }, ), ( AssumeRoleProvider._resolve_static_credentials_from_profile, { 'a470795f6ba451cf99ce7456fef24777f8087654', }, ), ( AssumeRoleProvider._resolve_credentials_from_source, { 'b3144366432c11cee4bca286bd5ff10c3449b97b', }, ), ( CanonicalNameCredentialSourcer.source_credentials, { '602930a78e0e64e3b313a046aab5edc3bcf5c2d9', }, ), ( CanonicalNameCredentialSourcer._get_provider, { 'c028b9776383cc566be10999745b6082f458d902', }, ), ( BotoProvider.load, { 'c85411fa3d3ff75b3edb98f38c36426c0ffed3bb', }, ), ( OriginalEC2Provider.load, { 'dc58cd1c79d177105b183a2d20e1154e6f8f0733', }, ), ( create_credential_resolver, { 'fe797afd33126be87f86e44ab20475d50d727a4e', }, ), ( get_credentials, { 'ff0c735a388ac8dd7fe300a32c1e36cdf33c0f56', }, ), ( _get_client_creator, { '6274e5675ecf13180b5a18daa0242bb71d6437b6', }, ), # configprovider.py ( SmartDefaultsConfigStoreFactory.merge_smart_defaults, { 'e320299bb739694fefe2f5df6be62cc5321d3dc5', }, ), ( SmartDefaultsConfigStoreFactory.resolve_auto_mode, { '013fa8904b42931c69e3d8623025a1582379ba2a', }, ), # endpoint.py ( convert_to_response_dict, { '5b7701c1f5b3cb2daa6eb307cdbdbbb2e9d33e5f', '0fb9d2f2ab899fad8b2be93223b403f11032670a', }, ), ( Endpoint.__init__, { '4bafe9733a02817950f5096612410ec4ebc40f55', }, ), ( Endpoint.create_request, { '37d0fbd02f91aef6c0499a2d0a725bf067c3ce8b', 'f78944c88453219ed3f0f5c8eefccdc69faf3046', }, ), ( Endpoint._send_request, { '5d40748a95c3005728e6548b402b90cb57d6f575', '0158d0b6c492fde95947b49c34fd0927e5d145a1', }, ), ( Endpoint._get_response, { 'bbf10e6e07147d50e09d7205bf0883bd673a8bf3', }, ), ( Endpoint._do_get_response, { 'a9f3ae0f375ba9967425b1fb3e4d45537df4f1e3', }, ), ( Endpoint._needs_retry, { 'f718e2ff874763a677648fe6f87cc65e4cec2792', }, ), ( Endpoint._send, { '644c7e5bb88fecaa0b2a204411f8c7e69cc90bf1', }, ), ( Endpoint._add_modeled_error_fields, { 'd0390647f2d7a4a325be048dcda4dcc7f42fdd17', }, ), ( EndpointCreator.create_endpoint, { '863e17b1299f9fda2cef5be3297d470d1bfa86ae', 'a49253f3c80fc69f66fd64c8549f6daa47f7c6ad', }, ), # eventstream.py ( EventStream._create_raw_event_generator, { '1764be20b3abe19b60381756a989794de298ffbb', }, ), ( EventStream.__iter__, { '8a9b454943f8ef6e81f5794d641adddd1fdd5248', }, ), ( EventStream._parse_event, { 'c5b4e65fe718653a6f4cee4e8647f286f10fae05', }, ), ( EventStream.get_initial_response, { 'aed648305970c90bb5d1e31f6fe5ff12cf6a2a06', }, ), # hooks.py ( HierarchicalEmitter._emit, { '5d9a6b1aea1323667a9310e707a9f0a006f8f6e8', }, ), ( HierarchicalEmitter.emit_until_response, { '23670e04e0b09a9575c7533442bca1b2972ade82', }, ), ( HierarchicalEmitter._verify_and_register, { '41eda968127e35e02e7120ec621240b61639e3dd', }, ), ( EventAliaser.emit_until_response, { '0d635bf7ae5022b1fdde891cd9a91cd4c449fd49', }, ), # paginate.py ( PageIterator.__iter__, { '0bfe2ffb0ea63faa1f28cc22250ce626959d05d1', }, ), ( PageIterator._make_request, { 'e926671018897ac5851a3add5d2bc15a2d6142df', }, ), ( PageIterator.result_key_iters, { 'e8cd36fdc4960e08c9aa50196c4e5d1ee4e39756', }, ), ( PageIterator.build_full_result, { '9051327d350ed5a4843c74d34be74ba2f1732e30', }, ), ( ResultKeyIterator, { '3028dde4c4de6029f628f4a9d1fff36986b41591', }, ), # parsers.py ( ResponseParserFactory.create_parser, { '5cf11c9acecd1f60a013f6facbe0f294daa3f390', }, ), ( ResponseParser.parse, { 'c2153eac3789855f4fc6a816a1f30a6afe0cf969', }, ), ( ResponseParser._create_event_stream, { '0564ba55383a71cc1ba3e5be7110549d7e9992f5', }, ), ( RestXMLParser._create_event_stream, { '0564ba55383a71cc1ba3e5be7110549d7e9992f5', }, ), ( EC2QueryParser._create_event_stream, { '0564ba55383a71cc1ba3e5be7110549d7e9992f5', }, ), ( QueryParser._create_event_stream, { '0564ba55383a71cc1ba3e5be7110549d7e9992f5', }, ), ( JSONParser._create_event_stream, { '0564ba55383a71cc1ba3e5be7110549d7e9992f5', }, ), ( JSONParser._do_parse, { '9c3d5832e6c55a87630128cc8b9121579ef4a708', }, ), ( JSONParser._handle_event_stream, { '3cf7bb1ecff0d72bafd7e7fd6625595b4060abd6', }, ), ( BaseRpcV2Parser._do_parse, { 'e2d884a116d830f57c5ca41f315d3baac49372eb', }, ), ( RestJSONParser._create_event_stream, { '0564ba55383a71cc1ba3e5be7110549d7e9992f5', }, ), ( RpcV2CBORParser.EVENT_STREAM_PARSER_CLS, { '10c1f773f07e22929ecac791729c1a30a4091bd3', }, ), ( RpcV2CBORParser._handle_event_stream, { '2aa007aaca55b37c7e327b7ef8c86237f19690cc', }, ), ( create_parser, { '37e9f1c3b60de17f477a9b79eae8e1acaa7c89d7', }, ), # regions.py ( EndpointRulesetResolver.construct_endpoint, { '05e4f37e807b57bf9ff37dbe870308b684c62c02', }, ), ( EndpointRulesetResolver._get_provider_params, { '8f4b9bd22516c9f00fc6655923312f415268c8c4', }, ), ( EndpointRulesetResolver._get_customized_builtins, { '41085e0e1ac19915c24339f25b8d966708905fd0', '51f00c9f658df0cd0e140aaed24b517954f9300c', }, ), # response.py ( StreamingBody, { 'e358f72191c4c1cb37d8fe90871342abf79afde2', }, ), ( get_response, { 'ea8686ae71fae32410e2f1774e5774d6715dd492', }, ), # session.py ( Session.__init__, { 'c796153d589ea6fe46a3a1afa2c460f06a1c37a2', }, ), ( Session._register_response_parser_factory, { 'bb8f7f3cc4d9ff9551f0875604747c4bb5030ff6', }, ), ( Session.create_client, { 'c796153d589ea6fe46a3a1afa2c460f06a1c37a2', }, ), ( Session._create_token_resolver, { '142df7a219db0dd9c96fd81dc9e84a764a2fe5fb', }, ), ( Session._create_credential_resolver, { '87e98d201c72d06f7fbdb4ebee2dce1c09de0fb2', }, ), ( Session.set_credentials, { '923e96e9386054d65b38a3871574dd32c0f5015f', }, ), ( Session.get_credentials, { '718da08b630569e631f93aedd65f1d9215bfc30b', }, ), ( get_session, { 'c47d588f5da9b8bde81ccc26eaef3aee19ddd901', }, ), ( Session.get_service_data, { '3879b969c0c2b1d5b454006a1025deb4322ae804', 'c6b755f2eff7277ccc6023552d306ff823280404', }, ), ( Session.get_service_model, { '1c8f93e6fb9913e859e43aea9bc2546edbea8365', }, ), ( Session.get_available_regions, { '9fb4df0b7d082a74d524a4a15aaf92a2717e0358', }, ), ( Session._register_smart_defaults_factory, { 'af5fc9cf6837ed119284603ca1086e4113febec0', }, ), # signers.py ( RequestSigner.handler, { '371909df136a0964ef7469a63d25149176c2b442', }, ), ( RequestSigner.sign, { '2f1f45a6fcfcca1be2c5e292c9e1b80453e7fa57', 'deb7af66bc099801810eee5b00db83da2563d0a3', }, ), ( RequestSigner.get_auth, { '13e90d57d536179621ac012ace97e4c2cbaa096e', }, ), ( RequestSigner.get_auth_instance, { '13e90d57d536179621ac012ace97e4c2cbaa096e', }, ), ( RequestSigner._choose_signer, { 'bd0e9784029b8aa182b5aec73910d94cb67c36b0', '23861aeaf48509e59c131547d98cdeb4fc4af1d3', }, ), ( RequestSigner.generate_presigned_url, { '417682868eacc10bf4c65f3dfbdba7d20d9250db', }, ), ( add_generate_presigned_url, { '5820f74ac46b004eb79e00eea1adc467bcf4defe', }, ), ( generate_presigned_url, { 'd03631d6810e2453b8874bc76619927b694a4207', }, ), ( S3PostPresigner.generate_presigned_post, { '01c61dc0f33392a19def738afc634f5def401e32', }, ), ( add_generate_presigned_post, { 'e30360f2bd893fabf47f5cdb04b0de420ccd414d', }, ), ( generate_presigned_post, { 'a3a834a08be2cf76c20ea137ba6b28e7a12f58ed', 'd93240c58dcda7b63cf2b7144ee0fea110f0e762', }, ), ( add_generate_db_auth_token, { 'f61014e6fac4b5c7ee7ac2d2bec15fb16fa9fbe5', }, ), ( generate_db_auth_token, { '1f37e1e5982d8528841ce6b79f229b3e23a18959', }, ), ( add_dsql_generate_db_auth_token_methods, { '95c68a1aac8ee549e11b5dc010b6bb03f9ea00ea', }, ), ( _dsql_generate_db_auth_token, { '53034b0475122209509db59fbd79a4ead70836cf', }, ), ( dsql_generate_db_connect_auth_token, { '29b5919b695113c55452f2325d0ff66dd719a647', }, ), ( dsql_generate_db_connect_admin_auth_token, { 'd7e7a4899b8fd3a544dd1df95196517e2cfd5c84', }, ), # tokens.py ( create_token_resolver, { '7e502da18a02d4f0224568041e3a7ef6a67b2de8', }, ), ( DeferredRefreshableToken.__init__, { '199254ed7e211119bdebf285c5d9a9789f6dc540', }, ), ( DeferredRefreshableToken.get_frozen_token, { '846a689a25550c63d2a460555dc27148abdcc992', }, ), ( DeferredRefreshableToken._refresh, { '92af1e549b5719caa246a81493823a37a684d017', }, ), ( DeferredRefreshableToken._protected_refresh, { 'bd5c1911626e420005e0e60d583a73c68925f4b6', }, ), ( SSOTokenProvider._attempt_create_token, { '9cf7b75618a253d585819485e5da641cef129d46', }, ), ( SSOTokenProvider._refresh_access_token, { '6263c009e6d86011ca1226d61ea95163bbfca258', }, ), ( SSOTokenProvider._refresher, { 'd89b3446344826e5b740840c12fd9279810c45d2', }, ), ( SSOTokenProvider.load_token, { 'fb9114c12952ba47b50e201057e3f5664abdb9d8', }, ), # utils.py ( ContainerMetadataFetcher.__init__, { '46d90a7249ba8389feb487779b0a02e6faa98e57', }, ), ( ContainerMetadataFetcher.retrieve_full_uri, { '2c7080f7d6ee5a3dacc1b690945c045dba1b1d21', }, ), ( ContainerMetadataFetcher.retrieve_uri, { '9067ffe122e6fcff4a7f4dd2b7f5b3de5e1ea4ec', }, ), ( ContainerMetadataFetcher._retrieve_credentials, { 'b00694931af86ef1a9305ad29328030ee366cea9', }, ), ( ContainerMetadataFetcher._get_response, { 'b2c2fe2d74ce1894168e8e052c4e97cc70539b1a', }, ), ( IMDSFetcher.__init__, { '50ea982d3b94d7301d39480f827cfc1502800cb4', }, ), ( IMDSFetcher._get_request, { '118354ef768da5a7402d5d2bf6f55b4fbb2525e4', }, ), ( IMDSFetcher._fetch_metadata_token, { '12225b35a73130632038785a8c2e6fbaaf9de1f4', }, ), ( IMDSFetcher._default_retry, { '362ce5eff50bfb74e58fbdd3f44146a87958318a', }, ), ( IMDSFetcher._is_non_ok_response, { '448b80545b1946ec44ff19ebca8d4993872a6281', }, ), ( IMDSFetcher._is_empty, { '241b141c9c352a4ef72964f8399d46cbe9a5aebc', }, ), ( IMDSFetcher._log_imds_response, { 'dcbe619ce2ddb8b5015f128612d86dd8a5dd31e8', }, ), ( InstanceMetadataFetcher.retrieve_iam_role_credentials, { '40f31ba06abb9853c2e6fea68846742bd3eda919', }, ), ( InstanceMetadataFetcher._get_iam_role, { '80073d7adc9fb604bc6235af87241f5efc296ad7', }, ), ( InstanceMetadataFetcher._get_credentials, { '1a64f59a3ca70b83700bd14deeac25af14100d58', }, ), ( InstanceMetadataFetcher._is_invalid_json, { '97818b51182a2507c99876a40155adda0451dd82', }, ), ( InstanceMetadataFetcher._needs_retry_for_role_name, { 'ca9557fb8e58d03e09d77f9fb63d21afb4689b58', }, ), ( InstanceMetadataFetcher._needs_retry_for_credentials, { 'e7e5a8ce541110eb79bf98414171d3a1c137e32b', }, ), ( S3RegionRedirectorv2.redirect_from_error, { '2a715115e94bddcea4cce936bf7c7013f1f6ecdf', }, ), ( S3RegionRedirectorv2.get_bucket_region, { 'b5bbc8b010576668dc2812d657c4b48af79e8f99', }, ), ( S3RegionRedirector.redirect_from_error, { '5db904d0311db5c875aed0f6a78278f173ca40a7', }, ), ( S3RegionRedirector.get_bucket_region, { 'b5bbc8b010576668dc2812d657c4b48af79e8f99', }, ), ( InstanceMetadataRegionFetcher.retrieve_region, { '0134024f0aa2d2b49ec436ea8058c1eca8fac4af', }, ), ( InstanceMetadataRegionFetcher._get_region, { '16e8fc546958471650eef233b0fd287758293019', }, ), ( IMDSRegionProvider.provide, { '09d1b70bc1dd7a37cb9ffd437acd71283b9142e9', }, ), ( IMDSRegionProvider._get_instance_metadata_region, { '4631ced79cff143de5d3fdf03cd69720778f141b', }, ), ( IMDSRegionProvider._create_fetcher, { '18da52c786a20d91615258a8127b566688ecbb39', }, ), ( IdentityCache.get_credentials, { 'baf98c4caaddfa0594745eb490c327c65cff8920', }, ), ( S3ExpressIdentityCache.get_credentials.__wrapped__, { '71f2ae5e0ea32e9bbac6f318cba963700e23b9a0', }, ), ( S3ExpressIdentityCache.build_refresh_callback, { '0e833cc5e30b76fa13e8caf5c024fe2a21c10f22', }, ), ( S3ExpressIdentityResolver.__init__, { '148a10274d3268dd42df05d3bcfb98c668f01086', }, ), ( create_nested_client, { '619a190d84c4871c0e3c4828b0d6ea607d8b89da', }, ), # waiter.py ( NormalizedOperationMethod.__call__, { '79723632d023739aa19c8a899bc2b814b8ab12ff', }, ), ( Waiter.wait, { 'be33b3d947f559950943305d35daa6a8ece33ea4', }, ), ( create_waiter_with_client, { 'e6ea06674b6fdf9157c95757a12b3c9c35af531c', '13d661baedd3db97e5291331ceebd4bbafaccf18', }, ), # handlers.py ( inject_presigned_url_rds, { 'b5d45b339686346e81b255d4e8c36e76d3fe6a78', }, ), ( inject_presigned_url_ec2, { '48e09a5e4e95577e716be30f2d2706949261a07f', }, ), ( parse_get_bucket_location, { '64ffbf5c6aa6ebd083f49371000fa046d0de1fc6', }, ), ( check_for_200_error, { '3a00f0bea409528f8457d6569aecf05998094386', }, ), # httpsession.py ( URLLib3Session, { 'c65ca250524de59d89a32f213f38c7403666bfef', }, ), ( EndpointDiscoveryHandler.discover_endpoint, { 'd87eff9008356a6aaa9b7078f23ba7a9ff0c7a60', }, ), ( EndpointDiscoveryManager.describe_endpoint, { '2d7c40eec571a14e6e3968b710c23677ae3685e6', }, ), ( EndpointDiscoveryManager._refresh_current_endpoints, { 'f8a51047c8f395d9458a904e778a3ac156a11911', }, ), # retries/adaptive.py # See comments in AsyncTokenBucket, we completely replace the ClientRateLimiter # implementation from botocore. ( adaptive.ClientRateLimiter, { '9dbf36d36614a4a2e2719ca7e4382aa4694caae3', }, ), ( adaptive.register_retry_handler, { '96c073719a3d5d41d1ca7ae5f7e31bbb431c75b3', }, ), # retries/standard.py ( standard.register_retry_handler, { 'da0ae35712211bc38938e93c4af8b7aeb999084e', '09035248202bfd94c7ae7c532e515ec6fe2fab4b', }, ), ( standard.RetryHandler.needs_retry, { '89a4148d7f4af9d2795d1d0189293528aa668b59', }, ), ( standard.RetryPolicy.should_retry, { 'b30eadcb94dadcdb90a5810cdeb2e3a0bc0c74c9', }, ), ( standard.StandardRetryConditions.__init__, { 'e17de49a447769160964a2da926b7d72544efd48', }, ), ( standard.StandardRetryConditions.is_retryable, { '558a0f0b4d30f996e046779fe233f587611ca5c7', }, ), ( standard.OrRetryChecker.is_retryable, { '5ef0b84b1ef3a49bc193d76a359dbd314682856b', }, ), # retries/special.py ( special.RetryDDBChecksumError.is_retryable, { '6c6e0945b0989b13fd8e7d78dbfcde307a131eae', }, ), # retries/bucket.py # See comments in AsyncTokenBucket, we completely replace the TokenBucket # implementation from botocore. ( TokenBucket, { 'ce932001b13e256d1a2cc625094989fff087d484', }, ), # awsresponse.py ( AWSResponse.content, { '307a4eb1d46360ef808a876d7d00cbbde6198eb1', }, ), ( AWSResponse.text, { 'a724100ba9f6d51b333b8fe470fac46376d5044a', }, ), # httpchecksum.py ( handle_checksum_body, { 'f019114f7fc3a4e200157b9689ed8a1cc9d72825', }, ), ( _handle_streaming_response, { '7ce971e012f9d4b04889f0af83f67281ed6a9e6e', '2fb57a6073db3052ca22c335e779cd991357088d', }, ), ( StreamingChecksumBody, { '2772706fc4f49bdd5ea810d255fc82b69451b9ac', }, ), ( _handle_bytes_response, { '0761c4590c6addbe8c674e40fca9f7dd375a184b', '11dce986975df44f08ff61d7e86cba4f92f7c19f', }, ), ( AwsChunkedWrapper.read, { '226db2259073a2b2e05f999e8ef55210394693d8', }, ), ( AwsChunkedWrapper._make_chunk, { '097361692f0fd6c863a17dd695739629982ef7e4', }, ), ( AwsChunkedWrapper.__iter__, { '261e26d1061655555fe3dcb2689d963e43f80fb0', }, ), ( apply_request_checksum, { '6d904d118cd9d768935e38a60a73a46c67a8d440', }, ), ( _apply_request_trailer_checksum, { '6c45ef3db14240dcaa98bd48b59076d6859c0b57', }, ), # retryhandler.py ( retryhandler.create_retry_handler, { '8fee36ed89d789194585f56b8dd4f525985a5811', }, ), ( retryhandler.create_checker_from_retry_config, { 'bc43996b75ab9ffc7a4e8f20fc62805857867109', }, ), ( retryhandler._create_single_checker, { 'da29339040ab1faeaf2d80752504e4f8116686f2', }, ), ( retryhandler._create_single_response_checker, { 'dda92bb44f295a1f61750c7e1fbc176f66cb8b44', }, ), ( retryhandler.RetryHandler.__call__, { 'e599399167b1f278e4cd839170f887d60eea5bfa', }, ), ( retryhandler.MaxAttemptsDecorator.__call__, { '24b442126f0ff730be0ae64dc7158929d4d2fca7', '631b9963bec06a9d1264bbddc3ad003ae4896945', }, ), ( retryhandler.MaxAttemptsDecorator._should_retry, { '581273f875bb779a9ff796df8c8597ec551abf97', }, ), ( retryhandler.MultiChecker.__call__, { 'e8302c52e1bbbb129b6f505633a4bc4ae1e5a34f', }, ), ( retryhandler.CRC32Checker.__call__, { '882a731eaf6b0ddca68ab4032a169a0fa09a4d43', }, ), ( retryhandler.CRC32Checker._check_response, { '3ee7afd0bb1a3bf53934d77e44f619962c52b0c9', }, ), ( stub.Stubber, { 'bccf23c3733cc656b909f5130cba80dbc9540b05', '7c01f505134b5ea3f4886e2288ea7f389577efd5', '0ff1c068779d3e8a84c4da0655cfdf5861fe1b2c', }, ), ], ids=lambda x: getattr(x, "__qualname__", None), ) def test_patches(obj, digests): try: source = getsource(obj) except TypeError: obj = obj.fget source = getsource(obj) digest = hashlib.sha1(source.encode('utf-8')).hexdigest() assert digest in digests aio-libs-aiobotocore-e39c8b3/tests/test_plugin_context.py000066400000000000000000000410171510465277300237700ustar00rootroot00000000000000""" Tests for plugin context functionality in aiobotocore. These tests validate that create_nested_client() properly applies plugin context to prevent infinite loops when clients are created from within credential providers or other plugins. """ import os from unittest.mock import Mock, patch import pytest from aiobotocore.credentials import _get_client_creator from aiobotocore.utils import create_nested_client class TestCreateNestedClient: """Test the create_nested_client function.""" def test_create_nested_client_returns_async_context_manager(self, session): """Test that create_nested_client returns an async context manager.""" client_context = create_nested_client( session, 's3', region_name='us-east-1' ) # Should return an async context manager assert hasattr(client_context, '__aenter__') assert hasattr(client_context, '__aexit__') # It's now an async generator context manager, not ClientCreatorContext assert '_AsyncGeneratorContextManager' in str(type(client_context)) async def test_create_nested_client_creates_working_client(self, session): """Test that create_nested_client creates a functional client.""" client_context = create_nested_client( session, 's3', region_name='us-east-1' ) async with client_context as client: # Should be an S3 client assert client._service_model.service_name == 's3' assert hasattr(client, 'list_buckets') assert hasattr(client, 'get_object') @patch('aiobotocore.utils.set_plugin_context') @patch('aiobotocore.utils.reset_plugin_context') @patch('aiobotocore.utils.PluginContext') async def test_create_nested_client_manages_plugin_context( self, mock_plugin_context, mock_reset, mock_set, session ): """Test that create_nested_client properly manages plugin context.""" mock_ctx = Mock() mock_plugin_context.return_value = mock_ctx mock_token = Mock() mock_set.return_value = mock_token # Call create_nested_client and actually enter the context manager client_context = create_nested_client( session, 's3', region_name='us-east-1' ) async with client_context: pass # Just test that context management works # Verify plugin context was created with plugins disabled mock_plugin_context.assert_called_once_with(plugins="DISABLED") # Verify context was set and reset mock_set.assert_called_once_with(mock_ctx) mock_reset.assert_called_once_with(mock_token) @patch('aiobotocore.utils.set_plugin_context') @patch('aiobotocore.utils.reset_plugin_context') async def test_create_nested_client_resets_context_on_exception( self, mock_reset, mock_set, session ): """Test that plugin context is reset even if client creation fails.""" mock_token = Mock() mock_set.return_value = mock_token # Mock session.create_client to raise an exception with patch.object( session, 'create_client', side_effect=Exception("Test error") ): client_context = create_nested_client( session, 's3', region_name='us-east-1' ) with pytest.raises(Exception, match="Test error"): async with client_context: pass # Verify context was still reset despite the exception mock_reset.assert_called_once_with(mock_token) async def test_create_nested_client_passes_kwargs(self, session): """Test that create_nested_client passes through all kwargs.""" with patch.object(session, 'create_client') as mock_create: # Mock create_client to return a simple async context manager mock_client = Mock() # Create a proper async context manager mock class MockAsyncContextManager: async def __aenter__(self): return mock_client async def __aexit__(self, exc_type, exc_val, exc_tb): return None mock_create.return_value = MockAsyncContextManager() client_context = create_nested_client( session, 's3', region_name='us-west-2', aws_access_key_id='test-key', aws_secret_access_key='test-secret', ) async with client_context as client: assert client == mock_client mock_create.assert_called_once_with( 's3', region_name='us-west-2', aws_access_key_id='test-key', aws_secret_access_key='test-secret', ) class TestGetClientCreator: """Test the _get_client_creator function.""" def test_get_client_creator_returns_function(self, session): """Test that _get_client_creator returns a callable function.""" client_creator = _get_client_creator(session, 'us-east-1') assert callable(client_creator) def test_client_creator_function_works(self, session): """Test that the returned client creator function works.""" client_creator = _get_client_creator(session, 'us-east-1') client_context = client_creator('s3') # Now returns an async context manager from create_nested_client assert hasattr(client_context, '__aenter__') assert hasattr(client_context, '__aexit__') assert '_AsyncGeneratorContextManager' in str(type(client_context)) def test_client_creator_uses_provided_region(self, session): """Test that client creator uses the provided region.""" region = 'us-west-2' client_creator = _get_client_creator(session, region) with patch( 'aiobotocore.credentials.create_nested_client' ) as mock_create_nested: client_creator('s3') # Should be called with the specified region mock_create_nested.assert_called_once_with( session, 's3', region_name=region ) def test_client_creator_merges_kwargs(self, session): """Test that client creator merges additional kwargs.""" client_creator = _get_client_creator(session, 'us-east-1') with patch( 'aiobotocore.credentials.create_nested_client' ) as mock_create_nested: client_creator( 's3', aws_access_key_id='test-key', endpoint_url='http://localhost:9000', ) mock_create_nested.assert_called_once_with( session, 's3', region_name='us-east-1', aws_access_key_id='test-key', endpoint_url='http://localhost:9000', ) def test_client_creator_kwargs_override_region(self, session): """Test that explicit region in kwargs overrides the default.""" client_creator = _get_client_creator(session, 'us-east-1') with patch( 'aiobotocore.credentials.create_nested_client' ) as mock_create_nested: client_creator('s3', region_name='eu-west-1') mock_create_nested.assert_called_once_with( session, 's3', region_name='eu-west-1' ) def test_client_creator_uses_create_nested_client(self, session): """Test that client creator uses create_nested_client internally.""" client_creator = _get_client_creator(session, 'us-east-1') with patch( 'aiobotocore.credentials.create_nested_client' ) as mock_create_nested: client_creator('s3', aws_access_key_id='test-key') mock_create_nested.assert_called_once_with( session, 's3', region_name='us-east-1', aws_access_key_id='test-key', ) class TestPluginContextIntegration: """Integration tests for plugin context functionality.""" async def test_plugin_context_prevents_infinite_loops(self, session): """Test that plugin context prevents infinite loops in credential providers.""" # This is a more complex integration test that simulates the scenario # where a credential provider might try to create a client call_count = 0 original_create_client = session._create_client async def mock_create_client(*args, **kwargs): nonlocal call_count call_count += 1 # Simulate a credential provider trying to create a nested client if call_count == 1: # This should use create_nested_client to prevent infinite recursion create_nested_client(session, 'sts', region_name='us-east-1') # We don't actually use the nested client, just create it return await original_create_client(*args, **kwargs) with patch.object( session, '_create_client', side_effect=mock_create_client ): client_context = session.create_client( 's3', region_name='us-east-1' ) async with client_context as client: assert client._service_model.service_name == 's3' # Should have been called at least once for the main client assert call_count >= 1 async def test_plugin_context_environment_variable_handling(self, session): """Test that plugin context properly handles environment variables.""" # Test with plugins enabled via environment variable with patch.dict( os.environ, {'BOTOCORE_EXPERIMENTAL__PLUGINS': 'test_plugin'} ): with patch( 'aiobotocore.utils.PluginContext' ) as mock_plugin_context: client_context = create_nested_client( session, 's3', region_name='us-east-1' ) async with client_context: # Should create context with plugins disabled regardless of env var mock_plugin_context.assert_called_once_with( plugins="DISABLED" ) async def test_multiple_nested_clients(self, session): """Test creating multiple nested clients works correctly.""" # Create multiple nested clients s3_context = create_nested_client( session, 's3', region_name='us-east-1' ) sts_context = create_nested_client( session, 'sts', region_name='us-east-1' ) # Both should work independently async with s3_context as s3_client: assert s3_client._service_model.service_name == 's3' async with sts_context as sts_client: assert sts_client._service_model.service_name == 'sts' def test_nested_client_with_different_configs(self, session): """Test that nested clients can have different configurations.""" from aiobotocore.config import AioConfig config1 = AioConfig( region_name='us-east-1', retries={'max_attempts': 3} ) config2 = AioConfig( region_name='us-west-2', retries={'max_attempts': 5} ) client1_context = create_nested_client(session, 's3', config=config1) client2_context = create_nested_client(session, 's3', config=config2) # Both should be created successfully with different configs assert hasattr(client1_context, '__aenter__') assert hasattr(client1_context, '__aexit__') assert hasattr(client2_context, '__aenter__') assert hasattr(client2_context, '__aexit__') class TestCredentialProviderIntegration: """Test integration with credential providers.""" def test_credential_resolver_uses_get_client_creator(self, session): """Test that credential resolver can use _get_client_creator.""" from aiobotocore.credentials import create_credential_resolver # This should not raise any import errors resolver = create_credential_resolver(session, region_name='us-east-1') assert resolver is not None def test_assume_role_provider_uses_client_creator(self, session): """Test that AssumeRoleProvider can be created with client creator.""" from aiobotocore.credentials import create_credential_resolver # Create credential resolver which should use _get_client_creator internally resolver = create_credential_resolver(session, region_name='us-east-1') # Should have providers that can use client creators assert len(resolver.providers) > 0 # Check that we can access the _get_client_creator function from aiobotocore.credentials import _get_client_creator client_creator = _get_client_creator(session, 'us-east-1') assert callable(client_creator) class TestErrorHandling: """Test error handling in plugin context functions.""" async def test_create_nested_client_with_invalid_service(self, session): """Test create_nested_client with invalid service name.""" # This should still manage plugin context properly even if service is invalid with patch('aiobotocore.utils.set_plugin_context') as mock_set: with patch('aiobotocore.utils.reset_plugin_context') as mock_reset: mock_token = Mock() mock_set.return_value = mock_token # This might raise an exception for invalid service, but context should still be managed client_context = create_nested_client( session, 'invalid-service', region_name='us-east-1' ) try: async with client_context: pass # pragma: no cover except Exception: pass # We expect this might fail # Context should still be set and reset mock_set.assert_called_once() mock_reset.assert_called_once_with(mock_token) def test_plugin_context_availability(self): """Test that plugin context availability is correctly detected.""" # The functions should always be available (either real or fallback) from aiobotocore.utils import ( PluginContext, reset_plugin_context, set_plugin_context, ) assert callable(PluginContext) assert callable(set_plugin_context) assert callable(reset_plugin_context) def test_plugin_context_import_fallback(self): """Test that plugin context functions can be imported.""" # This test ensures the imports work correctly from botocore.utils import ( PluginContext, reset_plugin_context, set_plugin_context, ) # These should be callable assert callable(PluginContext) assert callable(set_plugin_context) assert callable(reset_plugin_context) def test_create_nested_client_functional_test(self, session): """Functional test that create_nested_client actually works end-to-end.""" # This test verifies the actual functionality without mocking client_context = create_nested_client( session, 's3', region_name='us-east-1' ) # Should be able to create and use the client assert client_context is not None assert hasattr(client_context, '__aenter__') assert hasattr(client_context, '__aexit__') async def test_nested_client_context_manager(self, session): """Test that nested client works as a context manager.""" client_context = create_nested_client( session, 's3', region_name='us-east-1' ) async with client_context as client: # Should have the expected service name assert client._service_model.service_name == 's3' # Should have expected methods assert hasattr(client, 'list_buckets') assert hasattr(client, 'get_object') assert hasattr(client, 'put_object') def test_get_client_creator_functional_test(self, session): """Functional test for _get_client_creator.""" client_creator = _get_client_creator(session, 'us-east-1') # Should return a function assert callable(client_creator) # Function should create client contexts s3_context = client_creator('s3') assert hasattr(s3_context, '__aenter__') assert hasattr(s3_context, '__aexit__') # Should work with different services sts_context = client_creator('sts') assert hasattr(sts_context, '__aenter__') assert hasattr(sts_context, '__aexit__') aio-libs-aiobotocore-e39c8b3/tests/test_response.py000066400000000000000000000151571510465277300225720ustar00rootroot00000000000000import asyncio import io from unittest.mock import MagicMock import pytest from botocore.exceptions import IncompleteReadError from aiobotocore import response from aiobotocore.response import AioReadTimeoutError # https://github.com/boto/botocore/blob/develop/tests/unit/test_response.py async def assert_lines(line_iterator, expected_lines): for expected_line in expected_lines: line = await line_iterator.__anext__() assert line == expected_line # We should have exhausted the iterator. with pytest.raises(StopAsyncIteration): await line_iterator.__anext__() class AsyncBytesIO(io.BytesIO): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.content = self async def read(self, amt=-1): if amt == -1: # aiohttp to regular response amt = None return super().read(amt) async def readinto(self, b): return super().readinto(b) async def _tolist(aiter): results = [] async for item in aiter: results.append(item) return results async def test_streaming_wrapper_validates_content_length(): body = AsyncBytesIO(b'1234567890') stream = response.StreamingBody(body, content_length=10) assert await stream.read() == b'1234567890' async def test_streaming_body_with_invalid_length(): body = AsyncBytesIO(b'123456789') stream = response.StreamingBody(body, content_length=10) with pytest.raises(IncompleteReadError): assert await stream.read(9) == b'123456789' # The next read will have nothing returned and raise # an IncompleteReadError because we were expectd 10 bytes, not 9. await stream.read() async def test_streaming_body_with_zero_read(): body = AsyncBytesIO(b'1234567890') stream = response.StreamingBody(body, content_length=10) chunk = await stream.read(0) assert chunk == b'' assert await stream.read() == b'1234567890' async def test_streaming_body_with_single_read(): body = AsyncBytesIO(b'123456789') stream = response.StreamingBody(body, content_length=10) with pytest.raises(IncompleteReadError): await stream.read() async def test_streaming_body_readinto(): body = AsyncBytesIO(b"123456789") stream = response.StreamingBody(body, content_length=10) chunk = bytearray(b"\x00\x00\x00\x00\x00") assert 5 == await stream.readinto(chunk) assert chunk == bytearray(b"\x31\x32\x33\x34\x35") assert 4 == await stream.readinto(chunk) assert chunk == bytearray(b"\x36\x37\x38\x39\x35") async def test_streaming_body_readinto_with_invalid_length(): body = AsyncBytesIO(b"12") stream = response.StreamingBody(body, content_length=9) chunk = bytearray(b"\xde\xad\xbe\xef") assert 2 == await stream.readinto(chunk) assert chunk == bytearray(b"\x31\x32\xbe\xef") with pytest.raises(IncompleteReadError): await stream.readinto(chunk) async def test_streaming_body_readinto_with_empty_buffer(): body = AsyncBytesIO(b"12") stream = response.StreamingBody(body, content_length=9) chunk = bytearray(b"") assert 0 == await stream.readinto(chunk) async def test_streaming_body_readinto_with_timeout(): class TimeoutBody: def __init__(self, *args, **kwargs): self.content = MagicMock() self.content.read = self.read self.url = "" async def read(self, n: int): raise asyncio.TimeoutError() stream = response.StreamingBody(TimeoutBody(), content_length=9) with pytest.raises(AioReadTimeoutError): chunk = bytearray(b"\x00\x00\x00\x00\x00") await stream.readinto(chunk) async def test_streaming_body_closes(): body = AsyncBytesIO(b'1234567890') stream = response.StreamingBody(body, content_length=10) assert body.closed is False stream.close() assert body.closed is True async def test_default_iter_behavior(): body = AsyncBytesIO(b'a' * 2048) stream = response.StreamingBody(body, content_length=2048) chunks = await _tolist(stream) assert len(chunks) == 2 assert chunks, [b'a' * 1024 == b'a' * 1024] async def test_iter_chunks_single_byte(): body = AsyncBytesIO(b'abcde') stream = response.StreamingBody(body, content_length=5) chunks = await _tolist(stream.iter_chunks(chunk_size=1)) assert chunks, [b'a', b'b', b'c', b'd' == b'e'] async def test_iter_chunks_with_leftover(): body = AsyncBytesIO(b'abcde') stream = response.StreamingBody(body, content_length=5) chunks = await _tolist(stream.iter_chunks(chunk_size=2)) assert chunks, [b'ab', b'cd' == b'e'] async def test_iter_chunks_single_chunk(): body = AsyncBytesIO(b'abcde') stream = response.StreamingBody(body, content_length=5) chunks = await _tolist(stream.iter_chunks(chunk_size=1024)) assert chunks == [b'abcde'] async def test_streaming_line_iterator(): body = AsyncBytesIO(b'1234567890\n1234567890\n12345') stream = response.StreamingBody(body, content_length=27) await assert_lines( stream.iter_lines(), [b'1234567890', b'1234567890', b'12345'], ) async def test_streaming_line_iterator_ends_newline(): body = AsyncBytesIO(b'1234567890\n1234567890\n12345\n') stream = response.StreamingBody(body, content_length=28) await assert_lines( stream.iter_lines(), [b'1234567890', b'1234567890', b'12345'], ) async def test_streaming_line_iter_chunk_sizes(): for chunk_size in range(1, 30): body = AsyncBytesIO(b'1234567890\n1234567890\n12345') stream = response.StreamingBody(body, content_length=27) await assert_lines( stream.iter_lines(chunk_size), [b'1234567890', b'1234567890', b'12345'], ) async def test_streaming_body_is_an_iterator(): body = AsyncBytesIO(b'a' * 1024 + b'b' * 1024 + b'c' * 2) stream = response.StreamingBody(body, content_length=2050) assert b'a' * 1024 == await stream.__anext__() assert b'b' * 1024 == await stream.__anext__() assert b'c' * 2 == await stream.__anext__() with pytest.raises(StopAsyncIteration): await stream.__anext__() async def test_streaming_line_abstruse_newline_standard(): for chunk_size in range(1, 30): body = AsyncBytesIO(b'1234567890\r\n1234567890\r\n12345\r\n') stream = response.StreamingBody(body, content_length=31) await assert_lines( stream.iter_lines(chunk_size), [b'1234567890', b'1234567890', b'12345'], ) async def test_streaming_line_empty_body(): stream = response.StreamingBody( AsyncBytesIO(b''), content_length=0, ) await assert_lines(stream.iter_lines(), []) aio-libs-aiobotocore-e39c8b3/tests/test_session.py000066400000000000000000000030141510465277300224040ustar00rootroot00000000000000import logging import pytest from _pytest.logging import LogCaptureFixture from aiobotocore import __version__, httpsession from aiobotocore.config import AioConfig from aiobotocore.session import AioSession async def test_get_service_data(session): handler_called = False def handler(**kwargs): nonlocal handler_called handler_called = True session.register('service-data-loaded.s3', handler) await session.get_service_data('s3') assert handler_called async def test_retry( session: AioSession, caplog: LogCaptureFixture, monkeypatch ): caplog.set_level(logging.DEBUG) config = AioConfig( connect_timeout=1, read_timeout=1, # this goes through a slightly different codepath than regular retries retries={ "mode": "standard", "total_max_attempts": 3, }, ) async with session.create_client( 's3', config=config, aws_secret_access_key="xxx", aws_access_key_id="xxx", endpoint_url='http://localhost:7878', ) as client: # this needs the new style exceptions to work with pytest.raises(httpsession.EndpointConnectionError): await client.get_object(Bucket='foo', Key='bar') assert 'sleeping for' in caplog.text async def test_set_user_agent_for_session(session: AioSession): assert session.user_agent_name == "aiobotocore" assert session.user_agent_version == __version__ assert session.user_agent_extra.startswith("botocore/") aio-libs-aiobotocore-e39c8b3/tests/test_sns.py000066400000000000000000000066561510465277300215430ustar00rootroot00000000000000import json import botocore import pytest def _get_topic_policy(topic_arn: str): return { "Version": "2008-10-17", "Id": "__default_policy_ID", "Statement": [ { "Effect": "Allow", "Sid": "__default_statement_ID", "Principal": {"AWS": "*"}, "Action": [ "SNS:GetTopicAttributes", "SNS:SetTopicAttributes", "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic", "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish", "SNS:Receive", ], 'Resource': topic_arn, 'Condition': { 'StringEquals': {'AWS:SourceOwner': '123456789012'} }, } ], } async def test_topic_attributes(sns_client, topic_arn): response = await sns_client.list_topics() pytest.aio.assert_status_code(response, 200) arn1 = response['Topics'][0]['TopicArn'] topic_properties = await sns_client.get_topic_attributes(TopicArn=arn1) attributes = topic_properties['Attributes'] assert arn1 == topic_arn assert json.loads(attributes['Policy']) == _get_topic_policy(topic_arn) assert attributes['DisplayName'] == '' display_name = 'My display name' await sns_client.set_topic_attributes( TopicArn=arn1, AttributeName='DisplayName', AttributeValue=display_name ) topic_properties = await sns_client.get_topic_attributes(TopicArn=arn1) attributes = topic_properties['Attributes'] assert attributes['DisplayName'] == display_name async def test_creating_subscription(sns_client, topic_arn): response = await sns_client.subscribe( TopicArn=topic_arn, Protocol="http", Endpoint="http://httpbin.org/" ) subscription_arn = response['SubscriptionArn'] subscriptions = (await sns_client.list_subscriptions())["Subscriptions"] assert len([s for s in subscriptions if s['Protocol'] == 'http']) == 1 await sns_client.unsubscribe(SubscriptionArn=subscription_arn) subscriptions = (await sns_client.list_subscriptions())["Subscriptions"] assert len([s for s in subscriptions if s['Protocol'] == 'http']) == 0 async def test_publish_to_http(sns_client, topic_arn): response = await sns_client.subscribe( TopicArn=topic_arn, Protocol='http', Endpoint="http://httpbin.org/endpoint", ) subscription_arn = response['SubscriptionArn'] response = await sns_client.publish( TopicArn=topic_arn, Message="Test msg", Subject="my subject", ) pytest.aio.assert_status_code(response, 200) await sns_client.unsubscribe(SubscriptionArn=subscription_arn) async def test_get_missing_endpoint_attributes(sns_client): with pytest.raises(botocore.exceptions.ClientError): await sns_client.get_endpoint_attributes(EndpointArn="arn1") async def test_platform_applications(sns_client): await sns_client.create_platform_application( Name="app1", Platform="APNS", Attributes={}, ) await sns_client.create_platform_application( Name="app2", Platform="APNS", Attributes={}, ) repsonse = await sns_client.list_platform_applications() apps = repsonse['PlatformApplications'] assert len(apps) == 2 aio-libs-aiobotocore-e39c8b3/tests/test_sqs.py000066400000000000000000000037441510465277300215410ustar00rootroot00000000000000import time import pytest async def test_list_queues(sqs_client, sqs_queue_url): response = await sqs_client.list_queues() pytest.aio.assert_status_code(response, 200) assert sqs_queue_url in response['QueueUrls'] async def test_get_queue_name(sqs_client, sqs_queue_url): queue_name = sqs_queue_url.rsplit('/', 1)[-1] response = await sqs_client.get_queue_url(QueueName=queue_name) pytest.aio.assert_status_code(response, 200) assert sqs_queue_url == response['QueueUrl'] async def test_put_pull_delete_test(sqs_client, sqs_queue_url): response = await sqs_client.send_message( QueueUrl=sqs_queue_url, MessageBody='test_message_1', MessageAttributes={ 'attr1': {'DataType': 'String', 'StringValue': 'value1'} }, ) pytest.aio.assert_status_code(response, 200) response = await sqs_client.receive_message( QueueUrl=sqs_queue_url, MessageAttributeNames=['attr1'] ) pytest.aio.assert_status_code(response, 200) # Messages wont be a key if its empty assert len(response.get('Messages', [])) == 1 msg = response['Messages'][0] assert msg['Body'] == 'test_message_1' assert msg['MessageAttributes']['attr1']['StringValue'] == 'value1' receipt_handle = response['Messages'][0]['ReceiptHandle'] response = await sqs_client.delete_message( QueueUrl=sqs_queue_url, ReceiptHandle=receipt_handle ) pytest.aio.assert_status_code(response, 200) response = await sqs_client.receive_message( QueueUrl=sqs_queue_url, ) pytest.aio.assert_status_code(response, 200) assert len(response.get('Messages', [])) == 0 async def test_put_pull_wait(sqs_client, sqs_queue_url): start = time.perf_counter() response = await sqs_client.receive_message( QueueUrl=sqs_queue_url, WaitTimeSeconds=2 ) end = time.perf_counter() pytest.aio.assert_status_code(response, 200) assert 'Messages' not in response assert end - start > 1.5 aio-libs-aiobotocore-e39c8b3/tests/test_stubber.py000066400000000000000000000047201510465277300223740ustar00rootroot00000000000000from aiobotocore.awsrequest import AioAWSResponse from aiobotocore.session import AioSession from aiobotocore.stub import AioStubber from .mock_server import AIOServer async def test_add_response(): session = AioSession() async with ( AIOServer() as server, session.create_client( 's3', endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): stubber = AioStubber(s3_client) operation_name = 'put_object' service_response = dict( ETag="6805f2cfc46c0f04559748bb039d69ae", VersionId="psM2sYY4.o1501dSx8wMvnkOzSBB.V4a", ) expected_params = dict() stubber.add_response(operation_name, service_response, expected_params) assert len(stubber._queue) == 1 assert stubber._queue[0][ 'operation_name' ] == s3_client.meta.method_to_api_mapping.get(operation_name) assert isinstance(stubber._queue[0]['response'][0], AioAWSResponse) assert stubber._queue[0]['response'][1] == service_response assert stubber._queue[0]['expected_params'] == expected_params async def test_add_client_error(): session = AioSession() async with ( AIOServer() as server, session.create_client( 's3', endpoint_url=server.endpoint_url, aws_secret_access_key='xxx', aws_access_key_id='xxx', ) as s3_client, ): stubber = AioStubber(s3_client) operation_name = 'put_object' service_error_code = 'InvalidObjectState' service_message = 'Object is in invalid state' http_status_code = 400 service_error_meta = {"AdditionalInfo": "value"} response_meta = {"AdditionalResponseInfo": "value"} modeled_fields = {'StorageClass': 'foo', 'AccessTier': 'bar'} stubber.add_client_error( operation_name, service_error_code, service_message, http_status_code, service_error_meta, response_meta=response_meta, modeled_fields=modeled_fields, ) assert len(stubber._queue) == 1 assert stubber._queue[0][ 'operation_name' ] == s3_client.meta.method_to_api_mapping.get(operation_name) assert isinstance(stubber._queue[0]['response'][0], AioAWSResponse) assert stubber._queue[0]['response'][1] aio-libs-aiobotocore-e39c8b3/tests/test_version.py000066400000000000000000000150551510465277300224160ustar00rootroot00000000000000import ast import operator import re import sys from datetime import datetime from itertools import chain from pathlib import Path from typing import NamedTuple, Optional import docutils.frontend import docutils.nodes import docutils.parsers.rst import docutils.utils import requests from packaging import version from pip._internal.req import InstallRequirement from pip._internal.req.constructors import install_req_from_line from pip._vendor.packaging.specifiers import SpecifierSet import aiobotocore if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib _root_path = Path(__file__).absolute().parent.parent # date can be YYYY-MM-DD or "TBD" _rst_ver_date_str_re = re.compile( r'(?P\d+\.\d+\.\d+(\.dev\d+)?) \((?P\d{4}-\d{2}-\d{2}|TBD)\)' ) # from: https://stackoverflow.com/a/75996218 def _parse_rst(text: str) -> docutils.nodes.document: parser = docutils.parsers.rst.Parser() settings = docutils.frontend.get_default_settings( docutils.parsers.rst.Parser ) document = docutils.utils.new_document('', settings=settings) parser.parse(text, document) return document def _get_assign_target_name(node: ast.Assign): assert len(node.targets) == 1 target = node.targets[0] assert isinstance(target, ast.Name) return target.id class VersionInfo(NamedTuple): least_version: str specifier_set: SpecifierSet def _get_requirements_from_setup_py(setup_content: str): parsed = ast.parse(setup_content) top_level_vars = {"install_requires", "requires", "extras_require"} assignments = dict() for node in parsed.body: if isinstance(node, ast.Assign): target_name = _get_assign_target_name(node) if target_name not in top_level_vars: continue value = ast.literal_eval(node.value) assignments[target_name] = value return chain( assignments.get("install_requires", []), assignments.get("requires", []), assignments.get("extras_require", {}).values(), ) def _get_requirements_from_pyproject_toml(pyproject_content: str): content = tomllib.loads(pyproject_content) return chain( content["project"].get("dependencies", []), *content["project"].get("optional-dependencies", {}).values(), ) def _get_boto_module_versions( requirements, ensure_plus_one_patch_range: bool = False ): module_versions = dict() for ver in requirements: if isinstance(ver, str): ver: InstallRequirement = install_req_from_line(ver) elif isinstance(ver, list): assert len(ver) == 1 ver: InstallRequirement = install_req_from_line(ver[0]) else: assert False, f'Unsupported ver: {ver}' module = ver.req.name if module not in {'botocore', 'awscli', 'boto3'}: continue # NOTE: don't support complex versioning yet as requirements are unknown gte: Optional[version.Version] = None lt: Optional[version.Version] = None eq: Optional[version.Version] = None for spec in ver.req.specifier: if spec.operator == '>=': assert gte is None gte = version.parse(spec.version) elif spec.operator == '<': assert lt is None lt = version.parse(spec.version) elif spec.operator == '==': assert eq is None eq = version.parse(spec.version) else: assert False, f'unsupported operator: {spec.operator}' if ensure_plus_one_patch_range: assert len(gte.release) == len(lt.release) == 3, ( f'{module} gte: {gte} diff len than {lt}' ) assert lt.release == tuple( map(operator.add, gte.release, (0, 0, 1)) ), f'{module} gte: {gte} not one patch off from {lt}' module_versions[module] = VersionInfo( gte.public if gte else None, ver.req.specifier ) return module_versions def test_release_versions(): # ensures versions in CHANGES.rst + __init__.py match init_version = version.parse(aiobotocore.__version__) # the init version should be in canonical from assert str(init_version) == aiobotocore.__version__ changes_path = _root_path / 'CHANGES.rst' with changes_path.open('r') as f: changes_doc = _parse_rst(f.read()) rst_ver_str = changes_doc[0][1][0][0] # ex: 0.11.1 (2020-01-03) rst_prev_ver_str = changes_doc[0][2][0][0] rst_ver_groups = _rst_ver_date_str_re.match(rst_ver_str) rst_prev_ver_groups = _rst_ver_date_str_re.match(rst_prev_ver_str) rst_ver = version.parse(rst_ver_groups['version']) rst_prev_ver = version.parse(rst_prev_ver_groups['version']) # first the init version should match the rst version assert init_version == rst_ver # the current version must be greater than the previous version assert rst_ver > rst_prev_ver rst_date = rst_ver_groups['date'] rst_prev_date = rst_prev_ver_groups['date'] if rst_date == 'TBD': # TODO: we can now lock if we're a prerelease version pass # assert ( # rst_ver.is_prerelease # ), 'Version must be prerelease if final release date not set' else: rst_date = datetime.strptime(rst_date, '%Y-%m-%d').date() rst_prev_date = datetime.strptime(rst_prev_date, '%Y-%m-%d').date() assert rst_date >= rst_prev_date, ( 'Current release must be after last release' ) # get aioboto reqs with (_root_path / 'pyproject.toml').open() as f: content = f.read() aioboto_reqs = _get_boto_module_versions( _get_requirements_from_pyproject_toml(content), False, ) # get awscli reqs awscli_resp = requests.get( f"https://raw.githubusercontent.com/aws/aws-cli/" f"{aioboto_reqs['awscli'].least_version}/setup.py" ) awscli_reqs = _get_boto_module_versions( _get_requirements_from_setup_py(awscli_resp.text) ) assert awscli_reqs['botocore'].specifier_set.contains( aioboto_reqs['botocore'].least_version ) # get boto3 reqs boto3_resp = requests.get( f"https://raw.githubusercontent.com/boto/boto3/" f"{aioboto_reqs['boto3'].least_version}/setup.py" ) boto3_reqs = _get_boto_module_versions( _get_requirements_from_setup_py(boto3_resp.text) ) assert boto3_reqs['botocore'].specifier_set.contains( aioboto_reqs['botocore'].least_version ) print() aio-libs-aiobotocore-e39c8b3/tests/test_waiter.py000066400000000000000000000026501510465277300222210ustar00rootroot00000000000000from inspect import iscoroutinefunction import pytest from aiobotocore.waiter import ( AIOWaiter, WaiterModel, create_waiter_with_client, ) @pytest.fixture def cloudformation_waiter_model(cloudformation_client): config = cloudformation_client._get_waiter_config() return WaiterModel(config) async def test_create_waiter_with_client( cloudformation_client, cloudformation_waiter_model ): waiter = create_waiter_with_client( 'StackCreateComplete', cloudformation_waiter_model, cloudformation_client, ) assert isinstance(waiter, AIOWaiter) assert iscoroutinefunction(waiter.wait) async def test_sqs(cloudformation_client, current_http_backend: str): stack_name = 'my-stack-{current_http_backend}' cloudformation_template = """{ "AWSTemplateFormatVersion": "2010-09-09", "Resources": { "queue1": { "Type": "AWS::SQS::Queue", "Properties": { "QueueName": "my-queue" } } } }""" # Create stack resp = await cloudformation_client.create_stack( StackName=stack_name, TemplateBody=cloudformation_template ) assert resp['ResponseMetadata']['HTTPStatusCode'] == 200 # wait for complete waiter = cloudformation_client.get_waiter('stack_create_complete') await waiter.wait(StackName=stack_name) await cloudformation_client.delete_stack(StackName=stack_name) aio-libs-aiobotocore-e39c8b3/uv.lock000066400000000000000000023046731510465277300174730ustar00rootroot00000000000000version = 1 revision = 3 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", "python_full_version < '3.10'", ] [[package]] name = "aiobotocore" source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "aioitertools" }, { name = "botocore" }, { name = "jmespath" }, { name = "multidict" }, { name = "python-dateutil" }, { name = "wrapt" }, ] [package.optional-dependencies] awscli = [ { name = "awscli" }, ] boto3 = [ { name = "boto3" }, ] httpx = [ { name = "httpx" }, ] [package.dev-dependencies] botocore-dev = [ { name = "coverage" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, ] dev = [ { name = "anyio" }, { name = "dill" }, { name = "docker" }, { name = "docutils" }, { name = "moto", extra = ["awslambda", "batch", "cloudformation", "dynamodb", "s3", "server"] }, { name = "packaging" }, { name = "pip" }, { name = "pre-commit" }, { name = "requests" }, { name = "time-machine" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] [package.metadata] requires-dist = [ { name = "aiohttp", specifier = ">=3.9.2,<4.0.0" }, { name = "aioitertools", specifier = ">=0.5.1,<1.0.0" }, { name = "awscli", marker = "extra == 'awscli'", specifier = ">=1.42.46,<1.42.71" }, { name = "boto3", marker = "extra == 'boto3'", specifier = ">=1.40.46,<1.40.71" }, { name = "botocore", specifier = ">=1.40.46,<1.40.71" }, { name = "httpx", marker = "extra == 'httpx'", specifier = ">=0.25.1,<0.29" }, { name = "jmespath", specifier = ">=0.7.1,<2.0.0" }, { name = "multidict", specifier = ">=6.0.0,<7.0.0" }, { name = "python-dateutil", specifier = ">=2.1,<3.0.0" }, { name = "wrapt", specifier = ">=1.10.10,<2.0.0" }, ] provides-extras = ["awscli", "boto3", "httpx"] [package.metadata.requires-dev] botocore-dev = [ { name = "coverage", specifier = "==7.2.7" }, { name = "pytest", specifier = "==8.1.1" }, { name = "pytest-cov", specifier = "==5.0.0" }, { name = "pytest-xdist", specifier = "==3.5.0" }, ] dev = [ { name = "anyio", specifier = ">=4.11.0" }, { name = "dill", specifier = ">=0.3.3,<0.5" }, { name = "docker", specifier = ">=7.1,<8" }, { name = "docutils", specifier = ">=0.19,<0.22" }, { name = "moto", extras = ["server", "s3", "sqs", "awslambda", "dynamodb", "cloudformation", "sns", "batch", "ec2", "rds"], specifier = ">=5.0.11,<6" }, { name = "packaging", specifier = ">=24.1,<26" }, { name = "pip", specifier = ">=24.3.1,<26" }, { name = "pre-commit", specifier = ">=3.5.0,<5" }, { name = "requests", specifier = ">=2.32.3,<3" }, { name = "time-machine", specifier = ">=2.15.0,<3" }, { name = "tomli", marker = "python_full_version < '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.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, { name = "async-timeout", marker = "python_full_version < '3.11'" }, { name = "attrs" }, { name = "frozenlist" }, { name = "multidict" }, { name = "propcache" }, { name = "yarl" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/f1/8515650ac3121a9e55c7b217c60e7fae3e0134b5acfe65691781b5356929/aiohttp-3.13.0.tar.gz", hash = "sha256:378dbc57dd8cf341ce243f13fa1fa5394d68e2e02c15cd5f28eae35a70ec7f67", size = 7832348, upload-time = "2025-10-06T19:58:48.089Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/25/18/a3a9c9b7c8d400f71d1ff93c3e1520a5d53dba170f829ca9c6b2b070677b/aiohttp-3.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ca69ec38adf5cadcc21d0b25e2144f6a25b7db7bea7e730bac25075bc305eff0", size = 734428, upload-time = "2025-10-06T19:54:40.285Z" }, { url = "https://files.pythonhosted.org/packages/aa/02/f1eac06d78997e015030130ccf1c7cf864a919f97d77ff27e89c82fc3186/aiohttp-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:240f99f88a9a6beb53ebadac79a2e3417247aa756202ed234b1dbae13d248092", size = 491939, upload-time = "2025-10-06T19:54:42.113Z" }, { url = "https://files.pythonhosted.org/packages/e1/db/5d65af7cbe5f302e23b1ea5cfc156cd0c7738a0d2db531a3837d2754de94/aiohttp-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a4676b978a9711531e7cea499d4cdc0794c617a1c0579310ab46c9fdf5877702", size = 487229, upload-time = "2025-10-06T19:54:43.978Z" }, { url = "https://files.pythonhosted.org/packages/d3/d5/56c622ad3bd57ff4adc2b701f298dcc0408735a8af998cec1c66a9ce224e/aiohttp-3.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48fcdd5bc771cbbab8ccc9588b8b6447f6a30f9fe00898b1a5107098e00d6793", size = 1666118, upload-time = "2025-10-06T19:54:46.569Z" }, { url = "https://files.pythonhosted.org/packages/44/16/db236671ec3758e3a6be6977009e74016470368012a58fea4b3799546549/aiohttp-3.13.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eeea0cdd2f687e210c8f605f322d7b0300ba55145014a5dbe98bd4be6fff1f6c", size = 1633983, upload-time = "2025-10-06T19:54:48.244Z" }, { url = "https://files.pythonhosted.org/packages/19/ad/d96d7d7023e7f5215b8737cad21a7637f6d9d10fbfbfef0435d0277f71a2/aiohttp-3.13.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b3f01d5aeb632adaaf39c5e93f040a550464a768d54c514050c635adcbb9d0", size = 1725922, upload-time = "2025-10-06T19:54:49.885Z" }, { url = "https://files.pythonhosted.org/packages/88/d7/e8a5ba2bbd929ed587b2a8ea9390765daede2d8cd28dfae3a0773c6d3fbc/aiohttp-3.13.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4dc0b83e25267f42ef065ea57653de4365b56d7bc4e4cfc94fabe56998f8ee6", size = 1813770, upload-time = "2025-10-06T19:54:51.648Z" }, { url = "https://files.pythonhosted.org/packages/f9/ca/135c21e85ffeff66b80ecd8a647ca104f2e5a91c37dc86649244ddbf87ab/aiohttp-3.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:72714919ed9b90f030f761c20670e529c4af96c31bd000917dd0c9afd1afb731", size = 1667322, upload-time = "2025-10-06T19:54:53.668Z" }, { url = "https://files.pythonhosted.org/packages/f6/38/348c4343052a400968dbf2051ee3dc222bdefd95af5874cf0f04cc7a8c92/aiohttp-3.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:564be41e85318403fdb176e9e5b3e852d528392f42f2c1d1efcbeeed481126d7", size = 1553270, upload-time = "2025-10-06T19:54:56.054Z" }, { url = "https://files.pythonhosted.org/packages/47/89/71cbda30f0900ab16084769960c467a355d6b1db51668fbb821c4a4ad5ed/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:84912962071087286333f70569362e10793f73f45c48854e6859df11001eb2d3", size = 1637087, upload-time = "2025-10-06T19:54:58.548Z" }, { url = "https://files.pythonhosted.org/packages/bf/b1/5ff5fcaecccdcd5be7ff717cbde6e630760a8130e89167c3aa05b6b57707/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90b570f1a146181c3d6ae8f755de66227ded49d30d050479b5ae07710f7894c5", size = 1643443, upload-time = "2025-10-06T19:55:00.856Z" }, { url = "https://files.pythonhosted.org/packages/87/e2/1d1f202f43c8be1956f05196159064cc05dc6842a33c1397cbb1b99610af/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d71ca30257ce756e37a6078b1dff2d9475fee13609ad831eac9a6531bea903b", size = 1695571, upload-time = "2025-10-06T19:55:03.006Z" }, { url = "https://files.pythonhosted.org/packages/a4/b9/53c1df2991686f947a9651265757ea12c4afc29b351a249b73a0fc81dd3c/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:cd45eb70eca63f41bb156b7dffbe1a7760153b69892d923bdb79a74099e2ed90", size = 1539975, upload-time = "2025-10-06T19:55:04.839Z" }, { url = "https://files.pythonhosted.org/packages/93/24/345166f9c4cd2f5cc1d2173131998ee4adab0db8729126db32a7f91ed400/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5ae3a19949a27982c7425a7a5a963c1268fdbabf0be15ab59448cbcf0f992519", size = 1712866, upload-time = "2025-10-06T19:55:06.905Z" }, { url = "https://files.pythonhosted.org/packages/09/f1/e8f70462848b74d49b3115050623ecbd697889713c2c93c96616da56b2de/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ea6df292013c9f050cbf3f93eee9953d6e5acd9e64a0bf4ca16404bfd7aa9bcc", size = 1654058, upload-time = "2025-10-06T19:55:08.51Z" }, { url = "https://files.pythonhosted.org/packages/23/ba/47fd065510a8bfab5d5f6e1d97c0de672447c0a941c5021298bd7210afc3/aiohttp-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3b64f22fbb6dcd5663de5ef2d847a5638646ef99112503e6f7704bdecb0d1c4d", size = 430230, upload-time = "2025-10-06T19:55:10.178Z" }, { url = "https://files.pythonhosted.org/packages/c4/38/f5385cb79afa1f31bcaa3625a9e8d849b782edaeac09f894f46439e006a1/aiohttp-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8d877aa60d80715b2afc565f0f1aea66565824c229a2d065b31670e09fed6d7", size = 453013, upload-time = "2025-10-06T19:55:11.623Z" }, { url = "https://files.pythonhosted.org/packages/b1/db/df80cacac46cd548a736c5535b13cc18925cf6f9f83cd128cf3839842219/aiohttp-3.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99eb94e97a42367fef5fc11e28cb2362809d3e70837f6e60557816c7106e2e20", size = 741374, upload-time = "2025-10-06T19:55:13.095Z" }, { url = "https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca", size = 494956, upload-time = "2025-10-06T19:55:14.687Z" }, { url = "https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1", size = 491154, upload-time = "2025-10-06T19:55:16.661Z" }, { url = "https://files.pythonhosted.org/packages/fe/4d/ee8913c0d2c7da37fdc98673a342b51611eaa0871682b37b8430084e35b5/aiohttp-3.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aab715b1a0c37f7f11f9f1f579c6fbaa51ef569e47e3c0a4644fba46077a9409", size = 1745707, upload-time = "2025-10-06T19:55:18.376Z" }, { url = "https://files.pythonhosted.org/packages/f9/70/26b2c97e8fa68644aec43d788940984c5f3b53a8d1468d5baaa328f809c9/aiohttp-3.13.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7972c82bed87d7bd8e374b60a6b6e816d75ba4f7c2627c2d14eed216e62738e1", size = 1702404, upload-time = "2025-10-06T19:55:20.098Z" }, { url = "https://files.pythonhosted.org/packages/65/1e/c8aa3c293a0e8b18968b1b88e9bd8fb269eb67eb7449f504a4c3e175b159/aiohttp-3.13.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca8313cb852af788c78d5afdea24c40172cbfff8b35e58b407467732fde20390", size = 1805519, upload-time = "2025-10-06T19:55:21.811Z" }, { url = "https://files.pythonhosted.org/packages/51/b6/a3753fe86249eb441768658cfc00f8c4e0913b255c13be00ddb8192775e1/aiohttp-3.13.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c333a2385d2a6298265f4b3e960590f787311b87f6b5e6e21bb8375914ef504", size = 1893904, upload-time = "2025-10-06T19:55:23.462Z" }, { url = "https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3", size = 1745043, upload-time = "2025-10-06T19:55:25.208Z" }, { url = "https://files.pythonhosted.org/packages/e6/df/aad5dce268f9d4f29759c3eeb5fb5995c569d76abb267468dc1075218d5b/aiohttp-3.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ddedba3d0043349edc79df3dc2da49c72b06d59a45a42c1c8d987e6b8d175b8", size = 1604765, upload-time = "2025-10-06T19:55:27.157Z" }, { url = "https://files.pythonhosted.org/packages/1c/19/a84a0e97b2da2224c8b85e1aef5cac834d07b2903c17bff1a6bdbc7041d2/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23ca762140159417a6bbc959ca1927f6949711851e56f2181ddfe8d63512b5ad", size = 1721737, upload-time = "2025-10-06T19:55:28.854Z" }, { url = "https://files.pythonhosted.org/packages/6c/61/ca6ad390128d964a08554fd63d6df5810fb5fbc7e599cb9e617f1729ae19/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfe824d6707a5dc3c5676685f624bc0c63c40d79dc0239a7fd6c034b98c25ebe", size = 1716052, upload-time = "2025-10-06T19:55:30.563Z" }, { url = "https://files.pythonhosted.org/packages/2a/71/769e249e6625372c7d14be79b8b8c3b0592963a09793fb3d36758e60952c/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3c11fa5dd2ef773a8a5a6daa40243d83b450915992eab021789498dc87acc114", size = 1783532, upload-time = "2025-10-06T19:55:32.798Z" }, { url = "https://files.pythonhosted.org/packages/66/64/b9cd03cdbb629bc492e4a744fbe96550a8340b0cd7a0cc4a9c90cfecd8d3/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00fdfe370cffede3163ba9d3f190b32c0cfc8c774f6f67395683d7b0e48cdb8a", size = 1593072, upload-time = "2025-10-06T19:55:34.686Z" }, { url = "https://files.pythonhosted.org/packages/24/0e/87922c8cfdbd09f5e2197e9d87714a98c99c423560d44739e3af55400fe3/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6475e42ef92717a678bfbf50885a682bb360a6f9c8819fb1a388d98198fdcb80", size = 1798613, upload-time = "2025-10-06T19:55:36.393Z" }, { url = "https://files.pythonhosted.org/packages/c5/bb/a3adfe2af76e1ee9e3b5464522004b148b266bc99d7ec424ca7843d64a3c/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:77da5305a410910218b99f2a963092f4277d8a9c1f429c1ff1b026d1826bd0b6", size = 1737480, upload-time = "2025-10-06T19:55:38.043Z" }, { url = "https://files.pythonhosted.org/packages/ad/53/e124dcbd64e6365602f3493fe37a11ca5b7ac0a40822a6e2bc8260cd08e0/aiohttp-3.13.0-cp311-cp311-win32.whl", hash = "sha256:2f9d9ea547618d907f2ee6670c9a951f059c5994e4b6de8dcf7d9747b420c820", size = 429824, upload-time = "2025-10-06T19:55:39.595Z" }, { url = "https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469", size = 454137, upload-time = "2025-10-06T19:55:41.617Z" }, { url = "https://files.pythonhosted.org/packages/3a/95/7e8bdfa6e79099a086d59d42589492f1fe9d29aae3cefb58b676015ce278/aiohttp-3.13.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c272a9a18a5ecc48a7101882230046b83023bb2a662050ecb9bfcb28d9ab53a", size = 735585, upload-time = "2025-10-06T19:55:43.401Z" }, { url = "https://files.pythonhosted.org/packages/9f/20/2f1d3ee06ee94eafe516810705219bff234d09f135d6951661661d5595ae/aiohttp-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:97891a23d7fd4e1afe9c2f4473e04595e4acb18e4733b910b6577b74e7e21985", size = 490613, upload-time = "2025-10-06T19:55:45.237Z" }, { url = "https://files.pythonhosted.org/packages/74/15/ab8600ef6dc1dcd599009a81acfed2ea407037e654d32e47e344e0b08c34/aiohttp-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:475bd56492ce5f4cffe32b5533c6533ee0c406d1d0e6924879f83adcf51da0ae", size = 489750, upload-time = "2025-10-06T19:55:46.937Z" }, { url = "https://files.pythonhosted.org/packages/33/59/752640c2b86ca987fe5703a01733b00d375e6cd2392bc7574489934e64e5/aiohttp-3.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c32ada0abb4bc94c30be2b681c42f058ab104d048da6f0148280a51ce98add8c", size = 1736812, upload-time = "2025-10-06T19:55:48.917Z" }, { url = "https://files.pythonhosted.org/packages/3d/c6/dd6b86ddb852a7fdbcdc7a45b6bdc80178aef713c08279afcaee7a5a9f07/aiohttp-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4af1f8877ca46ecdd0bc0d4a6b66d4b2bddc84a79e2e8366bc0d5308e76bceb8", size = 1698535, upload-time = "2025-10-06T19:55:50.75Z" }, { url = "https://files.pythonhosted.org/packages/33/e2/27c92d205b9e8cee7661670e8e9f187931b71e26d42796b153d2a0ba6949/aiohttp-3.13.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e04ab827ec4f775817736b20cdc8350f40327f9b598dec4e18c9ffdcbea88a93", size = 1766573, upload-time = "2025-10-06T19:55:53.106Z" }, { url = "https://files.pythonhosted.org/packages/df/6a/1fc1ad71d130a30f7a207d8d958a41224c29b834463b5185efb2dbff6ad4/aiohttp-3.13.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a6d9487b9471ec36b0faedf52228cd732e89be0a2bbd649af890b5e2ce422353", size = 1865229, upload-time = "2025-10-06T19:55:55.01Z" }, { url = "https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8", size = 1750379, upload-time = "2025-10-06T19:55:57.219Z" }, { url = "https://files.pythonhosted.org/packages/ae/3d/2ec4b934f85856de1c0c18e90adc8902adadbfac2b3c0b831bfeb7214fc8/aiohttp-3.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469167d5372f5bb3aedff4fc53035d593884fff2617a75317740e885acd48b04", size = 1560798, upload-time = "2025-10-06T19:55:58.888Z" }, { url = "https://files.pythonhosted.org/packages/38/56/e23d9c3e13006e599fdce3851517c70279e177871e3e567d22cf3baf5d6c/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a9f3546b503975a69b547c9fd1582cad10ede1ce6f3e313a2f547c73a3d7814f", size = 1697552, upload-time = "2025-10-06T19:56:01.172Z" }, { url = "https://files.pythonhosted.org/packages/56/cb/caa32c2ccaeca0a3dc39129079fd2ad02f9406c3a5f7924340435b87d4cd/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6b4174fcec98601f0cfdf308ee29a6ae53c55f14359e848dab4e94009112ee7d", size = 1718609, upload-time = "2025-10-06T19:56:03.102Z" }, { url = "https://files.pythonhosted.org/packages/fb/c0/5911856fef9e40fd1ccbb8c54a90116875d5753a92c1cac66ce2059b390d/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a533873a7a4ec2270fb362ee5a0d3b98752e4e1dc9042b257cd54545a96bd8ed", size = 1735887, upload-time = "2025-10-06T19:56:04.841Z" }, { url = "https://files.pythonhosted.org/packages/0e/48/8d6f4757a24c02f0a454c043556593a00645d10583859f7156db44d8b7d3/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ce887c5e54411d607ee0959cac15bb31d506d86a9bcaddf0b7e9d63325a7a802", size = 1553079, upload-time = "2025-10-06T19:56:07.197Z" }, { url = "https://files.pythonhosted.org/packages/39/fa/e82c9445e40b50e46770702b5b6ca2f767966d53e1a5eef03583ceac6df6/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d871f6a30d43e32fc9252dc7b9febe1a042b3ff3908aa83868d7cf7c9579a59b", size = 1762750, upload-time = "2025-10-06T19:56:09.376Z" }, { url = "https://files.pythonhosted.org/packages/3d/e6/9d30554e7f1e700bfeae4ab6b153d5dc7441606a9ec5e929288fa93a1477/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:222c828243b4789d79a706a876910f656fad4381661691220ba57b2ab4547865", size = 1717461, upload-time = "2025-10-06T19:56:11.551Z" }, { url = "https://files.pythonhosted.org/packages/1f/e5/29cca547990a59ea54f0674fc01de98519fc628cfceeab6175711750eca7/aiohttp-3.13.0-cp312-cp312-win32.whl", hash = "sha256:682d2e434ff2f1108314ff7f056ce44e457f12dbed0249b24e106e385cf154b9", size = 424633, upload-time = "2025-10-06T19:56:13.316Z" }, { url = "https://files.pythonhosted.org/packages/8b/68/46dd042d7bc62eab30bafdb8569f55ef125c3a88bb174270324224f8df56/aiohttp-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a2be20eb23888df130214b91c262a90e2de1553d6fb7de9e9010cec994c0ff2", size = 451401, upload-time = "2025-10-06T19:56:15.188Z" }, { url = "https://files.pythonhosted.org/packages/86/2c/ac53efdc9c10e41399acc2395af98f835b86d0141d5c3820857eb9f6a14a/aiohttp-3.13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00243e51f16f6ec0fb021659d4af92f675f3cf9f9b39efd142aa3ad641d8d1e6", size = 730090, upload-time = "2025-10-06T19:56:16.858Z" }, { url = "https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54", size = 488041, upload-time = "2025-10-06T19:56:18.659Z" }, { url = "https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77", size = 486989, upload-time = "2025-10-06T19:56:20.371Z" }, { url = "https://files.pythonhosted.org/packages/37/b4/0e440481a0e77a551d6c5dcab5d11f1ff6b2b2ddb8dedc24f54f5caad732/aiohttp-3.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4aa995b9156ae499393d949a456a7ab0b994a8241a96db73a3b73c7a090eff6a", size = 1718331, upload-time = "2025-10-06T19:56:22.188Z" }, { url = "https://files.pythonhosted.org/packages/e6/59/76c421cc4a75bb1aceadb92f20ee6f05a990aa6960c64b59e8e0d340e3f5/aiohttp-3.13.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55ca0e95a3905f62f00900255ed807c580775174252999286f283e646d675a49", size = 1686263, upload-time = "2025-10-06T19:56:24.393Z" }, { url = "https://files.pythonhosted.org/packages/ec/ac/5095f12a79c7775f402cfc3e83651b6e0a92ade10ddf7f2c78c4fed79f71/aiohttp-3.13.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:49ce7525853a981fc35d380aa2353536a01a9ec1b30979ea4e35966316cace7e", size = 1754265, upload-time = "2025-10-06T19:56:26.365Z" }, { url = "https://files.pythonhosted.org/packages/05/d7/a48e4989bd76cc70600c505bbdd0d90ca1ad7f9053eceeb9dbcf9345a9ec/aiohttp-3.13.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2117be9883501eaf95503bd313eb4c7a23d567edd44014ba15835a1e9ec6d852", size = 1856486, upload-time = "2025-10-06T19:56:28.438Z" }, { url = "https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b", size = 1737545, upload-time = "2025-10-06T19:56:30.688Z" }, { url = "https://files.pythonhosted.org/packages/6c/a7/4fde058f1605c34a219348a83a99f14724cc64e68a42480fc03cf40f9ea3/aiohttp-3.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:703ad3f742fc81e543638a7bebddd35acadaa0004a5e00535e795f4b6f2c25ca", size = 1552958, upload-time = "2025-10-06T19:56:32.528Z" }, { url = "https://files.pythonhosted.org/packages/d1/12/0bac4d29231981e3aa234e88d1931f6ba38135ff4c2cf3afbb7895527630/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bf635c3476f4119b940cc8d94ad454cbe0c377e61b4527f0192aabeac1e9370", size = 1681166, upload-time = "2025-10-06T19:56:34.81Z" }, { url = "https://files.pythonhosted.org/packages/71/95/b829eb5f8ac1ca1d8085bb8df614c8acf3ff32e23ad5ad1173c7c9761daa/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cfe6285ef99e7ee51cef20609be2bc1dd0e8446462b71c9db8bb296ba632810a", size = 1710516, upload-time = "2025-10-06T19:56:36.787Z" }, { url = "https://files.pythonhosted.org/packages/47/6d/15ccf4ef3c254d899f62580e0c7fc717014f4d14a3ac31771e505d2c736c/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8af6391c5f2e69749d7f037b614b8c5c42093c251f336bdbfa4b03c57d6c4", size = 1731354, upload-time = "2025-10-06T19:56:38.659Z" }, { url = "https://files.pythonhosted.org/packages/46/6a/8acf6c57e03b6fdcc8b4c06392e66abaff3213ea275e41db3edb20738d91/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:12f5d820fadc5848d4559ea838aef733cf37ed2a1103bba148ac2f5547c14c29", size = 1548040, upload-time = "2025-10-06T19:56:40.578Z" }, { url = "https://files.pythonhosted.org/packages/75/7d/fbfd59ab2a83fe2578ce79ac3db49727b81e9f4c3376217ad09c03c6d279/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f1338b61ea66f4757a0544ed8a02ccbf60e38d9cfb3225888888dd4475ebb96", size = 1756031, upload-time = "2025-10-06T19:56:42.492Z" }, { url = "https://files.pythonhosted.org/packages/99/e7/cc9f0fdf06cab3ca61e6b62bff9a4b978b8ca736e9d76ddf54365673ab19/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:582770f82513419512da096e8df21ca44f86a2e56e25dc93c5ab4df0fe065bf0", size = 1714933, upload-time = "2025-10-06T19:56:45.542Z" }, { url = "https://files.pythonhosted.org/packages/db/43/7abbe1de94748a58a71881163ee280fd3217db36e8344d109f63638fe16a/aiohttp-3.13.0-cp313-cp313-win32.whl", hash = "sha256:3194b8cab8dbc882f37c13ef1262e0a3d62064fa97533d3aa124771f7bf1ecee", size = 423799, upload-time = "2025-10-06T19:56:47.779Z" }, { url = "https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21", size = 450138, upload-time = "2025-10-06T19:56:49.49Z" }, { url = "https://files.pythonhosted.org/packages/fe/c1/93bb1e35cd0c4665bb422b1ca3d87b588f4bca2656bbe9292b963d5b76a9/aiohttp-3.13.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c417f8c2e1137775569297c584a8a7144e5d1237789eae56af4faf1894a0b861", size = 733187, upload-time = "2025-10-06T19:56:51.385Z" }, { url = "https://files.pythonhosted.org/packages/5e/36/2d50eba91992d3fe7a6452506ccdab45d03685ee8d8acaa5b289384a7d4c/aiohttp-3.13.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f84b53326abf8e56ebc28a35cebf4a0f396a13a76300f500ab11fe0573bf0b52", size = 488684, upload-time = "2025-10-06T19:56:53.25Z" }, { url = "https://files.pythonhosted.org/packages/82/93/fa4b1d5ecdc7805bdf0815ef00257db4632ccf0a8bffd44f9fc4657b1677/aiohttp-3.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:990a53b9d6a30b2878789e490758e568b12b4a7fb2527d0c89deb9650b0e5813", size = 489255, upload-time = "2025-10-06T19:56:55.136Z" }, { url = "https://files.pythonhosted.org/packages/05/0f/85241f0d158da5e24e8ac9d50c0849ed24f882cafc53dc95749ef85eef09/aiohttp-3.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c811612711e01b901e18964b3e5dec0d35525150f5f3f85d0aee2935f059910a", size = 1715914, upload-time = "2025-10-06T19:56:57.286Z" }, { url = "https://files.pythonhosted.org/packages/ab/fc/c755590d6f6d2b5d1565c72d6ee658d3c30ec61acb18964d1e9bf991d9b5/aiohttp-3.13.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ee433e594d7948e760b5c2a78cc06ac219df33b0848793cf9513d486a9f90a52", size = 1665171, upload-time = "2025-10-06T19:56:59.688Z" }, { url = "https://files.pythonhosted.org/packages/3a/de/caa61e213ff546b8815aef5e931d7eae1dbe8c840a3f11ec5aa41c5ae462/aiohttp-3.13.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:19bb08e56f57c215e9572cd65cb6f8097804412c54081d933997ddde3e5ac579", size = 1755124, upload-time = "2025-10-06T19:57:02.69Z" }, { url = "https://files.pythonhosted.org/packages/fb/b7/40c3219dd2691aa35cf889b4fbb0c00e48a19092928707044bfe92068e01/aiohttp-3.13.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f27b7488144eb5dd9151cf839b195edd1569629d90ace4c5b6b18e4e75d1e63a", size = 1835949, upload-time = "2025-10-06T19:57:05.251Z" }, { url = "https://files.pythonhosted.org/packages/57/e8/66e3c32841fc0e26a09539c377aa0f3bbf6deac1957ac5182cf276c5719c/aiohttp-3.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d812838c109757a11354a161c95708ae4199c4fd4d82b90959b20914c1d097f6", size = 1714276, upload-time = "2025-10-06T19:57:07.41Z" }, { url = "https://files.pythonhosted.org/packages/6b/a5/c68e5b46ff0410fe3abfa508651b09372428f27036138beacf4ff6b7cb8c/aiohttp-3.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c20db99da682f9180fa5195c90b80b159632fb611e8dbccdd99ba0be0970620", size = 1545929, upload-time = "2025-10-06T19:57:09.336Z" }, { url = "https://files.pythonhosted.org/packages/7a/a6/4c97dc27f9935c0c0aa6e3e10e5b4548823ab5d056636bde374fcd297256/aiohttp-3.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cf8b0870047900eb1f17f453b4b3953b8ffbf203ef56c2f346780ff930a4d430", size = 1679988, upload-time = "2025-10-06T19:57:11.367Z" }, { url = "https://files.pythonhosted.org/packages/8e/1b/11f9c52fd72b786a47e796e6794883417280cdca8eb1032d8d0939928dfa/aiohttp-3.13.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b8a5557d5af3f4e3add52a58c4cf2b8e6e59fc56b261768866f5337872d596d", size = 1678031, upload-time = "2025-10-06T19:57:13.357Z" }, { url = "https://files.pythonhosted.org/packages/ea/eb/948903d40505f3a25e53e051488d2714ded3afac1f961df135f2936680f9/aiohttp-3.13.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:052bcdd80c1c54b8a18a9ea0cd5e36f473dc8e38d51b804cea34841f677a9971", size = 1726184, upload-time = "2025-10-06T19:57:15.478Z" }, { url = "https://files.pythonhosted.org/packages/44/14/c8ced38c7dfe80804dec17a671963ccf3cb282f12700ec70b1f689d8de7d/aiohttp-3.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:76484ba17b2832776581b7ab466d094e48eba74cb65a60aea20154dae485e8bd", size = 1542344, upload-time = "2025-10-06T19:57:17.611Z" }, { url = "https://files.pythonhosted.org/packages/a4/6e/f2e6bff550a51fd7c45fdab116a1dab7cc502e5d942956f10fc5c626bb15/aiohttp-3.13.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:62d8a0adcdaf62ee56bfb37737153251ac8e4b27845b3ca065862fb01d99e247", size = 1740913, upload-time = "2025-10-06T19:57:19.821Z" }, { url = "https://files.pythonhosted.org/packages/da/00/8f057300d9b598a706348abb375b3de9a253195fb615f17c0b2be2a72836/aiohttp-3.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5004d727499ecb95f7c9147dd0bfc5b5670f71d355f0bd26d7af2d3af8e07d2f", size = 1695535, upload-time = "2025-10-06T19:57:21.856Z" }, { url = "https://files.pythonhosted.org/packages/8a/ab/6919d584d8f053a14b15f0bfa3f315b3f548435c2142145459da2efa8673/aiohttp-3.13.0-cp314-cp314-win32.whl", hash = "sha256:a1c20c26af48aea984f63f96e5d7af7567c32cb527e33b60a0ef0a6313cf8b03", size = 429548, upload-time = "2025-10-06T19:57:24.285Z" }, { url = "https://files.pythonhosted.org/packages/c5/59/5d9e78de6132079066f5077d9687bf524f764a2f8207e04d8d68790060c6/aiohttp-3.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:56f7d230ec66e799fbfd8350e9544f8a45a4353f1cf40c1fea74c1780f555b8f", size = 455548, upload-time = "2025-10-06T19:57:26.136Z" }, { url = "https://files.pythonhosted.org/packages/7c/ea/7d98da03d1e9798bb99c3ca4963229150d45c9b7a3a16210c5b4a5f89e07/aiohttp-3.13.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:2fd35177dc483ae702f07b86c782f4f4b100a8ce4e7c5778cea016979023d9fd", size = 765319, upload-time = "2025-10-06T19:57:28.278Z" }, { url = "https://files.pythonhosted.org/packages/5c/02/37f29beced8213bb467c52ad509a5e3b41e6e967de2f6eaf7f8db63bea54/aiohttp-3.13.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4df1984c8804ed336089e88ac81a9417b1fd0db7c6f867c50a9264488797e778", size = 502567, upload-time = "2025-10-06T19:57:30.273Z" }, { url = "https://files.pythonhosted.org/packages/e7/22/b0afcafcfe3637bc8d7992abf08ee9452018366c0801e4e7d4efda2ed839/aiohttp-3.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e68c0076052dd911a81d3acc4ef2911cc4ef65bf7cadbfbc8ae762da24da858f", size = 507078, upload-time = "2025-10-06T19:57:32.619Z" }, { url = "https://files.pythonhosted.org/packages/49/4c/046c847b7a1993b49f3855cc3b97872d5df193d9240de835d0dc6a97b164/aiohttp-3.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc95c49853cd29613e4fe4ff96d73068ff89b89d61e53988442e127e8da8e7ba", size = 1862115, upload-time = "2025-10-06T19:57:34.758Z" }, { url = "https://files.pythonhosted.org/packages/1a/25/1449a59e3c6405da5e47b0138ee0855414dc12a8c306685d7fc3dd300e1f/aiohttp-3.13.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bdc89413117b40cc39baae08fd09cbdeb839d421c4e7dce6a34f6b54b3ac1", size = 1717147, upload-time = "2025-10-06T19:57:36.938Z" }, { url = "https://files.pythonhosted.org/packages/23/8f/50cc34ad267b38608f21c6a74327015dd08a66f1dd8e7ceac954d0953191/aiohttp-3.13.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e77a729df23be2116acc4e9de2767d8e92445fbca68886dd991dc912f473755", size = 1841443, upload-time = "2025-10-06T19:57:39.708Z" }, { url = "https://files.pythonhosted.org/packages/df/b9/b3ab1278faa0d1b8f434c85f9cf34eeb0a25016ffe1ee6bc361d09fef0ec/aiohttp-3.13.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e88ab34826d6eeb6c67e6e92400b9ec653faf5092a35f07465f44c9f1c429f82", size = 1933652, upload-time = "2025-10-06T19:57:42.33Z" }, { url = "https://files.pythonhosted.org/packages/88/e2/86050aaa3bd7021b115cdfc88477b754e8cf93ef0079867840eee22d3c34/aiohttp-3.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:019dbef24fe28ce2301419dd63a2b97250d9760ca63ee2976c2da2e3f182f82e", size = 1790682, upload-time = "2025-10-06T19:57:44.851Z" }, { url = "https://files.pythonhosted.org/packages/78/8d/9af903324c2ba24a0c4778e9bcc738b773c98dded3a4fcf8041d5211769f/aiohttp-3.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2c4aeaedd20771b7b4bcdf0ae791904445df6d856c02fc51d809d12d17cffdc7", size = 1622011, upload-time = "2025-10-06T19:57:47.025Z" }, { url = "https://files.pythonhosted.org/packages/84/97/5174971ba4986d913554ceb248b0401eb5358cb60672ea0166f9f596cd08/aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b3a8e6a2058a0240cfde542b641d0e78b594311bc1a710cbcb2e1841417d5cb3", size = 1787148, upload-time = "2025-10-06T19:57:49.149Z" }, { url = "https://files.pythonhosted.org/packages/dd/ae/8b397e980ac613ef3ddd8e996aa7a40a1828df958257800d4bb325657db3/aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8e38d55ca36c15f36d814ea414ecb2401d860de177c49f84a327a25b3ee752b", size = 1774816, upload-time = "2025-10-06T19:57:51.523Z" }, { url = "https://files.pythonhosted.org/packages/c7/54/0e8e2111dd92051c787e934b6bbf30c213daaa5e7ee5f51bca8913607492/aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a921edbe971aade1bf45bcbb3494e30ba6863a5c78f28be992c42de980fd9108", size = 1788610, upload-time = "2025-10-06T19:57:54.337Z" }, { url = "https://files.pythonhosted.org/packages/fa/dd/c9283dbfd9325ed6fa6c91f009db6344d8d370a7bcf09f36e7b2fcbfae02/aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:474cade59a447cb4019c0dce9f0434bf835fb558ea932f62c686fe07fe6db6a1", size = 1615498, upload-time = "2025-10-06T19:57:56.604Z" }, { url = "https://files.pythonhosted.org/packages/8c/f6/da76230679bd9ef175d876093f89e7fd6d6476c18505e115e3026fe5ef95/aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:99a303ad960747c33b65b1cb65d01a62ac73fa39b72f08a2e1efa832529b01ed", size = 1815187, upload-time = "2025-10-06T19:57:59.036Z" }, { url = "https://files.pythonhosted.org/packages/d5/78/394003ac738703822616f4f922705b54e5b3d8e7185831ecc1c97904174d/aiohttp-3.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bb34001fc1f05f6b323e02c278090c07a47645caae3aa77ed7ed8a3ce6abcce9", size = 1760281, upload-time = "2025-10-06T19:58:01.585Z" }, { url = "https://files.pythonhosted.org/packages/bd/b0/4bad0a9dd5910bd01c3119f8bd3d71887cd412d4105e4acddcdacf3cfa76/aiohttp-3.13.0-cp314-cp314t-win32.whl", hash = "sha256:dea698b64235d053def7d2f08af9302a69fcd760d1c7bd9988fd5d3b6157e657", size = 462608, upload-time = "2025-10-06T19:58:03.674Z" }, { url = "https://files.pythonhosted.org/packages/bd/af/ad12d592f623aae2bd1d3463201dc39c201ea362f9ddee0d03efd9e83720/aiohttp-3.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1f164699a060c0b3616459d13c1464a981fddf36f892f0a5027cbd45121fb14b", size = 496010, upload-time = "2025-10-06T19:58:05.589Z" }, { url = "https://files.pythonhosted.org/packages/40/26/6625e909ea770910d9901cb04336d0280a5a578daa352434eba5e482a1eb/aiohttp-3.13.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fcc425fb6fd2a00c6d91c85d084c6b75a61bc8bc12159d08e17c5711df6c5ba4", size = 736970, upload-time = "2025-10-06T19:58:07.563Z" }, { url = "https://files.pythonhosted.org/packages/b7/bc/ce07cc194f416f2d2539fec1237507b97496752f350738a051258be3dc08/aiohttp-3.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c2c4c9ce834801651f81d6760d0a51035b8b239f58f298de25162fcf6f8bb64", size = 493252, upload-time = "2025-10-06T19:58:10.146Z" }, { url = "https://files.pythonhosted.org/packages/e1/70/f51f30694aa4f9de3fad5b254609c263bf873a36dfa36f0370b37acb6b18/aiohttp-3.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f91e8f9053a07177868e813656ec57599cd2a63238844393cd01bd69c2e40147", size = 488585, upload-time = "2025-10-06T19:58:12.438Z" }, { url = "https://files.pythonhosted.org/packages/1f/89/4dd10ab758ba0dee025fc53345d24f5943f4d8741c1064ed3e92ca3274a8/aiohttp-3.13.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df46d9a3d78ec19b495b1107bf26e4fcf97c900279901f4f4819ac5bb2a02a4c", size = 1660915, upload-time = "2025-10-06T19:58:14.359Z" }, { url = "https://files.pythonhosted.org/packages/49/72/f3a894182b625c7f242ab90ff95d6fbf4319d39dc854568a33c8768220ec/aiohttp-3.13.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b1eb9871cbe43b6ca6fac3544682971539d8a1d229e6babe43446279679609d", size = 1625065, upload-time = "2025-10-06T19:58:16.754Z" }, { url = "https://files.pythonhosted.org/packages/a5/3d/31f310c67b33e30d1f2fe11bf5aeb82d178189e8abfcf696bed69a9713cb/aiohttp-3.13.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:62a3cddf8d9a2eae1f79585fa81d32e13d0c509bb9e7ad47d33c83b45a944df7", size = 1721983, upload-time = "2025-10-06T19:58:19.275Z" }, { url = "https://files.pythonhosted.org/packages/f1/6a/64f23ffd040409ac148a389be5c377f8edc4ab2aebdd601dbe786a30e05d/aiohttp-3.13.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0f735e680c323ee7e9ef8e2ea26425c7dbc2ede0086fa83ce9d7ccab8a089f26", size = 1810847, upload-time = "2025-10-06T19:58:21.419Z" }, { url = "https://files.pythonhosted.org/packages/51/64/9951b85fdf53aea1e9f41c715e9f4fc3e60f430fb58e0a46bc7837688e39/aiohttp-3.13.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a51839f778b0e283b43cd82bb17f1835ee2cc1bf1101765e90ae886e53e751c", size = 1658909, upload-time = "2025-10-06T19:58:23.406Z" }, { url = "https://files.pythonhosted.org/packages/ac/66/4d2c26a8a86b81a4493beb222457c6cba9f3853b6efa476a97edb4d52bb9/aiohttp-3.13.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac90cfab65bc281d6752f22db5fa90419e33220af4b4fa53b51f5948f414c0e7", size = 1551803, upload-time = "2025-10-06T19:58:25.568Z" }, { url = "https://files.pythonhosted.org/packages/a6/9a/c6b4eb325f473f3a117fab90008348fc7094ccbaeeedf811d89738454996/aiohttp-3.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:62fd54f3e6f17976962ba67f911d62723c760a69d54f5d7b74c3ceb1a4e9ef8d", size = 1632205, upload-time = "2025-10-06T19:58:27.976Z" }, { url = "https://files.pythonhosted.org/packages/55/27/6fa2a583d45f711cf2f719f0d69274739a94f3b6d3deddfd19106d2ef3b9/aiohttp-3.13.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cf2b60b65df05b6b2fa0d887f2189991a0dbf44a0dd18359001dc8fcdb7f1163", size = 1637447, upload-time = "2025-10-06T19:58:30.143Z" }, { url = "https://files.pythonhosted.org/packages/b5/2a/ca6f2ac71b6c7e6f93ad42616e0a75660d8b9400977053126b7eaeaa5132/aiohttp-3.13.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1ccedfe280e804d9a9d7fe8b8c4309d28e364b77f40309c86596baa754af50b1", size = 1692038, upload-time = "2025-10-06T19:58:32.227Z" }, { url = "https://files.pythonhosted.org/packages/4e/fa/221c09563a11ce0d22408bf41e4bd8385f2093a22ceb60eeddd1f5019bab/aiohttp-3.13.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ea01ffbe23df53ece0c8732d1585b3d6079bb8c9ee14f3745daf000051415a31", size = 1538478, upload-time = "2025-10-06T19:58:34.776Z" }, { url = "https://files.pythonhosted.org/packages/fd/09/06b8b45874ba26d7b012b9b21b6593d9a7a0a4e3375da83c74eb044caafa/aiohttp-3.13.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:19ba8625fa69523627b67f7e9901b587a4952470f68814d79cdc5bc460e9b885", size = 1708317, upload-time = "2025-10-06T19:58:38.746Z" }, { url = "https://files.pythonhosted.org/packages/63/b2/ee8c8bae8d2f17a146089bba8130cf18eb0e929d4c5262beae944da82b3a/aiohttp-3.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b14bfae90598d331b5061fd15a7c290ea0c15b34aeb1cf620464bb5ec02a602", size = 1647241, upload-time = "2025-10-06T19:58:41.233Z" }, { url = "https://files.pythonhosted.org/packages/c4/e4/455240c0433ebc7c9400c6014d5e4d09509d0aeddb8c9d22aff1f3d40500/aiohttp-3.13.0-cp39-cp39-win32.whl", hash = "sha256:cf7a4b976da219e726d0043fc94ae8169c0dba1d3a059b3c1e2c964bafc5a77d", size = 430922, upload-time = "2025-10-06T19:58:43.699Z" }, { url = "https://files.pythonhosted.org/packages/e6/e6/f49ed1869b961f41aa0421d35401dd4ee4c8ad74b3da618f7bde1a8ddc29/aiohttp-3.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b9697d15231aeaed4786f090c9c8bc3ab5f0e0a6da1e76c135a310def271020", size = 453958, upload-time = "2025-10-06T19:58:45.868Z" }, ] [[package]] name = "aioitertools" version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/de/38491a84ab323b47c7f86e94d2830e748780525f7a10c8600b67ead7e9ea/aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b", size = 19369, upload-time = "2024-09-02T03:33:40.349Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/85/13/58b70a580de00893223d61de8fea167877a3aed97d4a5e1405c9159ef925/aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796", size = 24345, upload-time = "2024-09-02T03:34:59.454Z" }, ] [[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 = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "antlr4-python3-runtime" version = "4.13.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467, upload-time = "2024-08-03T19:00:12.757Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462, upload-time = "2024-08-03T19:00:11.134Z" }, ] [[package]] name = "anyio" version = "4.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] [[package]] name = "async-timeout" version = "5.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] [[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 = "aws-sam-translator" version = "1.100.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, { name = "jsonschema" }, { name = "pydantic" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/63/2f0ef6d6612a16e8eb293d4724d9cdd07b713677d66999dfc93e084ebc87/aws_sam_translator-1.100.0.tar.gz", hash = "sha256:be4fb7eef864c971eb305af09de8e4e0bfe98a4eca122a00c069f6346aa00410", size = 343314, upload-time = "2025-08-28T23:16:30.109Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/92/af/eaeeb7f1f868a2f9578523a45de8685c1d7dfb81ae94b59cc4b3b5213e70/aws_sam_translator-1.100.0-py3-none-any.whl", hash = "sha256:643d24ab4c9c422fab19208528fdb97bbe3399735fdcc189a9af91f873b4f9e4", size = 401514, upload-time = "2025-08-28T23:16:28.251Z" }, ] [[package]] name = "aws-xray-sdk" version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "wrapt" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/6c/8e7fb2a45f20afc5c19d52807b560793fb48b0feca1de7de116b62a7893e/aws_xray_sdk-2.14.0.tar.gz", hash = "sha256:aab843c331af9ab9ba5cefb3a303832a19db186140894a523edafc024cc0493c", size = 93976, upload-time = "2024-06-04T22:11:38.124Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/41/69/b417833a8926fa5491e5346d7c233bf7d8a9b12ba1f4ef41ccea2494000c/aws_xray_sdk-2.14.0-py2.py3-none-any.whl", hash = "sha256:cfbe6feea3d26613a2a869d14c9246a844285c97087ad8f296f901633554ad94", size = 101922, upload-time = "2024-06-04T22:12:25.729Z" }, ] [[package]] name = "awscli" version = "1.42.70" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "colorama" }, { name = "docutils" }, { name = "pyyaml" }, { name = "rsa" }, { name = "s3transfer" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/dd/5753d4a3fdd07de3e18367e0e886a006bc637037c26961f7fbd73722fceb/awscli-1.42.70.tar.gz", hash = "sha256:8a500a282a17165deb60952f9cf8e1d961d5b2254ac1bfb9c6a011f3c9b15076", size = 1877774, upload-time = "2025-11-10T20:29:10.425Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ce/a2/a3da1e7576d86f5e126de28ec4d979fe1b0f1f2b87fe045f6d9faaf8fcbf/awscli-1.42.70-py3-none-any.whl", hash = "sha256:ced7cfaa2ecfbe6121313cfd7a8c90609935af48f6281b3bd4f973e3ff42c7ba", size = 4631482, upload-time = "2025-11-10T20:29:08.162Z" }, ] [[package]] name = "blinker" version = "1.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] [[package]] name = "boto3" version = "1.40.70" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] sdist = { url = "https://files.pythonhosted.org/packages/37/12/d5ac34e0536e1914dde28245f014a635056dde0427f6efa09f104d7999f4/boto3-1.40.70.tar.gz", hash = "sha256:191443707b391232ed15676bf6bba7e53caec1e71aafa12ccad2e825c5ee15cc", size = 111638, upload-time = "2025-11-10T20:29:15.199Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f3/cf/e24d08b37cd318754a8e94906c8b34b88676899aad1907ff6942311f13c4/boto3-1.40.70-py3-none-any.whl", hash = "sha256:e8c2f4f4cb36297270f1023ebe5b100333e0e88ab6457a9687d80143d2e15bf9", size = 139358, upload-time = "2025-11-10T20:29:13.512Z" }, ] [[package]] name = "botocore" version = "1.40.70" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/c1/8c4c199ae1663feee579a15861e34f10b29da11ae6ea0ad7b6a847ef3823/botocore-1.40.70.tar.gz", hash = "sha256:61b1f2cecd54d1b28a081116fa113b97bf4e17da57c62ae2c2751fe4c528af1f", size = 14444592, upload-time = "2025-11-10T20:29:04.046Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/55/d2/507fd0ee4dd574d2bdbdeac5df83f39d2cae1ffe97d4622cca6f6bab39f1/botocore-1.40.70-py3-none-any.whl", hash = "sha256:4a394ad25f5d9f1ef0bed610365744523eeb5c22de6862ab25d8c93f9f6d295c", size = 14106829, upload-time = "2025-11-10T20:29:01.101Z" }, ] [[package]] name = "certifi" version = "2025.10.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, ] [[package]] name = "cffi" version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, { url = "https://files.pythonhosted.org/packages/c0/cc/08ed5a43f2996a16b462f64a7055c6e962803534924b9b2f1371d8c00b7b/cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf", size = 184288, upload-time = "2025-09-08T23:23:48.404Z" }, { url = "https://files.pythonhosted.org/packages/3d/de/38d9726324e127f727b4ecc376bc85e505bfe61ef130eaf3f290c6847dd4/cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7", size = 180509, upload-time = "2025-09-08T23:23:49.73Z" }, { url = "https://files.pythonhosted.org/packages/9b/13/c92e36358fbcc39cf0962e83223c9522154ee8630e1df7c0b3a39a8124e2/cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c", size = 208813, upload-time = "2025-09-08T23:23:51.263Z" }, { url = "https://files.pythonhosted.org/packages/15/12/a7a79bd0df4c3bff744b2d7e52cc1b68d5e7e427b384252c42366dc1ecbc/cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165", size = 216498, upload-time = "2025-09-08T23:23:52.494Z" }, { url = "https://files.pythonhosted.org/packages/a3/ad/5c51c1c7600bdd7ed9a24a203ec255dccdd0ebf4527f7b922a0bde2fb6ed/cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534", size = 203243, upload-time = "2025-09-08T23:23:53.836Z" }, { url = "https://files.pythonhosted.org/packages/32/f2/81b63e288295928739d715d00952c8c6034cb6c6a516b17d37e0c8be5600/cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f", size = 203158, upload-time = "2025-09-08T23:23:55.169Z" }, { url = "https://files.pythonhosted.org/packages/1f/74/cc4096ce66f5939042ae094e2e96f53426a979864aa1f96a621ad128be27/cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63", size = 216548, upload-time = "2025-09-08T23:23:56.506Z" }, { url = "https://files.pythonhosted.org/packages/e8/be/f6424d1dc46b1091ffcc8964fa7c0ab0cd36839dd2761b49c90481a6ba1b/cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2", size = 218897, upload-time = "2025-09-08T23:23:57.825Z" }, { url = "https://files.pythonhosted.org/packages/f7/e0/dda537c2309817edf60109e39265f24f24aa7f050767e22c98c53fe7f48b/cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65", size = 211249, upload-time = "2025-09-08T23:23:59.139Z" }, { url = "https://files.pythonhosted.org/packages/2b/e7/7c769804eb75e4c4b35e658dba01de1640a351a9653c3d49ca89d16ccc91/cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322", size = 218041, upload-time = "2025-09-08T23:24:00.496Z" }, { url = "https://files.pythonhosted.org/packages/aa/d9/6218d78f920dcd7507fc16a766b5ef8f3b913cc7aa938e7fc80b9978d089/cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a", size = 172138, upload-time = "2025-09-08T23:24:01.7Z" }, { url = "https://files.pythonhosted.org/packages/54/8f/a1e836f82d8e32a97e6b29cc8f641779181ac7363734f12df27db803ebda/cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9", size = 182794, upload-time = "2025-09-08T23:24:02.943Z" }, ] [[package]] name = "cfgv" version = "3.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, ] [[package]] name = "cfn-lint" version = "1.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aws-sam-translator" }, { name = "jsonpatch" }, { name = "networkx", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyyaml" }, { name = "regex" }, { name = "sympy" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e2/0c/a395e0f9989b778bae6acb1dc8c57f4f2632a8f3c669144ba206e780f095/cfn_lint-1.40.0.tar.gz", hash = "sha256:ec3aedb928f04501c99742d8585251aeac073799823c60f2660f96acb9830987", size = 3359732, upload-time = "2025-09-29T16:02:09.049Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/1a/742ee754996eba40250a71aef4e08cf07802e24bf090fbc3095abbe83b90/cfn_lint-1.40.0-py3-none-any.whl", hash = "sha256:24e8e1f0a41f0928548b5960c2b0ffa458e08802ddda7dc11b75b613b8903df6", size = 5697129, upload-time = "2025-09-29T16:02:05.974Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695, upload-time = "2025-08-09T07:55:36.452Z" }, { url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153, upload-time = "2025-08-09T07:55:38.467Z" }, { url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428, upload-time = "2025-08-09T07:55:40.072Z" }, { url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627, upload-time = "2025-08-09T07:55:41.706Z" }, { url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388, upload-time = "2025-08-09T07:55:43.262Z" }, { url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077, upload-time = "2025-08-09T07:55:44.903Z" }, { url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631, upload-time = "2025-08-09T07:55:46.346Z" }, { url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210, upload-time = "2025-08-09T07:55:47.539Z" }, { url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739, upload-time = "2025-08-09T07:55:48.744Z" }, { url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825, upload-time = "2025-08-09T07:55:50.305Z" }, { url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452, upload-time = "2025-08-09T07:55:51.461Z" }, { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, { url = "https://files.pythonhosted.org/packages/c2/ca/9a0983dd5c8e9733565cf3db4df2b0a2e9a82659fd8aa2a868ac6e4a991f/charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", size = 207520, upload-time = "2025-08-09T07:57:11.026Z" }, { url = "https://files.pythonhosted.org/packages/39/c6/99271dc37243a4f925b09090493fb96c9333d7992c6187f5cfe5312008d2/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", size = 147307, upload-time = "2025-08-09T07:57:12.4Z" }, { url = "https://files.pythonhosted.org/packages/e4/69/132eab043356bba06eb333cc2cc60c6340857d0a2e4ca6dc2b51312886b3/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", size = 160448, upload-time = "2025-08-09T07:57:13.712Z" }, { url = "https://files.pythonhosted.org/packages/04/9a/914d294daa4809c57667b77470533e65def9c0be1ef8b4c1183a99170e9d/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", size = 157758, upload-time = "2025-08-09T07:57:14.979Z" }, { url = "https://files.pythonhosted.org/packages/b0/a8/6f5bcf1bcf63cb45625f7c5cadca026121ff8a6c8a3256d8d8cd59302663/charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", size = 152487, upload-time = "2025-08-09T07:57:16.332Z" }, { url = "https://files.pythonhosted.org/packages/c4/72/d3d0e9592f4e504f9dea08b8db270821c909558c353dc3b457ed2509f2fb/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", size = 150054, upload-time = "2025-08-09T07:57:17.576Z" }, { url = "https://files.pythonhosted.org/packages/20/30/5f64fe3981677fe63fa987b80e6c01042eb5ff653ff7cec1b7bd9268e54e/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", size = 161703, upload-time = "2025-08-09T07:57:20.012Z" }, { url = "https://files.pythonhosted.org/packages/e1/ef/dd08b2cac9284fd59e70f7d97382c33a3d0a926e45b15fc21b3308324ffd/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", size = 159096, upload-time = "2025-08-09T07:57:21.329Z" }, { url = "https://files.pythonhosted.org/packages/45/8c/dcef87cfc2b3f002a6478f38906f9040302c68aebe21468090e39cde1445/charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", size = 153852, upload-time = "2025-08-09T07:57:22.608Z" }, { url = "https://files.pythonhosted.org/packages/63/86/9cbd533bd37883d467fcd1bd491b3547a3532d0fbb46de2b99feeebf185e/charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", size = 99840, upload-time = "2025-08-09T07:57:23.883Z" }, { url = "https://files.pythonhosted.org/packages/ce/d6/7e805c8e5c46ff9729c49950acc4ee0aeb55efb8b3a56687658ad10c3216/charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", size = 107438, upload-time = "2025-08-09T07:57:25.287Z" }, { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, ] [[package]] name = "click" version = "8.1.8" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, ] [[package]] name = "click" version = "8.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, ] [[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 = "coverage" version = "7.2.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/45/8b/421f30467e69ac0e414214856798d4bc32da1336df745e49e49ae5c1e2a8/coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59", size = 762575, upload-time = "2023-05-29T20:08:50.273Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8", size = 200724, upload-time = "2023-05-29T20:07:03.422Z" }, { url = "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb", size = 201024, upload-time = "2023-05-29T20:07:05.694Z" }, { url = "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6", size = 229528, upload-time = "2023-05-29T20:07:07.307Z" }, { url = "https://files.pythonhosted.org/packages/d1/3a/67f5d18f911abf96857f6f7e4df37ca840e38179e2cc9ab6c0b9c3380f19/coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2", size = 227842, upload-time = "2023-05-29T20:07:09.331Z" }, { url = "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063", size = 228717, upload-time = "2023-05-29T20:07:11.38Z" }, { url = "https://files.pythonhosted.org/packages/2b/86/3dbf9be43f8bf6a5ca28790a713e18902b2d884bc5fa9512823a81dff601/coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1", size = 234632, upload-time = "2023-05-29T20:07:13.376Z" }, { url = "https://files.pythonhosted.org/packages/91/e8/469ed808a782b9e8305a08bad8c6fa5f8e73e093bda6546c5aec68275bff/coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353", size = 232875, upload-time = "2023-05-29T20:07:15.093Z" }, { url = "https://files.pythonhosted.org/packages/29/8f/4fad1c2ba98104425009efd7eaa19af9a7c797e92d40cd2ec026fa1f58cb/coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495", size = 234094, upload-time = "2023-05-29T20:07:17.013Z" }, { url = "https://files.pythonhosted.org/packages/94/4e/d4e46a214ae857be3d7dc5de248ba43765f60daeb1ab077cb6c1536c7fba/coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818", size = 203184, upload-time = "2023-05-29T20:07:18.69Z" }, { url = "https://files.pythonhosted.org/packages/1f/e9/d6730247d8dec2a3dddc520ebe11e2e860f0f98cee3639e23de6cf920255/coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850", size = 204096, upload-time = "2023-05-29T20:07:20.153Z" }, { url = "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f", size = 200895, upload-time = "2023-05-29T20:07:21.963Z" }, { url = "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe", size = 201120, upload-time = "2023-05-29T20:07:23.765Z" }, { url = "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3", size = 233178, upload-time = "2023-05-29T20:07:25.281Z" }, { url = "https://files.pythonhosted.org/packages/c1/49/4d487e2ad5d54ed82ac1101e467e8994c09d6123c91b2a962145f3d262c2/coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f", size = 230754, upload-time = "2023-05-29T20:07:27.044Z" }, { url = "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb", size = 232558, upload-time = "2023-05-29T20:07:28.743Z" }, { url = "https://files.pythonhosted.org/packages/8f/a8/12cc7b261f3082cc299ab61f677f7e48d93e35ca5c3c2f7241ed5525ccea/coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833", size = 241509, upload-time = "2023-05-29T20:07:30.434Z" }, { url = "https://files.pythonhosted.org/packages/04/fa/43b55101f75a5e9115259e8be70ff9279921cb6b17f04c34a5702ff9b1f7/coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97", size = 239924, upload-time = "2023-05-29T20:07:32.065Z" }, { url = "https://files.pythonhosted.org/packages/68/5f/d2bd0f02aa3c3e0311986e625ccf97fdc511b52f4f1a063e4f37b624772f/coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a", size = 240977, upload-time = "2023-05-29T20:07:34.184Z" }, { url = "https://files.pythonhosted.org/packages/ba/92/69c0722882643df4257ecc5437b83f4c17ba9e67f15dc6b77bad89b6982e/coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a", size = 203168, upload-time = "2023-05-29T20:07:35.869Z" }, { url = "https://files.pythonhosted.org/packages/b1/96/c12ed0dfd4ec587f3739f53eb677b9007853fd486ccb0e7d5512a27bab2e/coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562", size = 204185, upload-time = "2023-05-29T20:07:37.39Z" }, { url = "https://files.pythonhosted.org/packages/ff/d5/52fa1891d1802ab2e1b346d37d349cb41cdd4fd03f724ebbf94e80577687/coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4", size = 201020, upload-time = "2023-05-29T20:07:38.724Z" }, { url = "https://files.pythonhosted.org/packages/24/df/6765898d54ea20e3197a26d26bb65b084deefadd77ce7de946b9c96dfdc5/coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4", size = 233994, upload-time = "2023-05-29T20:07:40.274Z" }, { url = "https://files.pythonhosted.org/packages/15/81/b108a60bc758b448c151e5abceed027ed77a9523ecbc6b8a390938301841/coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01", size = 231358, upload-time = "2023-05-29T20:07:41.998Z" }, { url = "https://files.pythonhosted.org/packages/61/90/c76b9462f39897ebd8714faf21bc985b65c4e1ea6dff428ea9dc711ed0dd/coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6", size = 233316, upload-time = "2023-05-29T20:07:43.539Z" }, { url = "https://files.pythonhosted.org/packages/04/d6/8cba3bf346e8b1a4fb3f084df7d8cea25a6b6c56aaca1f2e53829be17e9e/coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d", size = 240159, upload-time = "2023-05-29T20:07:44.982Z" }, { url = "https://files.pythonhosted.org/packages/6e/ea/4a252dc77ca0605b23d477729d139915e753ee89e4c9507630e12ad64a80/coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de", size = 238127, upload-time = "2023-05-29T20:07:46.522Z" }, { url = "https://files.pythonhosted.org/packages/9f/5c/d9760ac497c41f9c4841f5972d0edf05d50cad7814e86ee7d133ec4a0ac8/coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d", size = 239833, upload-time = "2023-05-29T20:07:47.992Z" }, { url = "https://files.pythonhosted.org/packages/69/8c/26a95b08059db1cbb01e4b0e6d40f2e9debb628c6ca86b78f625ceaf9bab/coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511", size = 203463, upload-time = "2023-05-29T20:07:49.939Z" }, { url = "https://files.pythonhosted.org/packages/b7/00/14b00a0748e9eda26e97be07a63cc911108844004687321ddcc213be956c/coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3", size = 204347, upload-time = "2023-05-29T20:07:51.909Z" }, { url = "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9", size = 200725, upload-time = "2023-05-29T20:08:29.851Z" }, { url = "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2", size = 201022, upload-time = "2023-05-29T20:08:31.429Z" }, { url = "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7", size = 229102, upload-time = "2023-05-29T20:08:32.982Z" }, { url = "https://files.pythonhosted.org/packages/d9/1d/cd467fceb62c371f9adb1d739c92a05d4e550246daa90412e711226bd320/coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e", size = 227441, upload-time = "2023-05-29T20:08:35.044Z" }, { url = "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1", size = 228265, upload-time = "2023-05-29T20:08:36.861Z" }, { url = "https://files.pythonhosted.org/packages/88/8b/b0d9fe727acae907fa7f1c8194ccb6fe9d02e1c3e9001ecf74c741f86110/coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9", size = 234217, upload-time = "2023-05-29T20:08:38.837Z" }, { url = "https://files.pythonhosted.org/packages/66/2e/c99fe1f6396d93551aa352c75410686e726cd4ea104479b9af1af22367ce/coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250", size = 232466, upload-time = "2023-05-29T20:08:40.768Z" }, { url = "https://files.pythonhosted.org/packages/bb/e9/88747b40c8fb4a783b40222510ce6d66170217eb05d7f46462c36b4fa8cc/coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2", size = 233669, upload-time = "2023-05-29T20:08:42.944Z" }, { url = "https://files.pythonhosted.org/packages/b1/d5/a8e276bc005e42114468d4fe03e0a9555786bc51cbfe0d20827a46c1565a/coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb", size = 203199, upload-time = "2023-05-29T20:08:44.734Z" }, { url = "https://files.pythonhosted.org/packages/a9/0c/4a848ae663b47f1195abcb09a951751dd61f80b503303b9b9d768e0fd321/coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27", size = 204109, upload-time = "2023-05-29T20:08:46.417Z" }, { url = "https://files.pythonhosted.org/packages/67/fb/b3b1d7887e1ea25a9608b0776e480e4bbc303ca95a31fd585555ec4fff5a/coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d", size = 193207, upload-time = "2023-05-29T20:08:48.153Z" }, ] [package.optional-dependencies] toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] name = "cryptography" version = "46.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4a/9b/e301418629f7bfdf72db9e80ad6ed9d1b83c487c471803eaa6464c511a01/cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe", size = 749293, upload-time = "2025-10-01T00:29:11.856Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e0/98/7a8df8c19a335c8028414738490fc3955c0cecbfdd37fcc1b9c3d04bd561/cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663", size = 7261255, upload-time = "2025-10-01T00:27:22.947Z" }, { url = "https://files.pythonhosted.org/packages/c6/38/b2adb2aa1baa6706adc3eb746691edd6f90a656a9a65c3509e274d15a2b8/cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02", size = 4297596, upload-time = "2025-10-01T00:27:25.258Z" }, { url = "https://files.pythonhosted.org/packages/e4/27/0f190ada240003119488ae66c897b5e97149292988f556aef4a6a2a57595/cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135", size = 4450899, upload-time = "2025-10-01T00:27:27.458Z" }, { url = "https://files.pythonhosted.org/packages/85/d5/e4744105ab02fdf6bb58ba9a816e23b7a633255987310b4187d6745533db/cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92", size = 4300382, upload-time = "2025-10-01T00:27:29.091Z" }, { url = "https://files.pythonhosted.org/packages/33/fb/bf9571065c18c04818cb07de90c43fc042c7977c68e5de6876049559c72f/cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659", size = 4017347, upload-time = "2025-10-01T00:27:30.767Z" }, { url = "https://files.pythonhosted.org/packages/35/72/fc51856b9b16155ca071080e1a3ad0c3a8e86616daf7eb018d9565b99baa/cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa", size = 4983500, upload-time = "2025-10-01T00:27:32.741Z" }, { url = "https://files.pythonhosted.org/packages/c1/53/0f51e926799025e31746d454ab2e36f8c3f0d41592bc65cb9840368d3275/cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08", size = 4482591, upload-time = "2025-10-01T00:27:34.869Z" }, { url = "https://files.pythonhosted.org/packages/86/96/4302af40b23ab8aa360862251fb8fc450b2a06ff24bc5e261c2007f27014/cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5", size = 4300019, upload-time = "2025-10-01T00:27:37.029Z" }, { url = "https://files.pythonhosted.org/packages/9b/59/0be12c7fcc4c5e34fe2b665a75bc20958473047a30d095a7657c218fa9e8/cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc", size = 4950006, upload-time = "2025-10-01T00:27:40.272Z" }, { url = "https://files.pythonhosted.org/packages/55/1d/42fda47b0111834b49e31590ae14fd020594d5e4dadd639bce89ad790fba/cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b", size = 4482088, upload-time = "2025-10-01T00:27:42.668Z" }, { url = "https://files.pythonhosted.org/packages/17/50/60f583f69aa1602c2bdc7022dae86a0d2b837276182f8c1ec825feb9b874/cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1", size = 4425599, upload-time = "2025-10-01T00:27:44.616Z" }, { url = "https://files.pythonhosted.org/packages/d1/57/d8d4134cd27e6e94cf44adb3f3489f935bde85f3a5508e1b5b43095b917d/cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b", size = 4697458, upload-time = "2025-10-01T00:27:46.209Z" }, { url = "https://files.pythonhosted.org/packages/d1/2b/531e37408573e1da33adfb4c58875013ee8ac7d548d1548967d94a0ae5c4/cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee", size = 3056077, upload-time = "2025-10-01T00:27:48.424Z" }, { url = "https://files.pythonhosted.org/packages/a8/cd/2f83cafd47ed2dc5a3a9c783ff5d764e9e70d3a160e0df9a9dcd639414ce/cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb", size = 3512585, upload-time = "2025-10-01T00:27:50.521Z" }, { url = "https://files.pythonhosted.org/packages/00/36/676f94e10bfaa5c5b86c469ff46d3e0663c5dc89542f7afbadac241a3ee4/cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470", size = 2927474, upload-time = "2025-10-01T00:27:52.91Z" }, { url = "https://files.pythonhosted.org/packages/6f/cc/47fc6223a341f26d103cb6da2216805e08a37d3b52bee7f3b2aee8066f95/cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8", size = 7198626, upload-time = "2025-10-01T00:27:54.8Z" }, { url = "https://files.pythonhosted.org/packages/93/22/d66a8591207c28bbe4ac7afa25c4656dc19dc0db29a219f9809205639ede/cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a", size = 4287584, upload-time = "2025-10-01T00:27:57.018Z" }, { url = "https://files.pythonhosted.org/packages/8c/3e/fac3ab6302b928e0398c269eddab5978e6c1c50b2b77bb5365ffa8633b37/cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b", size = 4433796, upload-time = "2025-10-01T00:27:58.631Z" }, { url = "https://files.pythonhosted.org/packages/7d/d8/24392e5d3c58e2d83f98fe5a2322ae343360ec5b5b93fe18bc52e47298f5/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20", size = 4292126, upload-time = "2025-10-01T00:28:00.643Z" }, { url = "https://files.pythonhosted.org/packages/ed/38/3d9f9359b84c16c49a5a336ee8be8d322072a09fac17e737f3bb11f1ce64/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73", size = 3993056, upload-time = "2025-10-01T00:28:02.8Z" }, { url = "https://files.pythonhosted.org/packages/d6/a3/4c44fce0d49a4703cc94bfbe705adebf7ab36efe978053742957bc7ec324/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee", size = 4967604, upload-time = "2025-10-01T00:28:04.783Z" }, { url = "https://files.pythonhosted.org/packages/eb/c2/49d73218747c8cac16bb8318a5513fde3129e06a018af3bc4dc722aa4a98/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2", size = 4465367, upload-time = "2025-10-01T00:28:06.864Z" }, { url = "https://files.pythonhosted.org/packages/1b/64/9afa7d2ee742f55ca6285a54386ed2778556a4ed8871571cb1c1bfd8db9e/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f", size = 4291678, upload-time = "2025-10-01T00:28:08.982Z" }, { url = "https://files.pythonhosted.org/packages/50/48/1696d5ea9623a7b72ace87608f6899ca3c331709ac7ebf80740abb8ac673/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e", size = 4931366, upload-time = "2025-10-01T00:28:10.74Z" }, { url = "https://files.pythonhosted.org/packages/eb/3c/9dfc778401a334db3b24435ee0733dd005aefb74afe036e2d154547cb917/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36", size = 4464738, upload-time = "2025-10-01T00:28:12.491Z" }, { url = "https://files.pythonhosted.org/packages/dc/b1/abcde62072b8f3fd414e191a6238ce55a0050e9738090dc6cded24c12036/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a", size = 4419305, upload-time = "2025-10-01T00:28:14.145Z" }, { url = "https://files.pythonhosted.org/packages/c7/1f/3d2228492f9391395ca34c677e8f2571fb5370fe13dc48c1014f8c509864/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c", size = 4681201, upload-time = "2025-10-01T00:28:15.951Z" }, { url = "https://files.pythonhosted.org/packages/de/77/b687745804a93a55054f391528fcfc76c3d6bfd082ce9fb62c12f0d29fc1/cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1", size = 3022492, upload-time = "2025-10-01T00:28:17.643Z" }, { url = "https://files.pythonhosted.org/packages/60/a5/8d498ef2996e583de0bef1dcc5e70186376f00883ae27bf2133f490adf21/cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9", size = 3496215, upload-time = "2025-10-01T00:28:19.272Z" }, { url = "https://files.pythonhosted.org/packages/56/db/ee67aaef459a2706bc302b15889a1a8126ebe66877bab1487ae6ad00f33d/cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0", size = 2919255, upload-time = "2025-10-01T00:28:21.115Z" }, { url = "https://files.pythonhosted.org/packages/d5/bb/fa95abcf147a1b0bb94d95f53fbb09da77b24c776c5d87d36f3d94521d2c/cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685", size = 7248090, upload-time = "2025-10-01T00:28:22.846Z" }, { url = "https://files.pythonhosted.org/packages/b7/66/f42071ce0e3ffbfa80a88feadb209c779fda92a23fbc1e14f74ebf72ef6b/cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b", size = 4293123, upload-time = "2025-10-01T00:28:25.072Z" }, { url = "https://files.pythonhosted.org/packages/a8/5d/1fdbd2e5c1ba822828d250e5a966622ef00185e476d1cd2726b6dd135e53/cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1", size = 4439524, upload-time = "2025-10-01T00:28:26.808Z" }, { url = "https://files.pythonhosted.org/packages/c8/c1/5e4989a7d102d4306053770d60f978c7b6b1ea2ff8c06e0265e305b23516/cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c", size = 4297264, upload-time = "2025-10-01T00:28:29.327Z" }, { url = "https://files.pythonhosted.org/packages/28/78/b56f847d220cb1d6d6aef5a390e116ad603ce13a0945a3386a33abc80385/cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af", size = 4011872, upload-time = "2025-10-01T00:28:31.479Z" }, { url = "https://files.pythonhosted.org/packages/e1/80/2971f214b066b888944f7b57761bf709ee3f2cf805619a18b18cab9b263c/cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b", size = 4978458, upload-time = "2025-10-01T00:28:33.267Z" }, { url = "https://files.pythonhosted.org/packages/a5/84/0cb0a2beaa4f1cbe63ebec4e97cd7e0e9f835d0ba5ee143ed2523a1e0016/cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21", size = 4472195, upload-time = "2025-10-01T00:28:36.039Z" }, { url = "https://files.pythonhosted.org/packages/30/8b/2b542ddbf78835c7cd67b6fa79e95560023481213a060b92352a61a10efe/cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6", size = 4296791, upload-time = "2025-10-01T00:28:37.732Z" }, { url = "https://files.pythonhosted.org/packages/78/12/9065b40201b4f4876e93b9b94d91feb18de9150d60bd842a16a21565007f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023", size = 4939629, upload-time = "2025-10-01T00:28:39.654Z" }, { url = "https://files.pythonhosted.org/packages/f6/9e/6507dc048c1b1530d372c483dfd34e7709fc542765015425f0442b08547f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e", size = 4471988, upload-time = "2025-10-01T00:28:41.822Z" }, { url = "https://files.pythonhosted.org/packages/b1/86/d025584a5f7d5c5ec8d3633dbcdce83a0cd579f1141ceada7817a4c26934/cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90", size = 4422989, upload-time = "2025-10-01T00:28:43.608Z" }, { url = "https://files.pythonhosted.org/packages/4b/39/536370418b38a15a61bbe413006b79dfc3d2b4b0eafceb5581983f973c15/cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be", size = 4685578, upload-time = "2025-10-01T00:28:45.361Z" }, { url = "https://files.pythonhosted.org/packages/15/52/ea7e2b1910f547baed566c866fbb86de2402e501a89ecb4871ea7f169a81/cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c", size = 3036711, upload-time = "2025-10-01T00:28:47.096Z" }, { url = "https://files.pythonhosted.org/packages/71/9e/171f40f9c70a873e73c2efcdbe91e1d4b1777a03398fa1c4af3c56a2477a/cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62", size = 3500007, upload-time = "2025-10-01T00:28:48.967Z" }, { url = "https://files.pythonhosted.org/packages/3e/7c/15ad426257615f9be8caf7f97990cf3dcbb5b8dd7ed7e0db581a1c4759dd/cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1", size = 2918153, upload-time = "2025-10-01T00:28:51.003Z" }, { url = "https://files.pythonhosted.org/packages/25/b2/067a7db693488f19777ecf73f925bcb6a3efa2eae42355bafaafa37a6588/cryptography-46.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f25a41f5b34b371a06dad3f01799706631331adc7d6c05253f5bca22068c7a34", size = 3701860, upload-time = "2025-10-01T00:28:53.003Z" }, { url = "https://files.pythonhosted.org/packages/87/12/47c2aab2c285f97c71a791169529dbb89f48fc12e5f62bb6525c3927a1a2/cryptography-46.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e12b61e0b86611e3f4c1756686d9086c1d36e6fd15326f5658112ad1f1cc8807", size = 3429917, upload-time = "2025-10-01T00:28:55.03Z" }, { url = "https://files.pythonhosted.org/packages/b7/8c/1aabe338149a7d0f52c3e30f2880b20027ca2a485316756ed6f000462db3/cryptography-46.0.2-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1d3b3edd145953832e09607986f2bd86f85d1dc9c48ced41808b18009d9f30e5", size = 3714495, upload-time = "2025-10-01T00:28:57.222Z" }, { url = "https://files.pythonhosted.org/packages/e3/0a/0d10eb970fe3e57da9e9ddcfd9464c76f42baf7b3d0db4a782d6746f788f/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fe245cf4a73c20592f0f48da39748b3513db114465be78f0a36da847221bd1b4", size = 4243379, upload-time = "2025-10-01T00:28:58.989Z" }, { url = "https://files.pythonhosted.org/packages/7d/60/e274b4d41a9eb82538b39950a74ef06e9e4d723cb998044635d9deb1b435/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2b9cad9cf71d0c45566624ff76654e9bae5f8a25970c250a26ccfc73f8553e2d", size = 4409533, upload-time = "2025-10-01T00:29:00.785Z" }, { url = "https://files.pythonhosted.org/packages/19/9a/fb8548f762b4749aebd13b57b8f865de80258083fe814957f9b0619cfc56/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9bd26f2f75a925fdf5e0a446c0de2714f17819bf560b44b7480e4dd632ad6c46", size = 4243120, upload-time = "2025-10-01T00:29:02.515Z" }, { url = "https://files.pythonhosted.org/packages/71/60/883f24147fd4a0c5cab74ac7e36a1ff3094a54ba5c3a6253d2ff4b19255b/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7282d8f092b5be7172d6472f29b0631f39f18512a3642aefe52c3c0e0ccfad5a", size = 4408940, upload-time = "2025-10-01T00:29:04.42Z" }, { url = "https://files.pythonhosted.org/packages/d9/b5/c5e179772ec38adb1c072b3aa13937d2860509ba32b2462bf1dda153833b/cryptography-46.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c4b93af7920cdf80f71650769464ccf1fb49a4b56ae0024173c24c48eb6b1612", size = 3438518, upload-time = "2025-10-01T00:29:06.139Z" }, ] [[package]] name = "dill" version = "0.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, ] [[package]] name = "distlib" version = "0.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] name = "docker" version = "7.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "requests" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, ] [[package]] name = "docutils" version = "0.19" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/330ea8d383eb2ce973df34d1239b3b21e91cd8c865d21ff82902d952f91f/docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", size = 2056383, upload-time = "2022-07-05T20:17:31.045Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472, upload-time = "2022-07-05T20:17:26.388Z" }, ] [[package]] name = "exceptiongroup" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, ] [[package]] name = "execnet" version = "2.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, ] [[package]] name = "filelock" version = "3.19.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, ] [[package]] name = "flask" version = "3.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "click", version = "8.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "itsdangerous" }, { name = "jinja2" }, { name = "markupsafe" }, { name = "werkzeug" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, ] [[package]] name = "flask-cors" version = "6.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, { name = "werkzeug" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/37/bcfa6c7d5eec777c4c7cf45ce6b27631cebe5230caf88d85eadd63edd37a/flask_cors-6.0.1.tar.gz", hash = "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db", size = 13463, upload-time = "2025-06-11T01:32:08.518Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/17/f8/01bf35a3afd734345528f98d0353f2a978a476528ad4d7e78b70c4d149dd/flask_cors-6.0.1-py3-none-any.whl", hash = "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c", size = 13244, upload-time = "2025-06-11T01:32:07.352Z" }, ] [[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/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, { 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/c2/59/ae5cdac87a00962122ea37bb346d41b66aec05f9ce328fa2b9e216f8967b/frozenlist-1.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8b7138e5cd0647e4523d6685b0eac5d4be9a184ae9634492f25c6eb38c12a47", size = 86967, upload-time = "2025-10-06T05:37:55.607Z" }, { url = "https://files.pythonhosted.org/packages/8a/10/17059b2db5a032fd9323c41c39e9d1f5f9d0c8f04d1e4e3e788573086e61/frozenlist-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a6483e309ca809f1efd154b4d37dc6d9f61037d6c6a81c2dc7a15cb22c8c5dca", size = 49984, upload-time = "2025-10-06T05:37:57.049Z" }, { url = "https://files.pythonhosted.org/packages/4b/de/ad9d82ca8e5fa8f0c636e64606553c79e2b859ad253030b62a21fe9986f5/frozenlist-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b9290cf81e95e93fdf90548ce9d3c1211cf574b8e3f4b3b7cb0537cf2227068", size = 50240, upload-time = "2025-10-06T05:37:58.145Z" }, { url = "https://files.pythonhosted.org/packages/4e/45/3dfb7767c2a67d123650122b62ce13c731b6c745bc14424eea67678b508c/frozenlist-1.8.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59a6a5876ca59d1b63af8cd5e7ffffb024c3dc1e9cf9301b21a2e76286505c95", size = 219472, upload-time = "2025-10-06T05:37:59.239Z" }, { url = "https://files.pythonhosted.org/packages/0b/bf/5bf23d913a741b960d5c1dac7c1985d8a2a1d015772b2d18ea168b08e7ff/frozenlist-1.8.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6dc4126390929823e2d2d9dc79ab4046ed74680360fc5f38b585c12c66cdf459", size = 221531, upload-time = "2025-10-06T05:38:00.521Z" }, { url = "https://files.pythonhosted.org/packages/d0/03/27ec393f3b55860859f4b74cdc8c2a4af3dbf3533305e8eacf48a4fd9a54/frozenlist-1.8.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:332db6b2563333c5671fecacd085141b5800cb866be16d5e3eb15a2086476675", size = 219211, upload-time = "2025-10-06T05:38:01.842Z" }, { url = "https://files.pythonhosted.org/packages/3a/ad/0fd00c404fa73fe9b169429e9a972d5ed807973c40ab6b3cf9365a33d360/frozenlist-1.8.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ff15928d62a0b80bb875655c39bf517938c7d589554cbd2669be42d97c2cb61", size = 231775, upload-time = "2025-10-06T05:38:03.384Z" }, { url = "https://files.pythonhosted.org/packages/8a/c3/86962566154cb4d2995358bc8331bfc4ea19d07db1a96f64935a1607f2b6/frozenlist-1.8.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7bf6cdf8e07c8151fba6fe85735441240ec7f619f935a5205953d58009aef8c6", size = 236631, upload-time = "2025-10-06T05:38:04.609Z" }, { url = "https://files.pythonhosted.org/packages/ea/9e/6ffad161dbd83782d2c66dc4d378a9103b31770cb1e67febf43aea42d202/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:48e6d3f4ec5c7273dfe83ff27c91083c6c9065af655dc2684d2c200c94308bb5", size = 218632, upload-time = "2025-10-06T05:38:05.917Z" }, { url = "https://files.pythonhosted.org/packages/58/b2/4677eee46e0a97f9b30735e6ad0bf6aba3e497986066eb68807ac85cf60f/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:1a7607e17ad33361677adcd1443edf6f5da0ce5e5377b798fba20fae194825f3", size = 235967, upload-time = "2025-10-06T05:38:07.614Z" }, { url = "https://files.pythonhosted.org/packages/05/f3/86e75f8639c5a93745ca7addbbc9de6af56aebb930d233512b17e46f6493/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3a935c3a4e89c733303a2d5a7c257ea44af3a56c8202df486b7f5de40f37e1", size = 228799, upload-time = "2025-10-06T05:38:08.845Z" }, { url = "https://files.pythonhosted.org/packages/30/00/39aad3a7f0d98f5eb1d99a3c311215674ed87061aecee7851974b335c050/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:940d4a017dbfed9daf46a3b086e1d2167e7012ee297fef9e1c545c4d022f5178", size = 230566, upload-time = "2025-10-06T05:38:10.52Z" }, { url = "https://files.pythonhosted.org/packages/0d/4d/aa144cac44568d137846ddc4d5210fb5d9719eb1d7ec6fa2728a54b5b94a/frozenlist-1.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b9be22a69a014bc47e78072d0ecae716f5eb56c15238acca0f43d6eb8e4a5bda", size = 217715, upload-time = "2025-10-06T05:38:11.832Z" }, { url = "https://files.pythonhosted.org/packages/64/4c/8f665921667509d25a0dd72540513bc86b356c95541686f6442a3283019f/frozenlist-1.8.0-cp39-cp39-win32.whl", hash = "sha256:1aa77cb5697069af47472e39612976ed05343ff2e84a3dcf15437b232cbfd087", size = 39933, upload-time = "2025-10-06T05:38:13.061Z" }, { url = "https://files.pythonhosted.org/packages/79/bd/bcc926f87027fad5e59926ff12d136e1082a115025d33c032d1cd69ab377/frozenlist-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:7398c222d1d405e796970320036b1b563892b65809d9e5261487bb2c7f7b5c6a", size = 44121, upload-time = "2025-10-06T05:38:14.572Z" }, { url = "https://files.pythonhosted.org/packages/4c/07/9c2e4eb7584af4b705237b971b89a4155a8e57599c4483a131a39256a9a0/frozenlist-1.8.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f3b365f31c6cd4af24545ca0a244a53688cad8834e32f56831c4923b50a103", size = 40312, upload-time = "2025-10-06T05:38:15.699Z" }, { 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 = "graphql-core" version = "3.2.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] [[package]] name = "httpcore" version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] [[package]] name = "httpx" version = "0.28.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] [[package]] name = "identify" version = "2.6.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] name = "itsdangerous" version = "2.2.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, ] [[package]] name = "jinja2" version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "jmespath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] [[package]] name = "joserfc" version = "1.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b0/79/d63a882c0212e95f3ba8863a115e5ca9a5d39413b02273ddce72058e717f/joserfc-1.3.4.tar.gz", hash = "sha256:67d8413c501c239f65eefad5ae685cfbfc401aa63289fc409ef7cc331b007227", size = 197787, upload-time = "2025-09-21T15:49:34.7Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9a/78/beb171f0caac81f51695f43c87c0c20b3ce2a190cf74a3fbb0b9afe03b45/joserfc-1.3.4-py3-none-any.whl", hash = "sha256:30c845c58d441cfe32d08ac35e437812481ca8155373873b7abf80224bf601c0", size = 75638, upload-time = "2025-09-21T15:49:33.14Z" }, ] [[package]] name = "jsonpatch" version = "1.33" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpointer" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, ] [[package]] name = "jsonpath-ng" version = "1.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ply" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/86/08646239a313f895186ff0a4573452038eed8c86f54380b3ebac34d32fb2/jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c", size = 37838, upload-time = "2024-10-11T15:41:42.404Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/5a/73ecb3d82f8615f32ccdadeb9356726d6cae3a4bbc840b437ceb95708063/jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6", size = 30105, upload-time = "2024-11-20T17:58:30.418Z" }, ] [[package]] name = "jsonpointer" version = "3.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, ] [[package]] name = "jsonschema" version = "4.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, { name = "referencing" }, { name = "rpds-py" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, ] [[package]] name = "jsonschema-path" version = "0.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pathable" }, { name = "pyyaml" }, { name = "referencing" }, { name = "requests" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810, upload-time = "2025-01-24T14:33:14.652Z" }, ] [[package]] name = "jsonschema-specifications" version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, ] [[package]] name = "lazy-object-proxy" version = "1.12.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d6/2b/d5e8915038acbd6c6a9fcb8aaf923dc184222405d3710285a1fec6e262bc/lazy_object_proxy-1.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61d5e3310a4aa5792c2b599a7a78ccf8687292c8eb09cf187cca8f09cf6a7519", size = 26658, upload-time = "2025-08-22T13:42:23.373Z" }, { url = "https://files.pythonhosted.org/packages/da/8f/91fc00eeea46ee88b9df67f7c5388e60993341d2a406243d620b2fdfde57/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ca33565f698ac1aece152a10f432415d1a2aa9a42dfe23e5ba2bc255ab91f6", size = 68412, upload-time = "2025-08-22T13:42:24.727Z" }, { url = "https://files.pythonhosted.org/packages/07/d2/b7189a0e095caedfea4d42e6b6949d2685c354263bdf18e19b21ca9b3cd6/lazy_object_proxy-1.12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01c7819a410f7c255b20799b65d36b414379a30c6f1684c7bd7eb6777338c1b", size = 67559, upload-time = "2025-08-22T13:42:25.875Z" }, { url = "https://files.pythonhosted.org/packages/a3/ad/b013840cc43971582ff1ceaf784d35d3a579650eb6cc348e5e6ed7e34d28/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:029d2b355076710505c9545aef5ab3f750d89779310e26ddf2b7b23f6ea03cd8", size = 66651, upload-time = "2025-08-22T13:42:27.427Z" }, { url = "https://files.pythonhosted.org/packages/7e/6f/b7368d301c15612fcc4cd00412b5d6ba55548bde09bdae71930e1a81f2ab/lazy_object_proxy-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc6e3614eca88b1c8a625fc0a47d0d745e7c3255b21dac0e30b3037c5e3deeb8", size = 66901, upload-time = "2025-08-22T13:42:28.585Z" }, { url = "https://files.pythonhosted.org/packages/61/1b/c6b1865445576b2fc5fa0fbcfce1c05fee77d8979fd1aa653dd0f179aefc/lazy_object_proxy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:be5fe974e39ceb0d6c9db0663c0464669cf866b2851c73971409b9566e880eab", size = 26536, upload-time = "2025-08-22T13:42:29.636Z" }, { url = "https://files.pythonhosted.org/packages/01/b3/4684b1e128a87821e485f5a901b179790e6b5bc02f89b7ee19c23be36ef3/lazy_object_proxy-1.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf69cd1a6c7fe2dbcc3edaa017cf010f4192e53796538cc7d5e1fedbfa4bcff", size = 26656, upload-time = "2025-08-22T13:42:30.605Z" }, { url = "https://files.pythonhosted.org/packages/3a/03/1bdc21d9a6df9ff72d70b2ff17d8609321bea4b0d3cffd2cea92fb2ef738/lazy_object_proxy-1.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:efff4375a8c52f55a145dc8487a2108c2140f0bec4151ab4e1843e52eb9987ad", size = 68832, upload-time = "2025-08-22T13:42:31.675Z" }, { url = "https://files.pythonhosted.org/packages/3d/4b/5788e5e8bd01d19af71e50077ab020bc5cce67e935066cd65e1215a09ff9/lazy_object_proxy-1.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1192e8c2f1031a6ff453ee40213afa01ba765b3dc861302cd91dbdb2e2660b00", size = 69148, upload-time = "2025-08-22T13:42:32.876Z" }, { url = "https://files.pythonhosted.org/packages/79/0e/090bf070f7a0de44c61659cb7f74c2fe02309a77ca8c4b43adfe0b695f66/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3605b632e82a1cbc32a1e5034278a64db555b3496e0795723ee697006b980508", size = 67800, upload-time = "2025-08-22T13:42:34.054Z" }, { url = "https://files.pythonhosted.org/packages/cf/d2/b320325adbb2d119156f7c506a5fbfa37fcab15c26d13cf789a90a6de04e/lazy_object_proxy-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a61095f5d9d1a743e1e20ec6d6db6c2ca511961777257ebd9b288951b23b44fa", size = 68085, upload-time = "2025-08-22T13:42:35.197Z" }, { url = "https://files.pythonhosted.org/packages/6a/48/4b718c937004bf71cd82af3713874656bcb8d0cc78600bf33bb9619adc6c/lazy_object_proxy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:997b1d6e10ecc6fb6fe0f2c959791ae59599f41da61d652f6c903d1ee58b7370", size = 26535, upload-time = "2025-08-22T13:42:36.521Z" }, { url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" }, { url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" }, { url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" }, { url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" }, { url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" }, { url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" }, { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" }, { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" }, { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" }, { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" }, { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" }, { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" }, { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" }, { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" }, { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" }, { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" }, { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" }, { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" }, { url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" }, { url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" }, { url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" }, { url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" }, { url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" }, { url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" }, { url = "https://files.pythonhosted.org/packages/fd/dc/ab1dcba92b576ed278f358f95edee30e835463e7976a219248a8635f757d/lazy_object_proxy-1.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae575ad9b674d0029fc077c5231b3bc6b433a3d1a62a8c363df96974b5534728", size = 26648, upload-time = "2025-08-22T13:49:58.429Z" }, { url = "https://files.pythonhosted.org/packages/b6/67/b2a4ff7094f35d3dda1c7d0b3b629e8c1186b1a014370c6d20e02d4a23ce/lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31020c84005d3daa4cc0fa5a310af2066efe6b0d82aeebf9ab199292652ff036", size = 66845, upload-time = "2025-08-22T13:49:59.623Z" }, { url = "https://files.pythonhosted.org/packages/69/57/f1d038b4792dd621e2fa92dfdb4f652eee7b1d6000fe6849fc2308c3472e/lazy_object_proxy-1.12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800f32b00a47c27446a2b767df7538e6c66a3488632c402b4fb2224f9794f3c0", size = 66303, upload-time = "2025-08-22T13:50:00.941Z" }, { url = "https://files.pythonhosted.org/packages/87/df/ee5a4c0dba431ba8f77fb14388abb830d26617a15215f462d84251a91b40/lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:15400b18893f345857b9e18b9bd87bd06aba84af6ed086187add70aeaa3f93f1", size = 64865, upload-time = "2025-08-22T13:50:02.304Z" }, { url = "https://files.pythonhosted.org/packages/68/96/227207629436bf5690a32c38b8524fdb32228559e22ccb6b9cb149d8f217/lazy_object_proxy-1.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d3964fbd326578bcdfffd017ef101b6fb0484f34e731fe060ba9b8816498c36", size = 65123, upload-time = "2025-08-22T13:50:03.643Z" }, { url = "https://files.pythonhosted.org/packages/f9/84/9646e640689cba3a5b46c62cf4281dfaf3d3e224b16d48d0a24acb1ab74b/lazy_object_proxy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:424a8ab6695400845c39f13c685050eab69fa0bbac5790b201cd27375e5e41d7", size = 26413, upload-time = "2025-08-22T13:50:04.592Z" }, { url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, ] [[package]] name = "moto" version = "5.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, { name = "botocore" }, { name = "cryptography" }, { name = "jinja2" }, { name = "python-dateutil" }, { name = "requests" }, { name = "responses" }, { name = "werkzeug" }, { name = "xmltodict" }, ] sdist = { url = "https://files.pythonhosted.org/packages/39/c0/eb2c997ffb22af6878a5d369284ad0bf2356d6b22686b5350f749d7a7db7/moto-5.1.6.tar.gz", hash = "sha256:baf7afa9d4a92f07277b29cf466d0738f25db2ed2ee12afcb1dc3f2c540beebd", size = 6964053, upload-time = "2025-06-15T22:11:39.372Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f9/2f/62f959b7412dc87c545b8139249f0cb97cd133777e56405d54581114507f/moto-5.1.6-py3-none-any.whl", hash = "sha256:e4a3092bc8fe9139caa77cd34cdcbad804de4d9671e2270ea3b4d53f5c645047", size = 5046471, upload-time = "2025-06-15T22:11:37.207Z" }, ] [package.optional-dependencies] awslambda = [ { name = "docker" }, ] batch = [ { name = "docker" }, ] cloudformation = [ { name = "aws-xray-sdk" }, { name = "cfn-lint" }, { name = "docker" }, { name = "graphql-core" }, { name = "joserfc" }, { name = "openapi-spec-validator" }, { name = "py-partiql-parser" }, { name = "pyparsing" }, { name = "pyyaml" }, { name = "setuptools" }, ] dynamodb = [ { name = "docker" }, { name = "py-partiql-parser" }, ] s3 = [ { name = "py-partiql-parser" }, { name = "pyyaml" }, ] server = [ { name = "antlr4-python3-runtime" }, { name = "aws-xray-sdk" }, { name = "cfn-lint" }, { name = "docker" }, { name = "flask" }, { name = "flask-cors" }, { name = "graphql-core" }, { name = "joserfc" }, { name = "jsonpath-ng" }, { name = "openapi-spec-validator" }, { name = "py-partiql-parser" }, { name = "pyparsing" }, { name = "pyyaml" }, { name = "setuptools" }, ] [[package]] name = "mpmath" version = "1.3.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] [[package]] name = "multidict" version = "6.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] 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/a9/63/7bdd4adc330abcca54c85728db2327130e49e52e8c3ce685cec44e0f2e9f/multidict-6.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9f474ad5acda359c8758c8accc22032c6abe6dc87a8be2440d097785e27a9349", size = 77153, upload-time = "2025-10-06T14:48:26.409Z" }, { url = "https://files.pythonhosted.org/packages/3f/bb/b6c35ff175ed1a3142222b78455ee31be71a8396ed3ab5280fbe3ebe4e85/multidict-6.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b7a9db5a870f780220e931d0002bbfd88fb53aceb6293251e2c839415c1b20e", size = 44993, upload-time = "2025-10-06T14:48:28.4Z" }, { url = "https://files.pythonhosted.org/packages/e0/1f/064c77877c5fa6df6d346e68075c0f6998547afe952d6471b4c5f6a7345d/multidict-6.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03ca744319864e92721195fa28c7a3b2bc7b686246b35e4078c1e4d0eb5466d3", size = 44607, upload-time = "2025-10-06T14:48:29.581Z" }, { url = "https://files.pythonhosted.org/packages/04/7a/bf6aa92065dd47f287690000b3d7d332edfccb2277634cadf6a810463c6a/multidict-6.7.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f0e77e3c0008bc9316e662624535b88d360c3a5d3f81e15cf12c139a75250046", size = 241847, upload-time = "2025-10-06T14:48:32.107Z" }, { url = "https://files.pythonhosted.org/packages/94/39/297a8de920f76eda343e4ce05f3b489f0ab3f9504f2576dfb37b7c08ca08/multidict-6.7.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08325c9e5367aa379a3496aa9a022fe8837ff22e00b94db256d3a1378c76ab32", size = 242616, upload-time = "2025-10-06T14:48:34.054Z" }, { url = "https://files.pythonhosted.org/packages/39/3a/d0eee2898cfd9d654aea6cb8c4addc2f9756e9a7e09391cfe55541f917f7/multidict-6.7.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e2862408c99f84aa571ab462d25236ef9cb12a602ea959ba9c9009a54902fc73", size = 222333, upload-time = "2025-10-06T14:48:35.9Z" }, { url = "https://files.pythonhosted.org/packages/05/48/3b328851193c7a4240815b71eea165b49248867bbb6153a0aee227a0bb47/multidict-6.7.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d72a9a2d885f5c208b0cb91ff2ed43636bb7e345ec839ff64708e04f69a13cc", size = 253239, upload-time = "2025-10-06T14:48:37.302Z" }, { url = "https://files.pythonhosted.org/packages/b1/ca/0706a98c8d126a89245413225ca4a3fefc8435014de309cf8b30acb68841/multidict-6.7.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:478cc36476687bac1514d651cbbaa94b86b0732fb6855c60c673794c7dd2da62", size = 251618, upload-time = "2025-10-06T14:48:38.963Z" }, { url = "https://files.pythonhosted.org/packages/5e/4f/9c7992f245554d8b173f6f0a048ad24b3e645d883f096857ec2c0822b8bd/multidict-6.7.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6843b28b0364dc605f21481c90fadb5f60d9123b442eb8a726bb74feef588a84", size = 241655, upload-time = "2025-10-06T14:48:40.312Z" }, { url = "https://files.pythonhosted.org/packages/31/79/26a85991ae67efd1c0b1fc2e0c275b8a6aceeb155a68861f63f87a798f16/multidict-6.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23bfeee5316266e5ee2d625df2d2c602b829435fc3a235c2ba2131495706e4a0", size = 239245, upload-time = "2025-10-06T14:48:41.848Z" }, { url = "https://files.pythonhosted.org/packages/14/1e/75fa96394478930b79d0302eaf9a6c69f34005a1a5251ac8b9c336486ec9/multidict-6.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:680878b9f3d45c31e1f730eef731f9b0bc1da456155688c6745ee84eb818e90e", size = 233523, upload-time = "2025-10-06T14:48:43.749Z" }, { url = "https://files.pythonhosted.org/packages/b2/5e/085544cb9f9c4ad2b5d97467c15f856df8d9bac410cffd5c43991a5d878b/multidict-6.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:eb866162ef2f45063acc7a53a88ef6fe8bf121d45c30ea3c9cd87ce7e191a8d4", size = 243129, upload-time = "2025-10-06T14:48:45.225Z" }, { url = "https://files.pythonhosted.org/packages/b9/c3/e9d9e2f20c9474e7a8fcef28f863c5cbd29bb5adce6b70cebe8bdad0039d/multidict-6.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df0e3bf7993bdbeca5ac25aa859cf40d39019e015c9c91809ba7093967f7a648", size = 248999, upload-time = "2025-10-06T14:48:46.703Z" }, { url = "https://files.pythonhosted.org/packages/b5/3f/df171b6efa3239ae33b97b887e42671cd1d94d460614bfb2c30ffdab3b95/multidict-6.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:661709cdcd919a2ece2234f9bae7174e5220c80b034585d7d8a755632d3e2111", size = 243711, upload-time = "2025-10-06T14:48:48.146Z" }, { url = "https://files.pythonhosted.org/packages/3c/2f/9b5564888c4e14b9af64c54acf149263721a283aaf4aa0ae89b091d5d8c1/multidict-6.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:096f52730c3fb8ed419db2d44391932b63891b2c5ed14850a7e215c0ba9ade36", size = 237504, upload-time = "2025-10-06T14:48:49.447Z" }, { url = "https://files.pythonhosted.org/packages/6c/3a/0bd6ca0f7d96d790542d591c8c3354c1e1b6bfd2024d4d92dc3d87485ec7/multidict-6.7.0-cp310-cp310-win32.whl", hash = "sha256:afa8a2978ec65d2336305550535c9c4ff50ee527914328c8677b3973ade52b85", size = 41422, upload-time = "2025-10-06T14:48:50.789Z" }, { url = "https://files.pythonhosted.org/packages/00/35/f6a637ea2c75f0d3b7c7d41b1189189acff0d9deeb8b8f35536bb30f5e33/multidict-6.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:b15b3afff74f707b9275d5ba6a91ae8f6429c3ffb29bbfd216b0b375a56f13d7", size = 46050, upload-time = "2025-10-06T14:48:51.938Z" }, { url = "https://files.pythonhosted.org/packages/e7/b8/f7bf8329b39893d02d9d95cf610c75885d12fc0f402b1c894e1c8e01c916/multidict-6.7.0-cp310-cp310-win_arm64.whl", hash = "sha256:4b73189894398d59131a66ff157837b1fafea9974be486d036bb3d32331fdbf0", size = 43153, upload-time = "2025-10-06T14:48:53.146Z" }, { 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/90/d7/4cf84257902265c4250769ac49f4eaab81c182ee9aff8bf59d2714dbb174/multidict-6.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:363eb68a0a59bd2303216d2346e6c441ba10d36d1f9969fcb6f1ba700de7bb5c", size = 77073, upload-time = "2025-10-06T14:51:57.386Z" }, { url = "https://files.pythonhosted.org/packages/6d/51/194e999630a656e76c2965a1590d12faa5cd528170f2abaa04423e09fe8d/multidict-6.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d874eb056410ca05fed180b6642e680373688efafc7f077b2a2f61811e873a40", size = 44928, upload-time = "2025-10-06T14:51:58.791Z" }, { url = "https://files.pythonhosted.org/packages/e5/6b/2a195373c33068c9158e0941d0b46cfcc9c1d894ca2eb137d1128081dff0/multidict-6.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b55d5497b51afdfde55925e04a022f1de14d4f4f25cdfd4f5d9b0aa96166851", size = 44581, upload-time = "2025-10-06T14:52:00.174Z" }, { url = "https://files.pythonhosted.org/packages/69/7b/7f4f2e644b6978bf011a5fd9a5ebb7c21de3f38523b1f7897d36a1ac1311/multidict-6.7.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f8e5c0031b90ca9ce555e2e8fd5c3b02a25f14989cbc310701823832c99eb687", size = 239901, upload-time = "2025-10-06T14:52:02.416Z" }, { url = "https://files.pythonhosted.org/packages/3c/b5/952c72786710a031aa204a9adf7db66d7f97a2c6573889d58b9e60fe6702/multidict-6.7.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cf41880c991716f3c7cec48e2f19ae4045fc9db5fc9cff27347ada24d710bb5", size = 240534, upload-time = "2025-10-06T14:52:04.105Z" }, { url = "https://files.pythonhosted.org/packages/f3/ef/109fe1f2471e4c458c74242c7e4a833f2d9fc8a6813cd7ee345b0bad18f9/multidict-6.7.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cfc12a8630a29d601f48d47787bd7eb730e475e83edb5d6c5084317463373eb", size = 219545, upload-time = "2025-10-06T14:52:06.208Z" }, { url = "https://files.pythonhosted.org/packages/42/bd/327d91288114967f9fe90dc53de70aa3fec1b9073e46aa32c4828f771a87/multidict-6.7.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3996b50c3237c4aec17459217c1e7bbdead9a22a0fcd3c365564fbd16439dde6", size = 251187, upload-time = "2025-10-06T14:52:08.049Z" }, { url = "https://files.pythonhosted.org/packages/f4/13/a8b078ebbaceb7819fd28cd004413c33b98f1b70d542a62e6a00b74fb09f/multidict-6.7.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7f5170993a0dd3ab871c74f45c0a21a4e2c37a2f2b01b5f722a2ad9c6650469e", size = 249379, upload-time = "2025-10-06T14:52:09.831Z" }, { url = "https://files.pythonhosted.org/packages/e3/6d/ab12e1246be4d65d1f55de1e6f6aaa9b8120eddcfdd1d290439c7833d5ce/multidict-6.7.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ec81878ddf0e98817def1e77d4f50dae5ef5b0e4fe796fae3bd674304172416e", size = 239241, upload-time = "2025-10-06T14:52:11.561Z" }, { url = "https://files.pythonhosted.org/packages/bb/d7/079a93625208c173b8fa756396814397c0fd9fee61ef87b75a748820b86e/multidict-6.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9281bf5b34f59afbc6b1e477a372e9526b66ca446f4bf62592839c195a718b32", size = 237418, upload-time = "2025-10-06T14:52:13.671Z" }, { url = "https://files.pythonhosted.org/packages/c9/29/03777c2212274aa9440918d604dc9d6af0e6b4558c611c32c3dcf1a13870/multidict-6.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:68af405971779d8b37198726f2b6fe3955db846fee42db7a4286fc542203934c", size = 232987, upload-time = "2025-10-06T14:52:15.708Z" }, { url = "https://files.pythonhosted.org/packages/d9/00/11188b68d85a84e8050ee34724d6ded19ad03975caebe0c8dcb2829b37bf/multidict-6.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3ba3ef510467abb0667421a286dc906e30eb08569365f5cdb131d7aff7c2dd84", size = 240985, upload-time = "2025-10-06T14:52:17.317Z" }, { url = "https://files.pythonhosted.org/packages/df/0c/12eef6aeda21859c6cdf7d75bd5516d83be3efe3d8cc45fd1a3037f5b9dc/multidict-6.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b61189b29081a20c7e4e0b49b44d5d44bb0dc92be3c6d06a11cc043f81bf9329", size = 246855, upload-time = "2025-10-06T14:52:19.096Z" }, { url = "https://files.pythonhosted.org/packages/69/f6/076120fd8bb3975f09228e288e08bff6b9f1bfd5166397c7ba284f622ab2/multidict-6.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fb287618b9c7aa3bf8d825f02d9201b2f13078a5ed3b293c8f4d953917d84d5e", size = 241804, upload-time = "2025-10-06T14:52:21.166Z" }, { url = "https://files.pythonhosted.org/packages/5f/51/41bb950c81437b88a93e6ddfca1d8763569ae861e638442838c4375f7497/multidict-6.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:521f33e377ff64b96c4c556b81c55d0cfffb96a11c194fd0c3f1e56f3d8dd5a4", size = 235321, upload-time = "2025-10-06T14:52:23.208Z" }, { url = "https://files.pythonhosted.org/packages/5a/cf/5bbd31f055199d56c1f6b04bbadad3ccb24e6d5d4db75db774fc6d6674b8/multidict-6.7.0-cp39-cp39-win32.whl", hash = "sha256:ce8fdc2dca699f8dbf055a61d73eaa10482569ad20ee3c36ef9641f69afa8c91", size = 41435, upload-time = "2025-10-06T14:52:24.735Z" }, { url = "https://files.pythonhosted.org/packages/af/01/547ffe9c2faec91c26965c152f3fea6cff068b6037401f61d310cc861ff4/multidict-6.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:7e73299c99939f089dd9b2120a04a516b95cdf8c1cd2b18c53ebf0de80b1f18f", size = 46193, upload-time = "2025-10-06T14:52:26.101Z" }, { url = "https://files.pythonhosted.org/packages/27/77/cfa5461d1d2651d6fc24216c92b4a21d4e385a41c46e0d9f3b070675167b/multidict-6.7.0-cp39-cp39-win_arm64.whl", hash = "sha256:6bdce131e14b04fd34a809b6380dbfd826065c3e2fe8a50dbae659fa0c390546", size = 43118, upload-time = "2025-10-06T14:52:27.876Z" }, { 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 = "networkx" version = "3.2.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928, upload-time = "2023-10-28T08:41:39.364Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772, upload-time = "2023-10-28T08:41:36.945Z" }, ] [[package]] name = "networkx" version = "3.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, ] [[package]] name = "networkx" version = "3.5" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", ] sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, ] [[package]] name = "nodeenv" version = "1.9.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] [[package]] name = "openapi-schema-validator" version = "0.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema" }, { name = "jsonschema-specifications" }, { name = "rfc3339-validator" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" }, ] [[package]] name = "openapi-spec-validator" version = "0.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema" }, { name = "jsonschema-path" }, { name = "lazy-object-proxy" }, { name = "openapi-schema-validator" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" }, ] [[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 = "pathable" version = "0.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, ] [[package]] name = "pip" version = "25.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/16/650289cd3f43d5a2fadfd98c68bd1e1e7f2550a1a5326768cddfbcedb2c5/pip-25.2.tar.gz", hash = "sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2", size = 1840021, upload-time = "2025-07-30T21:50:15.401Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/3f/945ef7ab14dc4f9d7f40288d2df998d1837ee0888ec3659c813487572faa/pip-25.2-py3-none-any.whl", hash = "sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717", size = 1752557, upload-time = "2025-07-30T21:50:13.323Z" }, ] [[package]] name = "platformdirs" version = "4.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "ply" version = "3.11" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, ] [[package]] name = "pre-commit" version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, { name = "identify" }, { name = "nodeenv" }, { name = "pyyaml" }, { name = "virtualenv" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, ] [[package]] name = "propcache" version = "0.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ea/c8/d70cd26d845c6d85479d8f5a11a0fd7151e9bc4794cc5e6eb5a790f12df8/propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529", size = 45187, upload-time = "2025-10-04T21:57:39.546Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/7b/4bd85fea3dc58b6f246abf0e6c9e44adca26f6817e6c136780315d723b82/propcache-0.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:779aaae64089e2f4992e993faea801925395d26bb5de4a47df7ef7f942c14f80", size = 79437, upload-time = "2025-10-04T21:54:49.766Z" }, { url = "https://files.pythonhosted.org/packages/e0/91/379ecc1ab37fe33648c7cb2d2252f58969adac1edcd6ec74682d7fb2d920/propcache-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566552ed9b003030745e5bc7b402b83cf3cecae1bade95262d78543741786db5", size = 45369, upload-time = "2025-10-04T21:54:51.688Z" }, { url = "https://files.pythonhosted.org/packages/de/8e/2e002e59e359bbc6ababbb7da168226f93e0533429ea1e93989a7eedcb2a/propcache-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:944de70384c62d16d4a00c686b422aa75efbc67c4addaebefbb56475d1c16034", size = 47191, upload-time = "2025-10-04T21:54:52.915Z" }, { url = "https://files.pythonhosted.org/packages/b5/9a/f56eef9932dc3cbc63df4716f09fbaefec7a475608b643842784a01351b6/propcache-0.4.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e878553543ece1f8006d0ba4d096b40290580db173bfb18e16158045b9371335", size = 201000, upload-time = "2025-10-04T21:54:54.556Z" }, { url = "https://files.pythonhosted.org/packages/6e/84/e7ad1e09c13f0574dbad261441f6a7f1fb8cc1e2fcb23ec4d4b3e4c7dc67/propcache-0.4.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8659f995b19185179474b18de8755689e1f71e1334d05c14e1895caa4e409cf7", size = 209175, upload-time = "2025-10-04T21:54:55.996Z" }, { url = "https://files.pythonhosted.org/packages/4a/74/0b785ac0fbb44a5a7c267efc409b7a62d7a03b17c6442ecb52fd29152314/propcache-0.4.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7aa8cc5c94e682dce91cb4d12d7b81c01641f4ef5b3b3dc53325d43f0e3b9f2e", size = 214874, upload-time = "2025-10-04T21:54:57.831Z" }, { url = "https://files.pythonhosted.org/packages/b4/fd/e8d795def2b1d8dc1dc4731d36da1f6111d7c73212909e79462172d0434c/propcache-0.4.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da584d917a1a17f690fc726617fd2c3f3006ea959dae5bb07a5630f7b16f9f5f", size = 196686, upload-time = "2025-10-04T21:54:59.218Z" }, { url = "https://files.pythonhosted.org/packages/79/c2/dc992c712c3a1bfaa11d13ff177dbdf9b8b272e7bd443601e37f35728338/propcache-0.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:892a072e5b19c3f324a4f8543c9f7e8fc2b0aa08579e46f69bdf0cfc1b440454", size = 192000, upload-time = "2025-10-04T21:55:00.645Z" }, { url = "https://files.pythonhosted.org/packages/b3/de/bb108dbdfae594148b033ff283d9fa6e4b0906a99f2c03b98b526883149d/propcache-0.4.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c20d796210720455086ef3f85adc413d1e41d374742f9b439354f122bbc3b528", size = 190310, upload-time = "2025-10-04T21:55:02.107Z" }, { url = "https://files.pythonhosted.org/packages/ba/7b/1bdb5d44ba4c87d270bcf11354950f8f7fbc9ace1fbe7745e683fcb57b5a/propcache-0.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df7107a91126a495880576610ae989f19106e1900dd5218d08498391fa43b31d", size = 199646, upload-time = "2025-10-04T21:55:03.55Z" }, { url = "https://files.pythonhosted.org/packages/e5/04/44beda877f779f49f5b8c0ff4817a62b5f90a2dfac1ec5311df15a9dfceb/propcache-0.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0b04ac2120c161416c866d0b6a4259e47e92231ff166b518cc0efb95777367c3", size = 200507, upload-time = "2025-10-04T21:55:04.914Z" }, { url = "https://files.pythonhosted.org/packages/d4/62/a13ad0a63e06f3695fcaeaeeeb62e2cc685181a1248b23a2bc877c8b7111/propcache-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fa29c71ffa8d6a37324258737d09475f84715a6e8c350f67f0bc8e5e44993", size = 192787, upload-time = "2025-10-04T21:55:06.385Z" }, { url = "https://files.pythonhosted.org/packages/9b/07/386246b3b4a6b11208bcbf57580210fb8c923ab26759389fe594e5615cd7/propcache-0.4.0-cp310-cp310-win32.whl", hash = "sha256:01c0ebc172ca28e9d62876832befbf7f36080eee6ed9c9e00243de2a8089ad57", size = 38004, upload-time = "2025-10-04T21:55:07.692Z" }, { url = "https://files.pythonhosted.org/packages/a4/f2/e1fcb9694f590bc443ae5044f982546bb01cbaa3cdf05286e9473a9874bf/propcache-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:84f847e64f4d1a232e50460eebc1196642ee9b4c983612f41cd2d44fd2fe7c71", size = 41516, upload-time = "2025-10-04T21:55:08.854Z" }, { url = "https://files.pythonhosted.org/packages/15/f4/d211744d41d72fbb89d3ee53963c1dc26892c49f53ae3c49fbc15cfb2548/propcache-0.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:2166466a666a5bebc332cd209cad77d996fad925ca7e8a2a6310ba9e851ae641", size = 38122, upload-time = "2025-10-04T21:55:10.044Z" }, { url = "https://files.pythonhosted.org/packages/f9/c4/72b8d41bdbae8aea9c25b869d7cdc3ab5f281f979d8aea30f4646ad12743/propcache-0.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6a6a36b94c09711d6397d79006ca47901539fbc602c853d794c39abd6a326549", size = 80035, upload-time = "2025-10-04T21:55:11.266Z" }, { url = "https://files.pythonhosted.org/packages/e9/f8/f87115733e221408a363f3a9753419cf2d4be7a8a7ec9dc0788325cd23f1/propcache-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:da47070e1340a1639aca6b1c18fe1f1f3d8d64d3a1f9ddc67b94475f44cd40f3", size = 45622, upload-time = "2025-10-04T21:55:12.41Z" }, { url = "https://files.pythonhosted.org/packages/5d/cc/391f883248faa2efdf6886bdb12ac8edf20eac0863770d8d925450d8cc76/propcache-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de536cf796abc5b58d11c0ad56580215d231d9554ea4bb6b8b1b3bed80aa3234", size = 47517, upload-time = "2025-10-04T21:55:13.819Z" }, { url = "https://files.pythonhosted.org/packages/3e/d2/5593b59999f42d1044c5ab5f238be1f9d537ab91b0c910727986d520a6e9/propcache-0.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5c82af8e329c3cdc3e717dd3c7b2ff1a218b6de611f6ce76ee34967570a9de9", size = 214540, upload-time = "2025-10-04T21:55:15.206Z" }, { url = "https://files.pythonhosted.org/packages/bb/5d/028cdc0eaa1a66ee2ec339a08b5e6ec15e7e71dac86103bebe53ba10dc0f/propcache-0.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:abe04e7aa5ab2e4056fcf3255ebee2071e4a427681f76d4729519e292c46ecc1", size = 221603, upload-time = "2025-10-04T21:55:16.704Z" }, { url = "https://files.pythonhosted.org/packages/e8/f8/e30aee5f59ea21647faef9c82bd67fa510295c34908a7a38571def555881/propcache-0.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:075ca32384294434344760fdcb95f7833e1d7cf7c4e55f0e726358140179da35", size = 227749, upload-time = "2025-10-04T21:55:18.082Z" }, { url = "https://files.pythonhosted.org/packages/d7/85/0757dfc73931bea63b18d26b2c5e7bf13113ca60fe0e5f19905f104bcf6a/propcache-0.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:626ec13592928b677f48ff5861040b604b635e93d8e2162fb638397ea83d07e8", size = 209792, upload-time = "2025-10-04T21:55:19.475Z" }, { url = "https://files.pythonhosted.org/packages/d2/45/35a6a6241f46948c0ac2418d5bf50cfbcd9735739f42028a1c11e9066a72/propcache-0.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e071548b6a376e173b0102c3f55dc16e7d055b5307d487e844c320e38cacf2", size = 207979, upload-time = "2025-10-04T21:55:21.164Z" }, { url = "https://files.pythonhosted.org/packages/e3/d1/5930396e75c9ed477958eac1496e6fb08794d823e9b14a459f1c0e20f338/propcache-0.4.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2af6de831a26f42a3f94592964becd8d7f238551786d7525807f02e53defbd13", size = 201923, upload-time = "2025-10-04T21:55:22.5Z" }, { url = "https://files.pythonhosted.org/packages/98/72/675455f22bcefeda16907461f9a9a4a93709ff2095e8cf799bdb6c78e030/propcache-0.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c6dba1a3b8949e08c4280071c86e38cb602f02e0ed6659234108c7a7cd710", size = 212117, upload-time = "2025-10-04T21:55:23.858Z" }, { url = "https://files.pythonhosted.org/packages/13/27/c533302ff80a49a848c3dbd01bb18f87b06826602b3b37043ff00d6b5005/propcache-0.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:783e91595cf9b66c2deda17f2e8748ae8591aa9f7c65dcab038872bfe83c5bb1", size = 216594, upload-time = "2025-10-04T21:55:25.169Z" }, { url = "https://files.pythonhosted.org/packages/63/91/8250fbb601fd16c427e5f469132f27e175c6692dbfa784ef1266dc652e55/propcache-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c3f4b125285d354a627eb37f3ea7c13b8842c7c0d47783581d0df0e272dbf5f0", size = 204863, upload-time = "2025-10-04T21:55:26.511Z" }, { url = "https://files.pythonhosted.org/packages/34/c4/fd945a9a25845aafb6094b9fa6a88286e4e1c55686e60172c60fe669e0d1/propcache-0.4.0-cp311-cp311-win32.whl", hash = "sha256:71c45f02ffbb8a21040ae816ceff7f6cd749ffac29fc0f9daa42dc1a9652d577", size = 37948, upload-time = "2025-10-04T21:55:27.719Z" }, { url = "https://files.pythonhosted.org/packages/42/02/f30e7304661ffe8d51ff4050e06765ac2df6d95cf23c999dfe5a0cd0eb4c/propcache-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7d51f70f77950f8efafed4383865d3533eeee52d8a0dd1c35b65f24de41de4e0", size = 41511, upload-time = "2025-10-04T21:55:29.15Z" }, { url = "https://files.pythonhosted.org/packages/a5/f2/edd329d86085438a1ba32cf4cf45fc982d18343bed1f16b218b516c3340d/propcache-0.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:858eaabd2191dd0da5272993ad08a748b5d3ae1aefabea8aee619b45c2af4a64", size = 37957, upload-time = "2025-10-04T21:55:30.31Z" }, { url = "https://files.pythonhosted.org/packages/b3/cf/3f88344261d69f8021256f20e82e820c5df3aba96e5ba9b5fdd3685d3a9f/propcache-0.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:381c84a445efb8c9168f1393a5a7c566de22edc42bfe207a142fff919b37f5d9", size = 79846, upload-time = "2025-10-04T21:55:31.447Z" }, { url = "https://files.pythonhosted.org/packages/be/fa/0286fc92764eead9dcfee639b67828daa32e61dd0f1618831547141eb28b/propcache-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5a531d29d7b873b12730972237c48b1a4e5980b98cf21b3f09fa4710abd3a8c3", size = 45850, upload-time = "2025-10-04T21:55:32.637Z" }, { url = "https://files.pythonhosted.org/packages/c7/83/57840656f972f8a67992eee40781e4066657776dcb889f49df0e8eecb112/propcache-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cd6e22255ed73efeaaeb1765505a66a48a9ec9ebc919fce5ad490fe5e33b1555", size = 47171, upload-time = "2025-10-04T21:55:33.819Z" }, { url = "https://files.pythonhosted.org/packages/9f/8e/e0a0bd376c3440476b924eca517589ee535bb4520420d178268bf88558ba/propcache-0.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9a8d277dc218ddf04ec243a53ac309b1afcebe297c0526a8f82320139b56289", size = 225306, upload-time = "2025-10-04T21:55:35.312Z" }, { url = "https://files.pythonhosted.org/packages/84/fe/76884442da1bab6d4353ba1c43fdc4a770c3b3973f3ac7620a7205402fdd/propcache-0.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:399c73201d88c856a994916200d7cba41d7687096f8eb5139eb68f02785dc3f7", size = 230013, upload-time = "2025-10-04T21:55:37.005Z" }, { url = "https://files.pythonhosted.org/packages/f4/b7/322af273bd1136bb7e13628821fb855c9f61d64651c73fea71dded68dda5/propcache-0.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a1d5e474d43c238035b74ecf997f655afa67f979bae591ac838bb3fbe3076392", size = 238331, upload-time = "2025-10-04T21:55:38.713Z" }, { url = "https://files.pythonhosted.org/packages/84/5e/036d2b105927ae7f179346c9911d16c345f4dba5a19a063f23a8d28acfbd/propcache-0.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f589652ee38de96aa58dd219335604e09666092bc250c1d9c26a55bcef9932", size = 221461, upload-time = "2025-10-04T21:55:40.034Z" }, { url = "https://files.pythonhosted.org/packages/63/0d/babd038efb12a87a46ab070438c52daeac6bed0a930693a418feef8cb8a6/propcache-0.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5227da556b2939da6125cda1d5eecf9e412e58bc97b41e2f192605c3ccbb7c2", size = 216707, upload-time = "2025-10-04T21:55:41.455Z" }, { url = "https://files.pythonhosted.org/packages/ab/68/dd075a037381581f16e7e504a6da9c1d7e415e945dd8ed67905d608f0687/propcache-0.4.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:92bc43a1ab852310721ce856f40a3a352254aa6f5e26f0fad870b31be45bba2e", size = 212591, upload-time = "2025-10-04T21:55:42.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/43/22698f28fc8e04c32b109cb9cb81305a4873b77c907b17484566b6133aef/propcache-0.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:83ae2f5343f6f06f4c91ae530d95f56b415f768f9c401a5ee2a10459cf74370b", size = 220188, upload-time = "2025-10-04T21:55:44.53Z" }, { url = "https://files.pythonhosted.org/packages/96/7a/27886e4a4c69598a38fbeeed64f9b8ddfa6f08fe3452035845a1fe90336f/propcache-0.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:077a32977399dc05299b16e793210341a0b511eb0a86d1796873e83ce47334cc", size = 226736, upload-time = "2025-10-04T21:55:46.348Z" }, { url = "https://files.pythonhosted.org/packages/5b/c7/313c632b5888db3c9f4cb262420dcd5e57cf858d939d6ad9c3b1b90c12af/propcache-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94a278c45e6463031b5a8278e40a07edf2bcc3b5379510e22b6c1a6e6498c194", size = 216363, upload-time = "2025-10-04T21:55:47.768Z" }, { url = "https://files.pythonhosted.org/packages/7a/5d/5aaf82bd1542aedb47d10483b84f49ee8f00d970a58e27534cd241e9c5ac/propcache-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4c491462e1dc80f9deb93f428aad8d83bb286de212837f58eb48e75606e7726c", size = 37945, upload-time = "2025-10-04T21:55:49.104Z" }, { url = "https://files.pythonhosted.org/packages/4c/67/47ffff6eb176f383f56319f31c0e1bcf7500cb94ffb7582efc600c6b3c73/propcache-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdb0cecafb528ab15ed89cdfed183074d15912d046d3e304955513b50a34b907", size = 41530, upload-time = "2025-10-04T21:55:50.261Z" }, { url = "https://files.pythonhosted.org/packages/f3/7e/61b70306b9d7527286ce887a8ff28c304ab2514e5893eea36b5bdf7a21af/propcache-0.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:b2f29697d1110e8cdf7a39cc630498df0082d7898b79b731c1c863f77c6e8cfc", size = 37662, upload-time = "2025-10-04T21:55:51.35Z" }, { url = "https://files.pythonhosted.org/packages/cd/dd/f405b0fe84d29d356895bc048404d3321a2df849281cf3f932158c9346ac/propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d", size = 77565, upload-time = "2025-10-04T21:55:52.907Z" }, { url = "https://files.pythonhosted.org/packages/c0/48/dfb2c45e1b0d92228c9c66fa929af7316c15cbe69a7e438786aaa60c1b3c/propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937", size = 44602, upload-time = "2025-10-04T21:55:54.406Z" }, { url = "https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6", size = 46168, upload-time = "2025-10-04T21:55:55.537Z" }, { url = "https://files.pythonhosted.org/packages/40/ac/983e69cce8800251aab85858069cf9359b22222a9cda47591e03e2f24eec/propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85", size = 207997, upload-time = "2025-10-04T21:55:57.022Z" }, { url = "https://files.pythonhosted.org/packages/ae/9c/5586a7a54e7e0b9a87fdd8ba935961f398c0e6eaecd57baaa8eca468a236/propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c", size = 210948, upload-time = "2025-10-04T21:55:58.397Z" }, { url = "https://files.pythonhosted.org/packages/5f/ba/644e367f8a86461d45bd023ace521180938e76515040550af9b44085e99a/propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a", size = 217988, upload-time = "2025-10-04T21:56:00.251Z" }, { url = "https://files.pythonhosted.org/packages/24/0e/1e21af74b4732d002b0452605bdf31d6bf990fd8b720cb44e27a97d80db5/propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218", size = 204442, upload-time = "2025-10-04T21:56:01.93Z" }, { url = "https://files.pythonhosted.org/packages/fd/30/ae2eec96995a8a760acb9a0b6c92b9815f1fc885c7d8481237ccb554eab0/propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1", size = 199371, upload-time = "2025-10-04T21:56:03.25Z" }, { url = "https://files.pythonhosted.org/packages/45/1d/a18fac8cb04f8379ccb79cf15aac31f4167a270d1cd1111f33c0d38ce4fb/propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16", size = 196638, upload-time = "2025-10-04T21:56:04.619Z" }, { url = "https://files.pythonhosted.org/packages/48/45/3549a2b6f74dce6f21b2664d078bd26ceb876aae9c58f3c017cf590f0ee3/propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6", size = 203651, upload-time = "2025-10-04T21:56:06.153Z" }, { url = "https://files.pythonhosted.org/packages/7d/f0/90ea14d518c919fc154332742a9302db3004af4f1d3df688676959733283/propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7", size = 205726, upload-time = "2025-10-04T21:56:07.955Z" }, { url = "https://files.pythonhosted.org/packages/f6/de/8efc1dbafeb42108e7af744822cdca944b990869e9da70e79efb21569d6b/propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833", size = 199576, upload-time = "2025-10-04T21:56:09.43Z" }, { url = "https://files.pythonhosted.org/packages/d7/38/4d79fe3477b050398fb8d8f59301ed116d8c6ea3c4dbf09498c679103f90/propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc", size = 37474, upload-time = "2025-10-04T21:56:10.74Z" }, { url = "https://files.pythonhosted.org/packages/36/9b/a283daf665a1945cff1b03d1104e7c9ee92bb7b6bbcc6518b24fcdac8bd0/propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281", size = 40685, upload-time = "2025-10-04T21:56:11.896Z" }, { url = "https://files.pythonhosted.org/packages/e9/f7/def8fc0b4d7a89f1628f337cb122bb9a946c5ed97760f2442b27b7fa5a69/propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92", size = 37046, upload-time = "2025-10-04T21:56:13.021Z" }, { url = "https://files.pythonhosted.org/packages/ca/6b/f6e8b36b58d17dfb6c505b9ae1163fcf7a4cf98825032fdc77bba4ab5c4a/propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4", size = 81274, upload-time = "2025-10-04T21:56:14.206Z" }, { url = "https://files.pythonhosted.org/packages/8e/c5/1fd0baa222b8faf53ba04dd4f34de33ea820b80e34f87c7960666bae5f4f/propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc", size = 46232, upload-time = "2025-10-04T21:56:15.337Z" }, { url = "https://files.pythonhosted.org/packages/cb/6b/7aa5324983cab7666ed58fc32c68a0430468a18e02e3f04e7a879c002414/propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f", size = 48239, upload-time = "2025-10-04T21:56:16.768Z" }, { url = "https://files.pythonhosted.org/packages/24/0f/58c192301c0436762ed5fed5a3edadb0ae399cb73528fb9c1b5cb8e53523/propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1", size = 275804, upload-time = "2025-10-04T21:56:18.066Z" }, { url = "https://files.pythonhosted.org/packages/f7/b9/092ee32064ebfabedae4251952787e63e551075af1a1205e8061b3ed5838/propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d", size = 273996, upload-time = "2025-10-04T21:56:19.801Z" }, { url = "https://files.pythonhosted.org/packages/43/82/becf618ed28e732f3bba3df172cd290a1afbd99f291074f747fd5bd031bb/propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4", size = 280266, upload-time = "2025-10-04T21:56:21.136Z" }, { url = "https://files.pythonhosted.org/packages/51/be/b370930249a9332a81b5c4c550dac614b7e11b6c160080777e903d57e197/propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d", size = 263186, upload-time = "2025-10-04T21:56:22.787Z" }, { url = "https://files.pythonhosted.org/packages/33/b6/546fd3e31770aed3aed1c01b120944c689edb510aeb7a25472edc472ce23/propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be", size = 260721, upload-time = "2025-10-04T21:56:24.22Z" }, { url = "https://files.pythonhosted.org/packages/80/70/3751930d16e5984490c73ca65b80777e4b26e7a0015f2d41f31d75959a71/propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44", size = 247516, upload-time = "2025-10-04T21:56:25.577Z" }, { url = "https://files.pythonhosted.org/packages/59/90/4bc96ce6476f67e2e6b72469f328c92b53259a0e4d1d5386d71a36e9258c/propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0", size = 262675, upload-time = "2025-10-04T21:56:27.065Z" }, { url = "https://files.pythonhosted.org/packages/6f/d1/f16d096869c5f1c93d67fc37488c0c814add0560574f6877653a10239cde/propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6", size = 263379, upload-time = "2025-10-04T21:56:28.517Z" }, { url = "https://files.pythonhosted.org/packages/ab/2a/da5cd1bc1c6412939c457ea65bbe7e034045c395d98ff8ff880d06ec4553/propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da", size = 257694, upload-time = "2025-10-04T21:56:30.051Z" }, { url = "https://files.pythonhosted.org/packages/a5/11/938e67c07189b662a6c72551d48285a02496de885408392447c25657dd47/propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db", size = 41321, upload-time = "2025-10-04T21:56:31.406Z" }, { url = "https://files.pythonhosted.org/packages/f4/6e/72b11a4dcae68c728b15126cc5bc830bf275c84836da2633412b768d07e0/propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d", size = 44846, upload-time = "2025-10-04T21:56:32.5Z" }, { url = "https://files.pythonhosted.org/packages/94/09/0ef3c025e0621e703ef71b69e0085181a3124bcc1beef29e0ffef59ed7f4/propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9", size = 39689, upload-time = "2025-10-04T21:56:33.686Z" }, { url = "https://files.pythonhosted.org/packages/60/89/7699d8e9f8c222bbef1fae26afd72d448353f164a52125d5f87dd9fec2c7/propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79", size = 77977, upload-time = "2025-10-04T21:56:34.836Z" }, { url = "https://files.pythonhosted.org/packages/77/c5/2758a498199ce46d6d500ba4391a8594df35400cc85738aa9f0c9b8366db/propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4", size = 44715, upload-time = "2025-10-04T21:56:36.075Z" }, { url = "https://files.pythonhosted.org/packages/0d/da/5a44e10282a28c2dd576e5e1a2c7bb8145587070ddab7375fb643f7129d7/propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e", size = 46463, upload-time = "2025-10-04T21:56:37.227Z" }, { url = "https://files.pythonhosted.org/packages/d5/5a/b2c314f655f46c10c204dc0d69e19fadfb1cc4d40ab33f403698a35c3281/propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5", size = 206980, upload-time = "2025-10-04T21:56:38.828Z" }, { url = "https://files.pythonhosted.org/packages/7c/4e/f6643ec2cd5527b92c93488f9b67a170494736bb1c5460136399d709ce5a/propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e", size = 211385, upload-time = "2025-10-04T21:56:40.2Z" }, { url = "https://files.pythonhosted.org/packages/71/41/362766a346c3f8d3bbeb7899e1ff40f18844e0fe37e9f6f536553cf6b6be/propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b", size = 215315, upload-time = "2025-10-04T21:56:41.574Z" }, { url = "https://files.pythonhosted.org/packages/ff/98/17385d51816d56fa6acc035d8625fbf833b6a795d7ef7fb37ea3f62db6c9/propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb", size = 201416, upload-time = "2025-10-04T21:56:42.947Z" }, { url = "https://files.pythonhosted.org/packages/7a/83/801178ca1c29e217564ee507ff2a49d3f24a4dd85c9b9d681fd1d62b15f2/propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137", size = 197726, upload-time = "2025-10-04T21:56:44.313Z" }, { url = "https://files.pythonhosted.org/packages/d2/38/c8743917bca92b7e5474366b6b04c7b3982deac32a0fe4b705f2e92c09bb/propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738", size = 192819, upload-time = "2025-10-04T21:56:45.702Z" }, { url = "https://files.pythonhosted.org/packages/0b/74/3de3ef483e8615aaaf62026fcdcb20cbfc4535ea14871b12f72d52c1d6dc/propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f", size = 202492, upload-time = "2025-10-04T21:56:47.388Z" }, { url = "https://files.pythonhosted.org/packages/46/86/a130dd85199d651a6986ba6bf1ce297b7bbcafc01c8e139e6ba2b8218a20/propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90", size = 204106, upload-time = "2025-10-04T21:56:49.139Z" }, { url = "https://files.pythonhosted.org/packages/b2/f7/44eab58659d71d21995146c94139e63882bac280065b3a9ed10376897bcc/propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca", size = 198043, upload-time = "2025-10-04T21:56:50.561Z" }, { url = "https://files.pythonhosted.org/packages/96/14/df37be1bf1423d2dda201a4cdb1c5cb44048d34e31a97df227cc25b0a55c/propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93", size = 38036, upload-time = "2025-10-04T21:56:51.868Z" }, { url = "https://files.pythonhosted.org/packages/99/96/9cea65d6c50224737e80c57a3f3db4ca81bc7b1b52bc73346df8c50db400/propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b", size = 41156, upload-time = "2025-10-04T21:56:53.242Z" }, { url = "https://files.pythonhosted.org/packages/52/4d/91523dcbe23cc127b097623a6ba177da51fca6b7c979082aa49745b527b7/propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79", size = 37976, upload-time = "2025-10-04T21:56:54.351Z" }, { url = "https://files.pythonhosted.org/packages/ec/f7/7118a944cb6cdb548c9333cf311bda120f9793ecca54b2ca4a3f7e58723e/propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9", size = 81270, upload-time = "2025-10-04T21:56:55.516Z" }, { url = "https://files.pythonhosted.org/packages/ab/f9/04a8bc9977ea201783f3ccb04106f44697f635f70439a208852d4d08554d/propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f", size = 46224, upload-time = "2025-10-04T21:56:56.695Z" }, { url = "https://files.pythonhosted.org/packages/0f/3d/808b074034156f130a0047304d811a5a5df3bb0976c9adfb9383718fd888/propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321", size = 48246, upload-time = "2025-10-04T21:56:57.926Z" }, { url = "https://files.pythonhosted.org/packages/66/eb/e311f3a59ddc93078cb079b12699af9fd844142c4b4d382b386ee071d921/propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac", size = 275562, upload-time = "2025-10-04T21:56:59.221Z" }, { url = "https://files.pythonhosted.org/packages/f4/05/a146094d6a00bb2f2036dd2a2f4c2b2733ff9574b59ce53bd8513edfca5d/propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0", size = 273627, upload-time = "2025-10-04T21:57:00.582Z" }, { url = "https://files.pythonhosted.org/packages/91/95/a6d138f6e3d5f6c9b34dbd336b964a1293f2f1a79cafbe70ae3403d7cc46/propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a", size = 279778, upload-time = "2025-10-04T21:57:01.944Z" }, { url = "https://files.pythonhosted.org/packages/ac/09/19594a20da0519bfa00deef8cf35dda6c9a5b51bba947f366e85ea59b3de/propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2", size = 262833, upload-time = "2025-10-04T21:57:03.326Z" }, { url = "https://files.pythonhosted.org/packages/b5/92/60d2ddc7662f7b2720d3b628ad8ce888015f4ab5c335b7b1b50183194e68/propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3", size = 260456, upload-time = "2025-10-04T21:57:05.159Z" }, { url = "https://files.pythonhosted.org/packages/6f/e2/4c2e25c77cf43add2e05a86c4fcf51107edc4d92318e5c593bbdc2515d57/propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db", size = 247284, upload-time = "2025-10-04T21:57:06.566Z" }, { url = "https://files.pythonhosted.org/packages/dc/3e/c273ab8edc80683ec8b15b486e95c03096ef875d99e4b0ab0a36c1e42c94/propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4", size = 262368, upload-time = "2025-10-04T21:57:08.231Z" }, { url = "https://files.pythonhosted.org/packages/ac/a9/3fa231f65a9f78614c5aafa9cee788d7f55c22187cc2f33e86c7c16d0262/propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012", size = 263010, upload-time = "2025-10-04T21:57:09.641Z" }, { url = "https://files.pythonhosted.org/packages/38/a0/f4f5d368e60c9dc04d3158eaf1ca0ad899b40ac3d29c015bf62735225a6f/propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec", size = 257298, upload-time = "2025-10-04T21:57:11.125Z" }, { url = "https://files.pythonhosted.org/packages/c7/30/f78d6758dc36a98f1cddc39b3185cefde616cc58248715b7c65495491cb1/propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373", size = 42484, upload-time = "2025-10-04T21:57:12.652Z" }, { url = "https://files.pythonhosted.org/packages/4e/ad/de0640e9b56d2caa796c4266d7d1e6cc4544cc327c25b7ced5c59893b625/propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28", size = 46229, upload-time = "2025-10-04T21:57:14.034Z" }, { url = "https://files.pythonhosted.org/packages/da/bf/5aed62dddbf2bbe62a3564677436261909c9dd63a0fa1fb6cf0629daa13c/propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4", size = 40329, upload-time = "2025-10-04T21:57:15.198Z" }, { url = "https://files.pythonhosted.org/packages/84/24/31f6efbca9211550377731642131ad274b01b2dc4788c0a7d5c47fcd9e1c/propcache-0.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0cd30341142c68377cf3c4e2d9f0581e6e528694b2d57c62c786be441053d2fc", size = 80114, upload-time = "2025-10-04T21:57:16.407Z" }, { url = "https://files.pythonhosted.org/packages/21/96/295cb8cef28d50cfccf445689a1dc8de0b2e3fa923921404560ae7a423cc/propcache-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c46d37955820dd883cf9156ceb7825b8903e910bdd869902e20a5ac4ecd2c8b", size = 45697, upload-time = "2025-10-04T21:57:17.6Z" }, { url = "https://files.pythonhosted.org/packages/97/c0/3468b29275579cf533b25de083072b5fe11a7666106827a5ea704eb5bfc3/propcache-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0b12df77eb19266efd146627a65b8ad414f9d15672d253699a50c8205661a820", size = 47510, upload-time = "2025-10-04T21:57:18.807Z" }, { url = "https://files.pythonhosted.org/packages/29/4e/fd5f46077a435e0866eb27a1a9771067ab41bfd35e14b1aa51c097bcd035/propcache-0.4.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cdabd60e109506462e6a7b37008e57979e737dc6e7dfbe1437adcfe354d1a0a", size = 201095, upload-time = "2025-10-04T21:57:20.104Z" }, { url = "https://files.pythonhosted.org/packages/9c/d0/cdbc660a9946e3d389db2541145046073f9ce767e0dbea13bced2657fcf5/propcache-0.4.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65ff56a31f25925ef030b494fe63289bf07ef0febe6da181b8219146c590e185", size = 209505, upload-time = "2025-10-04T21:57:21.509Z" }, { url = "https://files.pythonhosted.org/packages/42/77/9ee6d0266bc33b5ee7c3eabddb48be30b141f6acddf964d0844ff521be6a/propcache-0.4.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:96153e037ae065bb71cae889f23c933190d81ae183f3696a030b47352fd8655d", size = 215226, upload-time = "2025-10-04T21:57:22.88Z" }, { url = "https://files.pythonhosted.org/packages/7d/c6/72c4ed7751a82f60d14fb713734efcfcf190942288dd2056898bac4824d2/propcache-0.4.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bf95be277fbb51513895c2cecc81ab12a421cdbd8837f159828a919a0167f96", size = 196960, upload-time = "2025-10-04T21:57:24.562Z" }, { url = "https://files.pythonhosted.org/packages/82/7e/e01179669192b94a640d942ad42535c38ba6cad70518db7354585e051d1c/propcache-0.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8d18d796ffecdc8253742fd53a94ceee2e77ad149eb9ed5960c2856b5f692f71", size = 192189, upload-time = "2025-10-04T21:57:25.973Z" }, { url = "https://files.pythonhosted.org/packages/ae/ea/0c2be8a7fdf012598583df72efd38220ef0db671fe9a9d8ff82f790bf313/propcache-0.4.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4a52c25a51d5894ba60c567b0dbcf73de2f3cd642cf5343679e07ca3a768b085", size = 190520, upload-time = "2025-10-04T21:57:27.394Z" }, { url = "https://files.pythonhosted.org/packages/17/68/288e1e53c4f677906eac137d047720165aa1e10cd4d8b28f18d6b4c5c29a/propcache-0.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:e0ce7f3d1faf7ad58652ed758cc9753049af5308b38f89948aa71793282419c5", size = 199773, upload-time = "2025-10-04T21:57:28.733Z" }, { url = "https://files.pythonhosted.org/packages/f8/52/eff7750f9d7a20000143fb857b6215fc6ac1e897ca62eeb20ef6b62af4ea/propcache-0.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:545987971b2aded25ba4698135ea0ae128836e7deb6e18c29a581076aaef44aa", size = 200567, upload-time = "2025-10-04T21:57:30.241Z" }, { url = "https://files.pythonhosted.org/packages/27/a5/1ecedd68f72554c7c9e5508158b7634eb84a24cb8880183d1cd74a433d6e/propcache-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7da5c4c72ae40fd3ce87213ab057db66df53e55600d0b9e72e2b7f5a470a2cc4", size = 192963, upload-time = "2025-10-04T21:57:32.382Z" }, { url = "https://files.pythonhosted.org/packages/67/0a/aeaf727f5a0dcea135e2f90469d2410767d84ef386005219a50825381021/propcache-0.4.0-cp39-cp39-win32.whl", hash = "sha256:2015218812ee8f13bbaebc9f52b1e424cc130b68d4857bef018e65e3834e1c4d", size = 38223, upload-time = "2025-10-04T21:57:33.889Z" }, { url = "https://files.pythonhosted.org/packages/45/0b/604ba6b7ad807312a2a0125a64c46cd83a12399fc29c1646cc42cf814f9e/propcache-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:39f0f6a3b56e82dc91d84c763b783c5c33720a33c70ee48a1c13ba800ac1fa69", size = 41782, upload-time = "2025-10-04T21:57:35.522Z" }, { url = "https://files.pythonhosted.org/packages/77/30/2107b450136c26fc5b3c3a811e80898b6343a4c514bedee4c6a3d098723e/propcache-0.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:236c8da353ea7c22a8e963ab78cddb1126f700ae9538e2c4c6ef471e5545494b", size = 38329, upload-time = "2025-10-04T21:57:36.959Z" }, { url = "https://files.pythonhosted.org/packages/c7/16/794c114f6041bbe2de23eb418ef58a0f45de27224d5540f5dbb266a73d72/propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557", size = 13183, upload-time = "2025-10-04T21:57:38.054Z" }, ] [[package]] name = "py-partiql-parser" version = "0.6.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/58/a1/0a2867e48b232b4f82c4929ef7135f2a5d72c3886b957dccf63c70aa2fcb/py_partiql_parser-0.6.1.tar.gz", hash = "sha256:8583ff2a0e15560ef3bc3df109a7714d17f87d81d33e8c38b7fed4e58a63215d", size = 17120, upload-time = "2024-12-25T22:06:41.327Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/97/84/0e410c20bbe9a504fc56e97908f13261c2b313d16cbb3b738556166f044a/py_partiql_parser-0.6.1-py2.py3-none-any.whl", hash = "sha256:ff6a48067bff23c37e9044021bf1d949c83e195490c17e020715e927fe5b2456", size = 23520, upload-time = "2024-12-25T22:06:39.106Z" }, ] [[package]] name = "pyasn1" version = "0.6.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, ] [[package]] name = "pycparser" version = "2.23" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, ] [[package]] name = "pydantic" version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, { name = "typing-inspection" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/da/b8a7ee04378a53f6fefefc0c5e05570a3ebfdfa0523a878bcd3b475683ee/pydantic-2.12.0.tar.gz", hash = "sha256:c1a077e6270dbfb37bfd8b498b3981e2bb18f68103720e51fa6c306a5a9af563", size = 814760, upload-time = "2025-10-07T15:58:03.467Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/9d/d5c855424e2e5b6b626fbc6ec514d8e655a600377ce283008b115abb7445/pydantic-2.12.0-py3-none-any.whl", hash = "sha256:f6a1da352d42790537e95e83a8bdfb91c7efbae63ffd0b86fa823899e807116f", size = 459730, upload-time = "2025-10-07T15:58:01.576Z" }, ] [[package]] name = "pydantic-core" version = "2.41.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/14/12b4a0d2b0b10d8e1d9a24ad94e7bbb43335eaf29c0c4e57860e8a30734a/pydantic_core-2.41.1.tar.gz", hash = "sha256:1ad375859a6d8c356b7704ec0f547a58e82ee80bb41baa811ad710e124bc8f2f", size = 454870, upload-time = "2025-10-07T10:50:45.974Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/2c/a5c4640dc7132540109f67fe83b566fbc7512ccf2a068cfa22a243df70c7/pydantic_core-2.41.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e63036298322e9aea1c8b7c0a6c1204d615dbf6ec0668ce5b83ff27f07404a61", size = 2113814, upload-time = "2025-10-06T21:09:50.892Z" }, { url = "https://files.pythonhosted.org/packages/e3/e7/a8694c3454a57842095d69c7a4ab3cf81c3c7b590f052738eabfdfc2e234/pydantic_core-2.41.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:241299ca91fc77ef64f11ed909d2d9220a01834e8e6f8de61275c4dd16b7c936", size = 1916660, upload-time = "2025-10-06T21:09:52.783Z" }, { url = "https://files.pythonhosted.org/packages/9c/58/29f12e65b19c1877a0269eb4f23c5d2267eded6120a7d6762501ab843dc9/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab7e594a2a5c24ab8013a7dc8cfe5f2260e80e490685814122081705c2cf2b0", size = 1975071, upload-time = "2025-10-06T21:09:54.009Z" }, { url = "https://files.pythonhosted.org/packages/98/26/4e677f2b7ec3fbdd10be6b586a82a814c8ebe3e474024c8df2d4260e564e/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b054ef1a78519cb934b58e9c90c09e93b837c935dcd907b891f2b265b129eb6e", size = 2067271, upload-time = "2025-10-06T21:09:55.175Z" }, { url = "https://files.pythonhosted.org/packages/29/50/50614bd906089904d7ca1be3b9ecf08c00a327143d48f1decfdc21b3c302/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2ab7d10d0ab2ed6da54c757233eb0f48ebfb4f86e9b88ccecb3f92bbd61a538", size = 2253207, upload-time = "2025-10-06T21:09:56.709Z" }, { url = "https://files.pythonhosted.org/packages/ea/58/b1e640b4ca559273cca7c28e0fe8891d5d8e9a600f5ab4882670ec107549/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2757606b7948bb853a27e4040820306eaa0ccb9e8f9f8a0fa40cb674e170f350", size = 2375052, upload-time = "2025-10-06T21:09:57.97Z" }, { url = "https://files.pythonhosted.org/packages/53/25/cd47df3bfb24350e03835f0950288d1054f1cc9a8023401dabe6d4ff2834/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec0e75eb61f606bad0a32f2be87507087514e26e8c73db6cbdb8371ccd27917", size = 2076834, upload-time = "2025-10-06T21:09:59.58Z" }, { url = "https://files.pythonhosted.org/packages/ec/b4/71b2c77e5df527fbbc1a03e72c3fd96c44cd10d4241a81befef8c12b9fc4/pydantic_core-2.41.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0234236514f44a5bf552105cfe2543a12f48203397d9d0f866affa569345a5b5", size = 2195374, upload-time = "2025-10-06T21:10:01.18Z" }, { url = "https://files.pythonhosted.org/packages/aa/08/4b8a50733005865efde284fec45da75fe16a258f706e16323c5ace4004eb/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1b974e41adfbb4ebb0f65fc4ca951347b17463d60893ba7d5f7b9bb087c83897", size = 2156060, upload-time = "2025-10-06T21:10:02.74Z" }, { url = "https://files.pythonhosted.org/packages/83/c3/1037cb603ef2130c210150a51b1710d86825b5c28df54a55750099f91196/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:248dafb3204136113c383e91a4d815269f51562b6659b756cf3df14eefc7d0bb", size = 2331640, upload-time = "2025-10-06T21:10:04.39Z" }, { url = "https://files.pythonhosted.org/packages/56/4c/52d111869610e6b1a46e1f1035abcdc94d0655587e39104433a290e9f377/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:678f9d76a91d6bcedd7568bbf6beb77ae8447f85d1aeebaab7e2f0829cfc3a13", size = 2329844, upload-time = "2025-10-06T21:10:05.68Z" }, { url = "https://files.pythonhosted.org/packages/32/5d/4b435f0b52ab543967761aca66b84ad3f0026e491e57de47693d15d0a8db/pydantic_core-2.41.1-cp310-cp310-win32.whl", hash = "sha256:dff5bee1d21ee58277900692a641925d2dddfde65182c972569b1a276d2ac8fb", size = 1991289, upload-time = "2025-10-06T21:10:07.199Z" }, { url = "https://files.pythonhosted.org/packages/88/52/31b4deafc1d3cb96d0e7c0af70f0dc05454982d135d07f5117e6336153e8/pydantic_core-2.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:5042da12e5d97d215f91567110fdfa2e2595a25f17c19b9ff024f31c34f9b53e", size = 2027747, upload-time = "2025-10-06T21:10:08.503Z" }, { url = "https://files.pythonhosted.org/packages/f6/a9/ec440f02e57beabdfd804725ef1e38ac1ba00c49854d298447562e119513/pydantic_core-2.41.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4f276a6134fe1fc1daa692642a3eaa2b7b858599c49a7610816388f5e37566a1", size = 2111456, upload-time = "2025-10-06T21:10:09.824Z" }, { url = "https://files.pythonhosted.org/packages/f0/f9/6bc15bacfd8dcfc073a1820a564516d9c12a435a9a332d4cbbfd48828ddd/pydantic_core-2.41.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07588570a805296ece009c59d9a679dc08fab72fb337365afb4f3a14cfbfc176", size = 1915012, upload-time = "2025-10-06T21:10:11.599Z" }, { url = "https://files.pythonhosted.org/packages/38/8a/d9edcdcdfe80bade17bed424284427c08bea892aaec11438fa52eaeaf79c/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28527e4b53400cd60ffbd9812ccb2b5135d042129716d71afd7e45bf42b855c0", size = 1973762, upload-time = "2025-10-06T21:10:13.154Z" }, { url = "https://files.pythonhosted.org/packages/d5/b3/ff225c6d49fba4279de04677c1c876fc3dc6562fd0c53e9bfd66f58c51a8/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46a1c935c9228bad738c8a41de06478770927baedf581d172494ab36a6b96575", size = 2065386, upload-time = "2025-10-06T21:10:14.436Z" }, { url = "https://files.pythonhosted.org/packages/47/ba/183e8c0be4321314af3fd1ae6bfc7eafdd7a49bdea5da81c56044a207316/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:447ddf56e2b7d28d200d3e9eafa936fe40485744b5a824b67039937580b3cb20", size = 2252317, upload-time = "2025-10-06T21:10:15.719Z" }, { url = "https://files.pythonhosted.org/packages/57/c5/aab61e94fd02f45c65f1f8c9ec38bb3b33fbf001a1837c74870e97462572/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63892ead40c1160ac860b5debcc95c95c5a0035e543a8b5a4eac70dd22e995f4", size = 2373405, upload-time = "2025-10-06T21:10:17.017Z" }, { url = "https://files.pythonhosted.org/packages/e5/4f/3aaa3bd1ea420a15acc42d7d3ccb3b0bbc5444ae2f9dbc1959f8173e16b8/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4a9543ca355e6df8fbe9c83e9faab707701e9103ae857ecb40f1c0cf8b0e94d", size = 2073794, upload-time = "2025-10-06T21:10:18.383Z" }, { url = "https://files.pythonhosted.org/packages/58/bd/e3975cdebe03ec080ef881648de316c73f2a6be95c14fc4efb2f7bdd0d41/pydantic_core-2.41.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2611bdb694116c31e551ed82e20e39a90bea9b7ad9e54aaf2d045ad621aa7a1", size = 2194430, upload-time = "2025-10-06T21:10:19.638Z" }, { url = "https://files.pythonhosted.org/packages/2b/b8/6b7e7217f147d3b3105b57fb1caec3c4f667581affdfaab6d1d277e1f749/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fecc130893a9b5f7bfe230be1bb8c61fe66a19db8ab704f808cb25a82aad0bc9", size = 2154611, upload-time = "2025-10-06T21:10:21.28Z" }, { url = "https://files.pythonhosted.org/packages/fe/7b/239c2fe76bd8b7eef9ae2140d737368a3c6fea4fd27f8f6b4cde6baa3ce9/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:1e2df5f8344c99b6ea5219f00fdc8950b8e6f2c422fbc1cc122ec8641fac85a1", size = 2329809, upload-time = "2025-10-06T21:10:22.678Z" }, { url = "https://files.pythonhosted.org/packages/bd/2e/77a821a67ff0786f2f14856d6bd1348992f695ee90136a145d7a445c1ff6/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:35291331e9d8ed94c257bab6be1cb3a380b5eee570a2784bffc055e18040a2ea", size = 2327907, upload-time = "2025-10-06T21:10:24.447Z" }, { url = "https://files.pythonhosted.org/packages/fd/9a/b54512bb9df7f64c586b369328c30481229b70ca6a5fcbb90b715e15facf/pydantic_core-2.41.1-cp311-cp311-win32.whl", hash = "sha256:2876a095292668d753f1a868c4a57c4ac9f6acbd8edda8debe4218d5848cf42f", size = 1989964, upload-time = "2025-10-06T21:10:25.676Z" }, { url = "https://files.pythonhosted.org/packages/9d/72/63c9a4f1a5c950e65dd522d7dd67f167681f9d4f6ece3b80085a0329f08f/pydantic_core-2.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:b92d6c628e9a338846a28dfe3fcdc1a3279388624597898b105e078cdfc59298", size = 2025158, upload-time = "2025-10-06T21:10:27.522Z" }, { url = "https://files.pythonhosted.org/packages/d8/16/4e2706184209f61b50c231529257c12eb6bd9eb36e99ea1272e4815d2200/pydantic_core-2.41.1-cp311-cp311-win_arm64.whl", hash = "sha256:7d82ae99409eb69d507a89835488fb657faa03ff9968a9379567b0d2e2e56bc5", size = 1972297, upload-time = "2025-10-06T21:10:28.814Z" }, { url = "https://files.pythonhosted.org/packages/ee/bc/5f520319ee1c9e25010412fac4154a72e0a40d0a19eb00281b1f200c0947/pydantic_core-2.41.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:db2f82c0ccbce8f021ad304ce35cbe02aa2f95f215cac388eed542b03b4d5eb4", size = 2099300, upload-time = "2025-10-06T21:10:30.463Z" }, { url = "https://files.pythonhosted.org/packages/31/14/010cd64c5c3814fb6064786837ec12604be0dd46df3327cf8474e38abbbd/pydantic_core-2.41.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47694a31c710ced9205d5f1e7e8af3ca57cbb8a503d98cb9e33e27c97a501601", size = 1910179, upload-time = "2025-10-06T21:10:31.782Z" }, { url = "https://files.pythonhosted.org/packages/8e/2e/23fc2a8a93efad52df302fdade0a60f471ecc0c7aac889801ac24b4c07d6/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e9decce94daf47baf9e9d392f5f2557e783085f7c5e522011545d9d6858e00", size = 1957225, upload-time = "2025-10-06T21:10:33.11Z" }, { url = "https://files.pythonhosted.org/packages/b9/b6/6db08b2725b2432b9390844852e11d320281e5cea8a859c52c68001975fa/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab0adafdf2b89c8b84f847780a119437a0931eca469f7b44d356f2b426dd9741", size = 2053315, upload-time = "2025-10-06T21:10:34.87Z" }, { url = "https://files.pythonhosted.org/packages/61/d9/4de44600f2d4514b44f3f3aeeda2e14931214b6b5bf52479339e801ce748/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5da98cc81873f39fd56882e1569c4677940fbc12bce6213fad1ead784192d7c8", size = 2224298, upload-time = "2025-10-06T21:10:36.233Z" }, { url = "https://files.pythonhosted.org/packages/7a/ae/dbe51187a7f35fc21b283c5250571a94e36373eb557c1cba9f29a9806dcf/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:209910e88afb01fd0fd403947b809ba8dba0e08a095e1f703294fda0a8fdca51", size = 2351797, upload-time = "2025-10-06T21:10:37.601Z" }, { url = "https://files.pythonhosted.org/packages/b5/a7/975585147457c2e9fb951c7c8dab56deeb6aa313f3aa72c2fc0df3f74a49/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365109d1165d78d98e33c5bfd815a9b5d7d070f578caefaabcc5771825b4ecb5", size = 2074921, upload-time = "2025-10-06T21:10:38.927Z" }, { url = "https://files.pythonhosted.org/packages/62/37/ea94d1d0c01dec1b7d236c7cec9103baab0021f42500975de3d42522104b/pydantic_core-2.41.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:706abf21e60a2857acdb09502bc853ee5bce732955e7b723b10311114f033115", size = 2187767, upload-time = "2025-10-06T21:10:40.651Z" }, { url = "https://files.pythonhosted.org/packages/d3/fe/694cf9fdd3a777a618c3afd210dba7b414cb8a72b1bd29b199c2e5765fee/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bf0bd5417acf7f6a7ec3b53f2109f587be176cb35f9cf016da87e6017437a72d", size = 2136062, upload-time = "2025-10-06T21:10:42.09Z" }, { url = "https://files.pythonhosted.org/packages/0f/ae/174aeabd89916fbd2988cc37b81a59e1186e952afd2a7ed92018c22f31ca/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2e71b1c6ceb9c78424ae9f63a07292fb769fb890a4e7efca5554c47f33a60ea5", size = 2317819, upload-time = "2025-10-06T21:10:43.974Z" }, { url = "https://files.pythonhosted.org/packages/65/e8/e9aecafaebf53fc456314f72886068725d6fba66f11b013532dc21259343/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80745b9770b4a38c25015b517451c817799bfb9d6499b0d13d8227ec941cb513", size = 2312267, upload-time = "2025-10-06T21:10:45.34Z" }, { url = "https://files.pythonhosted.org/packages/35/2f/1c2e71d2a052f9bb2f2df5a6a05464a0eb800f9e8d9dd800202fe31219e1/pydantic_core-2.41.1-cp312-cp312-win32.whl", hash = "sha256:83b64d70520e7890453f1aa21d66fda44e7b35f1cfea95adf7b4289a51e2b479", size = 1990927, upload-time = "2025-10-06T21:10:46.738Z" }, { url = "https://files.pythonhosted.org/packages/b1/78/562998301ff2588b9c6dcc5cb21f52fa919d6e1decc75a35055feb973594/pydantic_core-2.41.1-cp312-cp312-win_amd64.whl", hash = "sha256:377defd66ee2003748ee93c52bcef2d14fde48fe28a0b156f88c3dbf9bc49a50", size = 2034703, upload-time = "2025-10-06T21:10:48.524Z" }, { url = "https://files.pythonhosted.org/packages/b2/53/d95699ce5a5cdb44bb470bd818b848b9beadf51459fd4ea06667e8ede862/pydantic_core-2.41.1-cp312-cp312-win_arm64.whl", hash = "sha256:c95caff279d49c1d6cdfe2996e6c2ad712571d3b9caaa209a404426c326c4bde", size = 1972719, upload-time = "2025-10-06T21:10:50.256Z" }, { url = "https://files.pythonhosted.org/packages/27/8a/6d54198536a90a37807d31a156642aae7a8e1263ed9fe6fc6245defe9332/pydantic_core-2.41.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70e790fce5f05204ef4403159857bfcd587779da78627b0babb3654f75361ebf", size = 2105825, upload-time = "2025-10-06T21:10:51.719Z" }, { url = "https://files.pythonhosted.org/packages/4f/2e/4784fd7b22ac9c8439db25bf98ffed6853d01e7e560a346e8af821776ccc/pydantic_core-2.41.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cebf1ca35f10930612d60bd0f78adfacee824c30a880e3534ba02c207cceceb", size = 1910126, upload-time = "2025-10-06T21:10:53.145Z" }, { url = "https://files.pythonhosted.org/packages/f3/92/31eb0748059ba5bd0aa708fb4bab9fcb211461ddcf9e90702a6542f22d0d/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:170406a37a5bc82c22c3274616bf6f17cc7df9c4a0a0a50449e559cb755db669", size = 1961472, upload-time = "2025-10-06T21:10:55.754Z" }, { url = "https://files.pythonhosted.org/packages/ab/91/946527792275b5c4c7dde4cfa3e81241bf6900e9fee74fb1ba43e0c0f1ab/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12d4257fc9187a0ccd41b8b327d6a4e57281ab75e11dda66a9148ef2e1fb712f", size = 2063230, upload-time = "2025-10-06T21:10:57.179Z" }, { url = "https://files.pythonhosted.org/packages/31/5d/a35c5d7b414e5c0749f1d9f0d159ee2ef4bab313f499692896b918014ee3/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a75a33b4db105dd1c8d57839e17ee12db8d5ad18209e792fa325dbb4baeb00f4", size = 2229469, upload-time = "2025-10-06T21:10:59.409Z" }, { url = "https://files.pythonhosted.org/packages/21/4d/8713737c689afa57ecfefe38db78259d4484c97aa494979e6a9d19662584/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08a589f850803a74e0fcb16a72081cafb0d72a3cdda500106942b07e76b7bf62", size = 2347986, upload-time = "2025-10-06T21:11:00.847Z" }, { url = "https://files.pythonhosted.org/packages/f6/ec/929f9a3a5ed5cda767081494bacd32f783e707a690ce6eeb5e0730ec4986/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a97939d6ea44763c456bd8a617ceada2c9b96bb5b8ab3dfa0d0827df7619014", size = 2072216, upload-time = "2025-10-06T21:11:02.43Z" }, { url = "https://files.pythonhosted.org/packages/26/55/a33f459d4f9cc8786d9db42795dbecc84fa724b290d7d71ddc3d7155d46a/pydantic_core-2.41.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae423c65c556f09569524b80ffd11babff61f33055ef9773d7c9fabc11ed8d", size = 2193047, upload-time = "2025-10-06T21:11:03.787Z" }, { url = "https://files.pythonhosted.org/packages/77/af/d5c6959f8b089f2185760a2779079e3c2c411bfc70ea6111f58367851629/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:4dc703015fbf8764d6a8001c327a87f1823b7328d40b47ce6000c65918ad2b4f", size = 2140613, upload-time = "2025-10-06T21:11:05.607Z" }, { url = "https://files.pythonhosted.org/packages/58/e5/2c19bd2a14bffe7fabcf00efbfbd3ac430aaec5271b504a938ff019ac7be/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:968e4ffdfd35698a5fe659e5e44c508b53664870a8e61c8f9d24d3d145d30257", size = 2327641, upload-time = "2025-10-06T21:11:07.143Z" }, { url = "https://files.pythonhosted.org/packages/93/ef/e0870ccda798c54e6b100aff3c4d49df5458fd64217e860cb9c3b0a403f4/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fff2b76c8e172d34771cd4d4f0ade08072385310f214f823b5a6ad4006890d32", size = 2318229, upload-time = "2025-10-06T21:11:08.73Z" }, { url = "https://files.pythonhosted.org/packages/b1/4b/c3b991d95f5deb24d0bd52e47bcf716098fa1afe0ce2d4bd3125b38566ba/pydantic_core-2.41.1-cp313-cp313-win32.whl", hash = "sha256:a38a5263185407ceb599f2f035faf4589d57e73c7146d64f10577f6449e8171d", size = 1997911, upload-time = "2025-10-06T21:11:10.329Z" }, { url = "https://files.pythonhosted.org/packages/a7/ce/5c316fd62e01f8d6be1b7ee6b54273214e871772997dc2c95e204997a055/pydantic_core-2.41.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42ae7fd6760782c975897e1fdc810f483b021b32245b0105d40f6e7a3803e4b", size = 2034301, upload-time = "2025-10-06T21:11:12.113Z" }, { url = "https://files.pythonhosted.org/packages/29/41/902640cfd6a6523194123e2c3373c60f19006447f2fb06f76de4e8466c5b/pydantic_core-2.41.1-cp313-cp313-win_arm64.whl", hash = "sha256:ad4111acc63b7384e205c27a2f15e23ac0ee21a9d77ad6f2e9cb516ec90965fb", size = 1977238, upload-time = "2025-10-06T21:11:14.1Z" }, { url = "https://files.pythonhosted.org/packages/04/04/28b040e88c1b89d851278478842f0bdf39c7a05da9e850333c6c8cbe7dfa/pydantic_core-2.41.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:440d0df7415b50084a4ba9d870480c16c5f67c0d1d4d5119e3f70925533a0edc", size = 1875626, upload-time = "2025-10-06T21:11:15.69Z" }, { url = "https://files.pythonhosted.org/packages/d6/58/b41dd3087505220bb58bc81be8c3e8cbc037f5710cd3c838f44f90bdd704/pydantic_core-2.41.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71eaa38d342099405dae6484216dcf1e8e4b0bebd9b44a4e08c9b43db6a2ab67", size = 2045708, upload-time = "2025-10-06T21:11:17.258Z" }, { url = "https://files.pythonhosted.org/packages/d7/b8/760f23754e40bf6c65b94a69b22c394c24058a0ef7e2aa471d2e39219c1a/pydantic_core-2.41.1-cp313-cp313t-win_amd64.whl", hash = "sha256:555ecf7e50f1161d3f693bc49f23c82cf6cdeafc71fa37a06120772a09a38795", size = 1997171, upload-time = "2025-10-06T21:11:18.822Z" }, { url = "https://files.pythonhosted.org/packages/41/12/cec246429ddfa2778d2d6301eca5362194dc8749ecb19e621f2f65b5090f/pydantic_core-2.41.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:05226894a26f6f27e1deb735d7308f74ef5fa3a6de3e0135bb66cdcaee88f64b", size = 2107836, upload-time = "2025-10-06T21:11:20.432Z" }, { url = "https://files.pythonhosted.org/packages/20/39/baba47f8d8b87081302498e610aefc37142ce6a1cc98b2ab6b931a162562/pydantic_core-2.41.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85ff7911c6c3e2fd8d3779c50925f6406d770ea58ea6dde9c230d35b52b16b4a", size = 1904449, upload-time = "2025-10-06T21:11:22.185Z" }, { url = "https://files.pythonhosted.org/packages/50/32/9a3d87cae2c75a5178334b10358d631bd094b916a00a5993382222dbfd92/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47f1f642a205687d59b52dc1a9a607f45e588f5a2e9eeae05edd80c7a8c47674", size = 1961750, upload-time = "2025-10-06T21:11:24.348Z" }, { url = "https://files.pythonhosted.org/packages/27/42/a96c9d793a04cf2a9773bff98003bb154087b94f5530a2ce6063ecfec583/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df11c24e138876ace5ec6043e5cae925e34cf38af1a1b3d63589e8f7b5f5cdc4", size = 2063305, upload-time = "2025-10-06T21:11:26.556Z" }, { url = "https://files.pythonhosted.org/packages/3e/8d/028c4b7d157a005b1f52c086e2d4b0067886b213c86220c1153398dbdf8f/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f0bf7f5c8f7bf345c527e8a0d72d6b26eda99c1227b0c34e7e59e181260de31", size = 2228959, upload-time = "2025-10-06T21:11:28.426Z" }, { url = "https://files.pythonhosted.org/packages/08/f7/ee64cda8fcc9ca3f4716e6357144f9ee71166775df582a1b6b738bf6da57/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82b887a711d341c2c47352375d73b029418f55b20bd7815446d175a70effa706", size = 2345421, upload-time = "2025-10-06T21:11:30.226Z" }, { url = "https://files.pythonhosted.org/packages/13/c0/e8ec05f0f5ee7a3656973ad9cd3bc73204af99f6512c1a4562f6fb4b3f7d/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5f1d5d6bbba484bdf220c72d8ecd0be460f4bd4c5e534a541bb2cd57589fb8b", size = 2065288, upload-time = "2025-10-06T21:11:32.019Z" }, { url = "https://files.pythonhosted.org/packages/0a/25/d77a73ff24e2e4fcea64472f5e39b0402d836da9b08b5361a734d0153023/pydantic_core-2.41.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bf1917385ebe0f968dc5c6ab1375886d56992b93ddfe6bf52bff575d03662be", size = 2189759, upload-time = "2025-10-06T21:11:33.753Z" }, { url = "https://files.pythonhosted.org/packages/66/45/4a4ebaaae12a740552278d06fe71418c0f2869537a369a89c0e6723b341d/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4f94f3ab188f44b9a73f7295663f3ecb8f2e2dd03a69c8f2ead50d37785ecb04", size = 2140747, upload-time = "2025-10-06T21:11:35.781Z" }, { url = "https://files.pythonhosted.org/packages/da/6d/b727ce1022f143194a36593243ff244ed5a1eb3c9122296bf7e716aa37ba/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:3925446673641d37c30bd84a9d597e49f72eacee8b43322c8999fa17d5ae5bc4", size = 2327416, upload-time = "2025-10-06T21:11:37.75Z" }, { url = "https://files.pythonhosted.org/packages/6f/8c/02df9d8506c427787059f87c6c7253435c6895e12472a652d9616ee0fc95/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:49bd51cc27adb980c7b97357ae036ce9b3c4d0bb406e84fbe16fb2d368b602a8", size = 2318138, upload-time = "2025-10-06T21:11:39.463Z" }, { url = "https://files.pythonhosted.org/packages/98/67/0cf429a7d6802536941f430e6e3243f6d4b68f41eeea4b242372f1901794/pydantic_core-2.41.1-cp314-cp314-win32.whl", hash = "sha256:a31ca0cd0e4d12ea0df0077df2d487fc3eb9d7f96bbb13c3c5b88dcc21d05159", size = 1998429, upload-time = "2025-10-06T21:11:41.989Z" }, { url = "https://files.pythonhosted.org/packages/38/60/742fef93de5d085022d2302a6317a2b34dbfe15258e9396a535c8a100ae7/pydantic_core-2.41.1-cp314-cp314-win_amd64.whl", hash = "sha256:1b5c4374a152e10a22175d7790e644fbd8ff58418890e07e2073ff9d4414efae", size = 2028870, upload-time = "2025-10-06T21:11:43.66Z" }, { url = "https://files.pythonhosted.org/packages/31/38/cdd8ccb8555ef7720bd7715899bd6cfbe3c29198332710e1b61b8f5dd8b8/pydantic_core-2.41.1-cp314-cp314-win_arm64.whl", hash = "sha256:4fee76d757639b493eb600fba668f1e17475af34c17dd61db7a47e824d464ca9", size = 1974275, upload-time = "2025-10-06T21:11:45.476Z" }, { url = "https://files.pythonhosted.org/packages/e7/7e/8ac10ccb047dc0221aa2530ec3c7c05ab4656d4d4bd984ee85da7f3d5525/pydantic_core-2.41.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f9b9c968cfe5cd576fdd7361f47f27adeb120517e637d1b189eea1c3ece573f4", size = 1875124, upload-time = "2025-10-06T21:11:47.591Z" }, { url = "https://files.pythonhosted.org/packages/c3/e4/7d9791efeb9c7d97e7268f8d20e0da24d03438a7fa7163ab58f1073ba968/pydantic_core-2.41.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ebc7ab67b856384aba09ed74e3e977dded40e693de18a4f197c67d0d4e6d8e", size = 2043075, upload-time = "2025-10-06T21:11:49.542Z" }, { url = "https://files.pythonhosted.org/packages/2d/c3/3f6e6b2342ac11ac8cd5cb56e24c7b14afa27c010e82a765ffa5f771884a/pydantic_core-2.41.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8ae0dc57b62a762985bc7fbf636be3412394acc0ddb4ade07fe104230f1b9762", size = 1995341, upload-time = "2025-10-06T21:11:51.497Z" }, { url = "https://files.pythonhosted.org/packages/aa/d2/90421a4749f15aa4f06dd1d25a6419b91b181ae7994a4e7c4ed0a6415057/pydantic_core-2.41.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:10ce489cf09a4956a1549af839b983edc59b0f60e1b068c21b10154e58f54f80", size = 2114974, upload-time = "2025-10-06T21:11:53.549Z" }, { url = "https://files.pythonhosted.org/packages/39/6a/3b5c2ba43da5380f252b35f7e74851e1379f4935c8bccbbda05992b5fe4d/pydantic_core-2.41.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff548c908caffd9455fd1342366bcf8a1ec8a3fca42f35c7fc60883d6a901074", size = 1940064, upload-time = "2025-10-06T21:11:55.268Z" }, { url = "https://files.pythonhosted.org/packages/81/a9/050595183529316cf95d0f97662a4fe782dbea5f31dba0cf366015b67fad/pydantic_core-2.41.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d43bf082025082bda13be89a5f876cc2386b7727c7b322be2d2b706a45cea8e", size = 1976637, upload-time = "2025-10-06T21:11:57.024Z" }, { url = "https://files.pythonhosted.org/packages/46/a8/846a8e466edd841c67f11f0ae738ca5c5d87968f6d8630bc449e2e6e11f2/pydantic_core-2.41.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:666aee751faf1c6864b2db795775dd67b61fdcf646abefa309ed1da039a97209", size = 2069274, upload-time = "2025-10-06T21:11:59.129Z" }, { url = "https://files.pythonhosted.org/packages/4c/dc/19d01747082daf3667f952b6deee73e9e63338caa9c61442558cbdf8c876/pydantic_core-2.41.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b83aaeff0d7bde852c32e856f3ee410842ebc08bc55c510771d87dcd1c01e1ed", size = 2255302, upload-time = "2025-10-07T10:49:36.917Z" }, { url = "https://files.pythonhosted.org/packages/fa/99/0d4f031aeddf2cf03a5eb8eafde50147259067716c32174551b786aa72e1/pydantic_core-2.41.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:055c7931b0329cb8acde20cdde6d9c2cbc2a02a0a8e54a792cddd91e2ea92c65", size = 2386549, upload-time = "2025-10-07T10:49:39.385Z" }, { url = "https://files.pythonhosted.org/packages/09/7f/027061a060718733a6c016e7d4acc864c8bb69f0092d9b3da7e3888b102f/pydantic_core-2.41.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530bbb1347e3e5ca13a91ac087c4971d7da09630ef8febd27a20a10800c2d06d", size = 2079817, upload-time = "2025-10-07T10:49:41.409Z" }, { url = "https://files.pythonhosted.org/packages/3a/5e/791c16d5e2a0b394c2c236f7d2556dbc381f8666bc12db7d35dc051c67e3/pydantic_core-2.41.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65a0ea16cfea7bfa9e43604c8bd726e63a3788b61c384c37664b55209fcb1d74", size = 2196276, upload-time = "2025-10-07T10:49:43.367Z" }, { url = "https://files.pythonhosted.org/packages/a3/99/2c7010145da82fdd30955c1c0e1e75723ca7aef32b52f2565383fd2347d2/pydantic_core-2.41.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8fa93fadff794c6d15c345c560513b160197342275c6d104cc879f932b978afc", size = 2157417, upload-time = "2025-10-07T10:49:45.176Z" }, { url = "https://files.pythonhosted.org/packages/c6/df/b8f2ac7fa15479e989d0c2ea88e5e28eeb923096b2462804b9113bce51b5/pydantic_core-2.41.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c8a1af9ac51969a494c6a82b563abae6859dc082d3b999e8fa7ba5ee1b05e8e8", size = 2333884, upload-time = "2025-10-07T10:49:46.896Z" }, { url = "https://files.pythonhosted.org/packages/60/e8/06387d852bf67402fb0129b3297aa0c358aa9647e59f795c0965a7bedefe/pydantic_core-2.41.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30edab28829703f876897c9471a857e43d847b8799c3c9e2fbce644724b50aa4", size = 2330735, upload-time = "2025-10-07T10:49:48.79Z" }, { url = "https://files.pythonhosted.org/packages/07/41/8964006fd8897df13cb0eec374bda053d1936cbc81315acdd755d85c99d5/pydantic_core-2.41.1-cp39-cp39-win32.whl", hash = "sha256:84d0ff869f98be2e93efdf1ae31e5a15f0926d22af8677d51676e373abbfe57a", size = 1992855, upload-time = "2025-10-07T10:49:50.806Z" }, { url = "https://files.pythonhosted.org/packages/d3/c9/0f68c22ba0cac693326a7de73f04c7543886e0b240e2320f8ced861f0c3d/pydantic_core-2.41.1-cp39-cp39-win_amd64.whl", hash = "sha256:b5674314987cdde5a5511b029fa5fb1556b3d147a367e01dd583b19cfa8e35df", size = 2030219, upload-time = "2025-10-07T10:49:52.712Z" }, { url = "https://files.pythonhosted.org/packages/16/89/d0afad37ba25f5801735af1472e650b86baad9fe807a42076508e4824a2a/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:68f2251559b8efa99041bb63571ec7cdd2d715ba74cc82b3bc9eff824ebc8bf0", size = 2124001, upload-time = "2025-10-07T10:49:54.369Z" }, { url = "https://files.pythonhosted.org/packages/8e/c4/08609134b34520568ddebb084d9ed0a2a3f5f52b45739e6e22cb3a7112eb/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:c7bc140c596097cb53b30546ca257dbe3f19282283190b1b5142928e5d5d3a20", size = 1941841, upload-time = "2025-10-07T10:49:56.248Z" }, { url = "https://files.pythonhosted.org/packages/2a/43/94a4877094e5fe19a3f37e7e817772263e2c573c94f1e3fa2b1eee56ef3b/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2896510fce8f4725ec518f8b9d7f015a00db249d2fd40788f442af303480063d", size = 1961129, upload-time = "2025-10-07T10:49:58.298Z" }, { url = "https://files.pythonhosted.org/packages/a2/30/23a224d7e25260eb5f69783a63667453037e07eb91ff0e62dabaadd47128/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ced20e62cfa0f496ba68fa5d6c7ee71114ea67e2a5da3114d6450d7f4683572a", size = 2148770, upload-time = "2025-10-07T10:49:59.959Z" }, { url = "https://files.pythonhosted.org/packages/2b/3e/a51c5f5d37b9288ba30683d6e96f10fa8f1defad1623ff09f1020973b577/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b04fa9ed049461a7398138c604b00550bc89e3e1151d84b81ad6dc93e39c4c06", size = 2115344, upload-time = "2025-10-07T10:50:02.466Z" }, { url = "https://files.pythonhosted.org/packages/5a/bd/389504c9e0600ef4502cd5238396b527afe6ef8981a6a15cd1814fc7b434/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b3b7d9cfbfdc43c80a16638c6dc2768e3956e73031fca64e8e1a3ae744d1faeb", size = 1927994, upload-time = "2025-10-07T10:50:04.379Z" }, { url = "https://files.pythonhosted.org/packages/ff/9c/5111c6b128861cb792a4c082677e90dac4f2e090bb2e2fe06aa5b2d39027/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eec83fc6abef04c7f9bec616e2d76ee9a6a4ae2a359b10c21d0f680e24a247ca", size = 1959394, upload-time = "2025-10-07T10:50:06.335Z" }, { url = "https://files.pythonhosted.org/packages/14/3f/cfec8b9a0c48ce5d64409ec5e1903cb0b7363da38f14b41de2fcb3712700/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6771a2d9f83c4038dfad5970a3eef215940682b2175e32bcc817bdc639019b28", size = 2147365, upload-time = "2025-10-07T10:50:07.978Z" }, { url = "https://files.pythonhosted.org/packages/d4/31/f403d7ca8352e3e4df352ccacd200f5f7f7fe81cef8e458515f015091625/pydantic_core-2.41.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fabcbdb12de6eada8d6e9a759097adb3c15440fafc675b3e94ae5c9cb8d678a0", size = 2114268, upload-time = "2025-10-07T10:50:10.257Z" }, { url = "https://files.pythonhosted.org/packages/6e/b5/334473b6d2810df84db67f03d4f666acacfc538512c2d2a254074fee0889/pydantic_core-2.41.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e97ccfaf0aaf67d55de5085b0ed0d994f57747d9d03f2de5cc9847ca737b08", size = 1935786, upload-time = "2025-10-07T10:50:12.333Z" }, { url = "https://files.pythonhosted.org/packages/ea/5e/45513e4dc621f47397cfa5fef12ba8fa5e8b1c4c07f2ff2a5fef8ff81b25/pydantic_core-2.41.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34df1fe8fea5d332484a763702e8b6a54048a9d4fe6ccf41e34a128238e01f52", size = 1971995, upload-time = "2025-10-07T10:50:14.071Z" }, { url = "https://files.pythonhosted.org/packages/22/e3/f1797c168e5f52b973bed1c585e99827a22d5e579d1ed57d51bc15b14633/pydantic_core-2.41.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:421b5595f845842fc093f7250e24ee395f54ca62d494fdde96f43ecf9228ae01", size = 2191264, upload-time = "2025-10-07T10:50:15.788Z" }, { url = "https://files.pythonhosted.org/packages/bb/e1/24ef4c3b4ab91c21c3a09a966c7d2cffe101058a7bfe5cc8b2c7c7d574e2/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dce8b22663c134583aaad24827863306a933f576c79da450be3984924e2031d1", size = 2152430, upload-time = "2025-10-07T10:50:18.018Z" }, { url = "https://files.pythonhosted.org/packages/35/74/70c1e225d67f7ef3fdba02c506d9011efaf734020914920b2aa3d1a45e61/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:300a9c162fea9906cc5c103893ca2602afd84f0ec90d3be36f4cc360125d22e1", size = 2324691, upload-time = "2025-10-07T10:50:19.801Z" }, { url = "https://files.pythonhosted.org/packages/c8/bf/dd4d21037c8bef0d8cce90a86a3f2dcb011c30086db2a10113c3eea23eba/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e019167628f6e6161ae7ab9fb70f6d076a0bf0d55aa9b20833f86a320c70dd65", size = 2324493, upload-time = "2025-10-07T10:50:21.568Z" }, { url = "https://files.pythonhosted.org/packages/7e/78/3093b334e9c9796c8236a4701cd2ddef1c56fb0928fe282a10c797644380/pydantic_core-2.41.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:13ab9cc2de6f9d4ab645a050ae5aee61a2424ac4d3a16ba23d4c2027705e0301", size = 2146156, upload-time = "2025-10-07T10:50:23.475Z" }, { url = "https://files.pythonhosted.org/packages/e6/6c/fa3e45c2b054a1e627a89a364917f12cbe3abc3e91b9004edaae16e7b3c5/pydantic_core-2.41.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:af2385d3f98243fb733862f806c5bb9122e5fba05b373e3af40e3c82d711cef1", size = 2112094, upload-time = "2025-10-07T10:50:25.513Z" }, { url = "https://files.pythonhosted.org/packages/e5/17/7eebc38b4658cc8e6902d0befc26388e4c2a5f2e179c561eeb43e1922c7b/pydantic_core-2.41.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6550617a0c2115be56f90c31a5370261d8ce9dbf051c3ed53b51172dd34da696", size = 1935300, upload-time = "2025-10-07T10:50:27.715Z" }, { url = "https://files.pythonhosted.org/packages/2b/00/9fe640194a1717a464ab861d43595c268830f98cb1e2705aa134b3544b70/pydantic_core-2.41.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc17b6ecf4983d298686014c92ebc955a9f9baf9f57dad4065e7906e7bee6222", size = 1970417, upload-time = "2025-10-07T10:50:29.573Z" }, { url = "https://files.pythonhosted.org/packages/b2/ad/f4cdfaf483b78ee65362363e73b6b40c48e067078d7b146e8816d5945ad6/pydantic_core-2.41.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42ae9352cf211f08b04ea110563d6b1e415878eea5b4c70f6bdb17dca3b932d2", size = 2190745, upload-time = "2025-10-07T10:50:31.48Z" }, { url = "https://files.pythonhosted.org/packages/cb/c1/18f416d40a10f44e9387497ba449f40fdb1478c61ba05c4b6bdb82300362/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e82947de92068b0a21681a13dd2102387197092fbe7defcfb8453e0913866506", size = 2150888, upload-time = "2025-10-07T10:50:33.477Z" }, { url = "https://files.pythonhosted.org/packages/42/30/134c8a921630d8a88d6f905a562495a6421e959a23c19b0f49b660801d67/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e244c37d5471c9acdcd282890c6c4c83747b77238bfa19429b8473586c907656", size = 2324489, upload-time = "2025-10-07T10:50:36.48Z" }, { url = "https://files.pythonhosted.org/packages/9c/48/a9263aeaebdec81e941198525b43edb3b44f27cfa4cb8005b8d3eb8dec72/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1e798b4b304a995110d41ec93653e57975620ccb2842ba9420037985e7d7284e", size = 2322763, upload-time = "2025-10-07T10:50:38.751Z" }, { url = "https://files.pythonhosted.org/packages/1d/62/755d2bd2593f701c5839fc084e9c2c5e2418f460383ad04e3b5d0befc3ca/pydantic_core-2.41.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f1fc716c0eb1663c59699b024428ad5ec2bcc6b928527b8fe28de6cb89f47efb", size = 2144046, upload-time = "2025-10-07T10:50:40.686Z" }, ] [[package]] name = "pyparsing" version = "3.2.5" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, ] [[package]] name = "pytest" version = "8.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/30/b7/7d44bbc04c531dcc753056920e0988032e5871ac674b5a84cb979de6e7af/pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044", size = 1409703, upload-time = "2024-03-09T11:51:08.012Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4d/7e/c79cecfdb6aa85c6c2e3cf63afc56d0f165f24f5c66c03c695c4d9b84756/pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7", size = 337359, upload-time = "2024-03-09T11:51:04.858Z" }, ] [[package]] name = "pytest-cov" version = "5.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" }, ] [[package]] name = "pytest-xdist" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/f4/ac9c4ccbc5984ebc3bef6dbdbcdaf553a1aae07c08e63b8b25a6239ecc45/pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a", size = 78977, upload-time = "2023-11-21T15:21:15.305Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/50/37/125fe5ec459321e2d48a0c38672cfc2419ad87d580196fd894e5f25230b0/pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24", size = 42017, upload-time = "2023-11-21T15:21:13.278Z" }, ] [[package]] name = "python-dateutil" version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, { url = "https://files.pythonhosted.org/packages/59/42/b86689aac0cdaee7ae1c58d464b0ff04ca909c19bb6502d4973cdd9f9544/pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b", size = 8760837, upload-time = "2025-07-14T20:12:59.59Z" }, { url = "https://files.pythonhosted.org/packages/9f/8a/1403d0353f8c5a2f0829d2b1c4becbf9da2f0a4d040886404fc4a5431e4d/pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91", size = 9590187, upload-time = "2025-07-14T20:13:01.419Z" }, { url = "https://files.pythonhosted.org/packages/60/22/e0e8d802f124772cec9c75430b01a212f86f9de7546bda715e54140d5aeb/pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d", size = 8778162, upload-time = "2025-07-14T20:13:03.544Z" }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, ] [[package]] name = "referencing" version = "0.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, ] [[package]] name = "regex" version = "2025.9.18" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/d8/7e06171db8e55f917c5b8e89319cea2d86982e3fc46b677f40358223dece/regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788", size = 484829, upload-time = "2025-09-19T00:35:05.215Z" }, { url = "https://files.pythonhosted.org/packages/8d/70/bf91bb39e5bedf75ce730ffbaa82ca585584d13335306d637458946b8b9f/regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4", size = 288993, upload-time = "2025-09-19T00:35:08.154Z" }, { url = "https://files.pythonhosted.org/packages/fe/89/69f79b28365eda2c46e64c39d617d5f65a2aa451a4c94de7d9b34c2dc80f/regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61", size = 286624, upload-time = "2025-09-19T00:35:09.717Z" }, { url = "https://files.pythonhosted.org/packages/44/31/81e62955726c3a14fcc1049a80bc716765af6c055706869de5e880ddc783/regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251", size = 780473, upload-time = "2025-09-19T00:35:11.013Z" }, { url = "https://files.pythonhosted.org/packages/fb/23/07072b7e191fbb6e213dc03b2f5b96f06d3c12d7deaded84679482926fc7/regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746", size = 849290, upload-time = "2025-09-19T00:35:12.348Z" }, { url = "https://files.pythonhosted.org/packages/b3/f0/aec7f6a01f2a112210424d77c6401b9015675fb887ced7e18926df4ae51e/regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2", size = 897335, upload-time = "2025-09-19T00:35:14.058Z" }, { url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0", size = 789946, upload-time = "2025-09-19T00:35:15.403Z" }, { url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8", size = 780787, upload-time = "2025-09-19T00:35:17.061Z" }, { url = "https://files.pythonhosted.org/packages/4f/92/c54cdb4aa41009632e69817a5aa452673507f07e341076735a2f6c46a37c/regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea", size = 773632, upload-time = "2025-09-19T00:35:18.57Z" }, { url = "https://files.pythonhosted.org/packages/db/99/75c996dc6a2231a8652d7ad0bfbeaf8a8c77612d335580f520f3ec40e30b/regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8", size = 844104, upload-time = "2025-09-19T00:35:20.259Z" }, { url = "https://files.pythonhosted.org/packages/1c/f7/25aba34cc130cb6844047dbfe9716c9b8f9629fee8b8bec331aa9241b97b/regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25", size = 834794, upload-time = "2025-09-19T00:35:22.002Z" }, { url = "https://files.pythonhosted.org/packages/51/eb/64e671beafa0ae29712268421597596d781704973551312b2425831d4037/regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29", size = 778535, upload-time = "2025-09-19T00:35:23.298Z" }, { url = "https://files.pythonhosted.org/packages/26/33/c0ebc0b07bd0bf88f716cca240546b26235a07710ea58e271cfe390ae273/regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444", size = 264115, upload-time = "2025-09-19T00:35:25.206Z" }, { url = "https://files.pythonhosted.org/packages/59/39/aeb11a4ae68faaec2498512cadae09f2d8a91f1f65730fe62b9bffeea150/regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450", size = 276143, upload-time = "2025-09-19T00:35:26.785Z" }, { url = "https://files.pythonhosted.org/packages/29/04/37f2d3fc334a1031fc2767c9d89cec13c2e72207c7e7f6feae8a47f4e149/regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442", size = 268473, upload-time = "2025-09-19T00:35:28.39Z" }, { url = "https://files.pythonhosted.org/packages/58/61/80eda662fc4eb32bfedc331f42390974c9e89c7eac1b79cd9eea4d7c458c/regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a", size = 484832, upload-time = "2025-09-19T00:35:30.011Z" }, { url = "https://files.pythonhosted.org/packages/a6/d9/33833d9abddf3f07ad48504ddb53fe3b22f353214bbb878a72eee1e3ddbf/regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8", size = 288994, upload-time = "2025-09-19T00:35:31.733Z" }, { url = "https://files.pythonhosted.org/packages/2a/b3/526ee96b0d70ea81980cbc20c3496fa582f775a52e001e2743cc33b2fa75/regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414", size = 286619, upload-time = "2025-09-19T00:35:33.221Z" }, { url = "https://files.pythonhosted.org/packages/65/4f/c2c096b02a351b33442aed5895cdd8bf87d372498d2100927c5a053d7ba3/regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a", size = 792454, upload-time = "2025-09-19T00:35:35.361Z" }, { url = "https://files.pythonhosted.org/packages/24/15/b562c9d6e47c403c4b5deb744f8b4bf6e40684cf866c7b077960a925bdff/regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4", size = 858723, upload-time = "2025-09-19T00:35:36.949Z" }, { url = "https://files.pythonhosted.org/packages/f2/01/dba305409849e85b8a1a681eac4c03ed327d8de37895ddf9dc137f59c140/regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a", size = 905899, upload-time = "2025-09-19T00:35:38.723Z" }, { url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f", size = 798981, upload-time = "2025-09-19T00:35:40.416Z" }, { url = "https://files.pythonhosted.org/packages/c4/5e/72db90970887bbe02296612bd61b0fa31e6d88aa24f6a4853db3e96c575e/regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a", size = 781900, upload-time = "2025-09-19T00:35:42.077Z" }, { url = "https://files.pythonhosted.org/packages/50/ff/596be45eea8e9bc31677fde243fa2904d00aad1b32c31bce26c3dbba0b9e/regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9", size = 852952, upload-time = "2025-09-19T00:35:43.751Z" }, { url = "https://files.pythonhosted.org/packages/e5/1b/2dfa348fa551e900ed3f5f63f74185b6a08e8a76bc62bc9c106f4f92668b/regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2", size = 844355, upload-time = "2025-09-19T00:35:45.309Z" }, { url = "https://files.pythonhosted.org/packages/f4/bf/aefb1def27fe33b8cbbb19c75c13aefccfbef1c6686f8e7f7095705969c7/regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95", size = 787254, upload-time = "2025-09-19T00:35:46.904Z" }, { url = "https://files.pythonhosted.org/packages/e3/4e/8ef042e7cf0dbbb401e784e896acfc1b367b95dfbfc9ada94c2ed55a081f/regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07", size = 264129, upload-time = "2025-09-19T00:35:48.597Z" }, { url = "https://files.pythonhosted.org/packages/b4/7d/c4fcabf80dcdd6821c0578ad9b451f8640b9110fb3dcb74793dd077069ff/regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9", size = 276160, upload-time = "2025-09-19T00:36:00.45Z" }, { url = "https://files.pythonhosted.org/packages/64/f8/0e13c8ae4d6df9d128afaba138342d532283d53a4c1e7a8c93d6756c8f4a/regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df", size = 268471, upload-time = "2025-09-19T00:36:02.149Z" }, { url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e", size = 486335, upload-time = "2025-09-19T00:36:03.661Z" }, { url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a", size = 289720, upload-time = "2025-09-19T00:36:05.471Z" }, { url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab", size = 287257, upload-time = "2025-09-19T00:36:07.072Z" }, { url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5", size = 797463, upload-time = "2025-09-19T00:36:08.399Z" }, { url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742", size = 862670, upload-time = "2025-09-19T00:36:10.101Z" }, { url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425", size = 910881, upload-time = "2025-09-19T00:36:12.223Z" }, { url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352", size = 802011, upload-time = "2025-09-19T00:36:13.901Z" }, { url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d", size = 786668, upload-time = "2025-09-19T00:36:15.391Z" }, { url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56", size = 856578, upload-time = "2025-09-19T00:36:16.845Z" }, { url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e", size = 849017, upload-time = "2025-09-19T00:36:18.597Z" }, { url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282", size = 788150, upload-time = "2025-09-19T00:36:20.464Z" }, { url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459", size = 264536, upload-time = "2025-09-19T00:36:21.922Z" }, { url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77", size = 275501, upload-time = "2025-09-19T00:36:23.4Z" }, { url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5", size = 268601, upload-time = "2025-09-19T00:36:25.092Z" }, { url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2", size = 485955, upload-time = "2025-09-19T00:36:26.822Z" }, { url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb", size = 289583, upload-time = "2025-09-19T00:36:28.577Z" }, { url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af", size = 287000, upload-time = "2025-09-19T00:36:30.161Z" }, { url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29", size = 797535, upload-time = "2025-09-19T00:36:31.876Z" }, { url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f", size = 862603, upload-time = "2025-09-19T00:36:33.344Z" }, { url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68", size = 910829, upload-time = "2025-09-19T00:36:34.826Z" }, { url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783", size = 802059, upload-time = "2025-09-19T00:36:36.664Z" }, { url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac", size = 786781, upload-time = "2025-09-19T00:36:38.168Z" }, { url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e", size = 856578, upload-time = "2025-09-19T00:36:40.129Z" }, { url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23", size = 849119, upload-time = "2025-09-19T00:36:41.651Z" }, { url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f", size = 788219, upload-time = "2025-09-19T00:36:43.575Z" }, { url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d", size = 264517, upload-time = "2025-09-19T00:36:45.503Z" }, { url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d", size = 275481, upload-time = "2025-09-19T00:36:46.965Z" }, { url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb", size = 268598, upload-time = "2025-09-19T00:36:48.314Z" }, { url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2", size = 489765, upload-time = "2025-09-19T00:36:49.996Z" }, { url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3", size = 291228, upload-time = "2025-09-19T00:36:51.654Z" }, { url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12", size = 289270, upload-time = "2025-09-19T00:36:53.118Z" }, { url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0", size = 806326, upload-time = "2025-09-19T00:36:54.631Z" }, { url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6", size = 871556, upload-time = "2025-09-19T00:36:56.208Z" }, { url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef", size = 913817, upload-time = "2025-09-19T00:36:57.807Z" }, { url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a", size = 811055, upload-time = "2025-09-19T00:36:59.762Z" }, { url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d", size = 794534, upload-time = "2025-09-19T00:37:01.405Z" }, { url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368", size = 866684, upload-time = "2025-09-19T00:37:03.441Z" }, { url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90", size = 853282, upload-time = "2025-09-19T00:37:04.985Z" }, { url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7", size = 797830, upload-time = "2025-09-19T00:37:06.697Z" }, { url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e", size = 267281, upload-time = "2025-09-19T00:37:08.568Z" }, { url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730", size = 278724, upload-time = "2025-09-19T00:37:10.023Z" }, { url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a", size = 269771, upload-time = "2025-09-19T00:37:13.041Z" }, { url = "https://files.pythonhosted.org/packages/44/b7/3b4663aa3b4af16819f2ab6a78c4111c7e9b066725d8107753c2257448a5/regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129", size = 486130, upload-time = "2025-09-19T00:37:14.527Z" }, { url = "https://files.pythonhosted.org/packages/80/5b/4533f5d7ac9c6a02a4725fe8883de2aebc713e67e842c04cf02626afb747/regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea", size = 289539, upload-time = "2025-09-19T00:37:16.356Z" }, { url = "https://files.pythonhosted.org/packages/b8/8d/5ab6797c2750985f79e9995fad3254caa4520846580f266ae3b56d1cae58/regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1", size = 287233, upload-time = "2025-09-19T00:37:18.025Z" }, { url = "https://files.pythonhosted.org/packages/cb/1e/95afcb02ba8d3a64e6ffeb801718ce73471ad6440c55d993f65a4a5e7a92/regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47", size = 797876, upload-time = "2025-09-19T00:37:19.609Z" }, { url = "https://files.pythonhosted.org/packages/c8/fb/720b1f49cec1f3b5a9fea5b34cd22b88b5ebccc8c1b5de9cc6f65eed165a/regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379", size = 863385, upload-time = "2025-09-19T00:37:21.65Z" }, { url = "https://files.pythonhosted.org/packages/a9/ca/e0d07ecf701e1616f015a720dc13b84c582024cbfbb3fc5394ae204adbd7/regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203", size = 910220, upload-time = "2025-09-19T00:37:23.723Z" }, { url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164", size = 801827, upload-time = "2025-09-19T00:37:25.684Z" }, { url = "https://files.pythonhosted.org/packages/b8/a6/740fbd9fcac31a1305a8eed30b44bf0f7f1e042342be0a4722c0365ecfca/regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb", size = 786843, upload-time = "2025-09-19T00:37:27.62Z" }, { url = "https://files.pythonhosted.org/packages/80/a7/0579e8560682645906da640c9055506465d809cb0f5415d9976f417209a6/regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743", size = 857430, upload-time = "2025-09-19T00:37:29.362Z" }, { url = "https://files.pythonhosted.org/packages/8d/9b/4dc96b6c17b38900cc9fee254fc9271d0dde044e82c78c0811b58754fde5/regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282", size = 848612, upload-time = "2025-09-19T00:37:31.42Z" }, { url = "https://files.pythonhosted.org/packages/b3/6a/6f659f99bebb1775e5ac81a3fb837b85897c1a4ef5acffd0ff8ffe7e67fb/regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773", size = 787967, upload-time = "2025-09-19T00:37:34.019Z" }, { url = "https://files.pythonhosted.org/packages/61/35/9e35665f097c07cf384a6b90a1ac11b0b1693084a0b7a675b06f760496c6/regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788", size = 269847, upload-time = "2025-09-19T00:37:35.759Z" }, { url = "https://files.pythonhosted.org/packages/af/64/27594dbe0f1590b82de2821ebfe9a359b44dcb9b65524876cd12fabc447b/regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3", size = 278755, upload-time = "2025-09-19T00:37:37.367Z" }, { url = "https://files.pythonhosted.org/packages/30/a3/0cd8d0d342886bd7d7f252d701b20ae1a3c72dc7f34ef4b2d17790280a09/regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d", size = 271873, upload-time = "2025-09-19T00:37:39.125Z" }, { url = "https://files.pythonhosted.org/packages/99/cb/8a1ab05ecf404e18b54348e293d9b7a60ec2bd7aa59e637020c5eea852e8/regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306", size = 489773, upload-time = "2025-09-19T00:37:40.968Z" }, { url = "https://files.pythonhosted.org/packages/93/3b/6543c9b7f7e734d2404fa2863d0d710c907bef99d4598760ed4563d634c3/regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946", size = 291221, upload-time = "2025-09-19T00:37:42.901Z" }, { url = "https://files.pythonhosted.org/packages/cd/91/e9fdee6ad6bf708d98c5d17fded423dcb0661795a49cba1b4ffb8358377a/regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f", size = 289268, upload-time = "2025-09-19T00:37:44.823Z" }, { url = "https://files.pythonhosted.org/packages/94/a6/bc3e8a918abe4741dadeaeb6c508e3a4ea847ff36030d820d89858f96a6c/regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95", size = 806659, upload-time = "2025-09-19T00:37:46.684Z" }, { url = "https://files.pythonhosted.org/packages/2b/71/ea62dbeb55d9e6905c7b5a49f75615ea1373afcad95830047e4e310db979/regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b", size = 871701, upload-time = "2025-09-19T00:37:48.882Z" }, { url = "https://files.pythonhosted.org/packages/6a/90/fbe9dedb7dad24a3a4399c0bae64bfa932ec8922a0a9acf7bc88db30b161/regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3", size = 913742, upload-time = "2025-09-19T00:37:51.015Z" }, { url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571", size = 811117, upload-time = "2025-09-19T00:37:52.686Z" }, { url = "https://files.pythonhosted.org/packages/2a/da/435f29fddfd015111523671e36d30af3342e8136a889159b05c1d9110480/regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad", size = 794647, upload-time = "2025-09-19T00:37:54.626Z" }, { url = "https://files.pythonhosted.org/packages/23/66/df5e6dcca25c8bc57ce404eebc7342310a0d218db739d7882c9a2b5974a3/regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494", size = 866747, upload-time = "2025-09-19T00:37:56.367Z" }, { url = "https://files.pythonhosted.org/packages/82/42/94392b39b531f2e469b2daa40acf454863733b674481fda17462a5ffadac/regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b", size = 853434, upload-time = "2025-09-19T00:37:58.39Z" }, { url = "https://files.pythonhosted.org/packages/a8/f8/dcc64c7f7bbe58842a8f89622b50c58c3598fbbf4aad0a488d6df2c699f1/regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41", size = 798024, upload-time = "2025-09-19T00:38:00.397Z" }, { url = "https://files.pythonhosted.org/packages/20/8d/edf1c5d5aa98f99a692313db813ec487732946784f8f93145e0153d910e5/regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096", size = 273029, upload-time = "2025-09-19T00:38:02.383Z" }, { url = "https://files.pythonhosted.org/packages/a7/24/02d4e4f88466f17b145f7ea2b2c11af3a942db6222429c2c146accf16054/regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a", size = 282680, upload-time = "2025-09-19T00:38:04.102Z" }, { url = "https://files.pythonhosted.org/packages/1f/a3/c64894858aaaa454caa7cc47e2f225b04d3ed08ad649eacf58d45817fad2/regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01", size = 273034, upload-time = "2025-09-19T00:38:05.807Z" }, { url = "https://files.pythonhosted.org/packages/ed/d2/5b0ded10467d6e96f78de5e6f195b7f9b57251f411b1090004597cffe5d9/regex-2025.9.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3dbcfcaa18e9480669030d07371713c10b4f1a41f791ffa5cb1a99f24e777f40", size = 484847, upload-time = "2025-09-19T00:38:07.367Z" }, { url = "https://files.pythonhosted.org/packages/55/35/051da2c0ae6124e3f1aa1442ecc2bb4e2de930e95433bce1301a2e7ae255/regex-2025.9.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1e85f73ef7095f0380208269055ae20524bfde3f27c5384126ddccf20382a638", size = 288995, upload-time = "2025-09-19T00:38:09.253Z" }, { url = "https://files.pythonhosted.org/packages/22/4b/4bfc51cad95263d25b6ed8c5253831b2536e8e279e6736d0a08c9f7ffe98/regex-2025.9.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9098e29b3ea4ffffeade423f6779665e2a4f8db64e699c0ed737ef0db6ba7b12", size = 286642, upload-time = "2025-09-19T00:38:11.012Z" }, { url = "https://files.pythonhosted.org/packages/0e/67/d2f3e2483e09d1e9f7d93b4fe106b04933fba5e619bc901530d1c90d62da/regex-2025.9.18-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90b6b7a2d0f45b7ecaaee1aec6b362184d6596ba2092dd583ffba1b78dd0231c", size = 779896, upload-time = "2025-09-19T00:38:12.732Z" }, { url = "https://files.pythonhosted.org/packages/14/5e/49a4f07ce6f5563de02b0e321220b9534f3fd3bae275311b785dd618aea5/regex-2025.9.18-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c81b892af4a38286101502eae7aec69f7cd749a893d9987a92776954f3943408", size = 848954, upload-time = "2025-09-19T00:38:14.716Z" }, { url = "https://files.pythonhosted.org/packages/00/8d/f5995ae51225c77ca9215d78ceb1dc30c52fa2b22c41dac977214e8b4bbd/regex-2025.9.18-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3b524d010973f2e1929aeb635418d468d869a5f77b52084d9f74c272189c251d", size = 896770, upload-time = "2025-09-19T00:38:16.381Z" }, { url = "https://files.pythonhosted.org/packages/6b/15/2a3a744d73a557337c7561db2114bab10b4e9941c626c03169ea62f42c8f/regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b498437c026a3d5d0be0020023ff76d70ae4d77118e92f6f26c9d0423452446", size = 789484, upload-time = "2025-09-19T00:38:18.183Z" }, { url = "https://files.pythonhosted.org/packages/d8/27/e425f3d17d32062a657b836d0c8a68f5e71a9e6295fa637159f265eaa609/regex-2025.9.18-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0716e4d6e58853d83f6563f3cf25c281ff46cf7107e5f11879e32cb0b59797d9", size = 780150, upload-time = "2025-09-19T00:38:19.879Z" }, { url = "https://files.pythonhosted.org/packages/62/28/79dfae89b6fd7901b82611ac1a96ec25deceb7e918e9c5eb3f96cf5ad654/regex-2025.9.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:065b6956749379d41db2625f880b637d4acc14c0a4de0d25d609a62850e96d36", size = 773160, upload-time = "2025-09-19T00:38:21.641Z" }, { url = "https://files.pythonhosted.org/packages/0b/67/df83d6ae608f487448e9be7ac26211af2afa2b6e34465fde3e07d1f11290/regex-2025.9.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d4a691494439287c08ddb9b5793da605ee80299dd31e95fa3f323fac3c33d9d4", size = 843555, upload-time = "2025-09-19T00:38:23.696Z" }, { url = "https://files.pythonhosted.org/packages/32/67/c65f56f3edd3f213d3aa41e9b9b07cc2247721a23d34bcfb2947dc0f4685/regex-2025.9.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef8d10cc0989565bcbe45fb4439f044594d5c2b8919d3d229ea2c4238f1d55b0", size = 834169, upload-time = "2025-09-19T00:38:25.997Z" }, { url = "https://files.pythonhosted.org/packages/95/90/7fca37435e3aa1a032c38fa1e171fdaf809c8dbf2717508e3f6a92c75446/regex-2025.9.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4baeb1b16735ac969a7eeecc216f1f8b7caf60431f38a2671ae601f716a32d25", size = 778024, upload-time = "2025-09-19T00:38:28.043Z" }, { url = "https://files.pythonhosted.org/packages/8b/05/c2ee512cdf34d6be5ac5cf938a58c1b79a9d96cbad404bc4d70404212edb/regex-2025.9.18-cp39-cp39-win32.whl", hash = "sha256:8e5f41ad24a1e0b5dfcf4c4e5d9f5bd54c895feb5708dd0c1d0d35693b24d478", size = 264151, upload-time = "2025-09-19T00:38:30.23Z" }, { url = "https://files.pythonhosted.org/packages/f8/2f/8414fb46181b6108484f04d670ece196db6734cc4c683f41125043fd3280/regex-2025.9.18-cp39-cp39-win_amd64.whl", hash = "sha256:50e8290707f2fb8e314ab3831e594da71e062f1d623b05266f8cfe4db4949afd", size = 276232, upload-time = "2025-09-19T00:38:31.981Z" }, { url = "https://files.pythonhosted.org/packages/61/63/f40931d477e1ed4b53105d506758a58cfec1b052c12972054930ec743ee5/regex-2025.9.18-cp39-cp39-win_arm64.whl", hash = "sha256:039a9d7195fd88c943d7c777d4941e8ef736731947becce773c31a1009cb3c35", size = 268505, upload-time = "2025-09-19T00:38:34.015Z" }, ] [[package]] name = "requests" version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, { name = "idna" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] name = "responses" version = "0.25.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "requests" }, { name = "urllib3", version = "1.26.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "urllib3", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/95/89c054ad70bfef6da605338b009b2e283485835351a9935c7bfbfaca7ffc/responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4", size = 79320, upload-time = "2025-08-08T19:01:46.709Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1c/4c/cc276ce57e572c102d9542d383b2cfd551276581dc60004cb94fe8774c11/responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c", size = 34769, upload-time = "2025-08-08T19:01:45.018Z" }, ] [[package]] name = "rfc3339-validator" version = "0.1.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, ] [[package]] name = "rpds-py" version = "0.27.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" }, { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" }, { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" }, { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" }, { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" }, { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" }, { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" }, { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" }, { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" }, { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" }, { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" }, { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" }, { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" }, { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" }, { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, { url = "https://files.pythonhosted.org/packages/7f/6c/252e83e1ce7583c81f26d1d884b2074d40a13977e1b6c9c50bbf9a7f1f5a/rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", size = 372140, upload-time = "2025-08-27T12:15:05.441Z" }, { url = "https://files.pythonhosted.org/packages/9d/71/949c195d927c5aeb0d0629d329a20de43a64c423a6aa53836290609ef7ec/rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", size = 354086, upload-time = "2025-08-27T12:15:07.404Z" }, { url = "https://files.pythonhosted.org/packages/9f/02/e43e332ad8ce4f6c4342d151a471a7f2900ed1d76901da62eb3762663a71/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", size = 382117, upload-time = "2025-08-27T12:15:09.275Z" }, { url = "https://files.pythonhosted.org/packages/d0/05/b0fdeb5b577197ad72812bbdfb72f9a08fa1e64539cc3940b1b781cd3596/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", size = 394520, upload-time = "2025-08-27T12:15:10.727Z" }, { url = "https://files.pythonhosted.org/packages/67/1f/4cfef98b2349a7585181e99294fa2a13f0af06902048a5d70f431a66d0b9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", size = 522657, upload-time = "2025-08-27T12:15:12.613Z" }, { url = "https://files.pythonhosted.org/packages/44/55/ccf37ddc4c6dce7437b335088b5ca18da864b334890e2fe9aa6ddc3f79a9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", size = 402967, upload-time = "2025-08-27T12:15:14.113Z" }, { url = "https://files.pythonhosted.org/packages/74/e5/5903f92e41e293b07707d5bf00ef39a0eb2af7190aff4beaf581a6591510/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", size = 384372, upload-time = "2025-08-27T12:15:15.842Z" }, { url = "https://files.pythonhosted.org/packages/8f/e3/fbb409e18aeefc01e49f5922ac63d2d914328430e295c12183ce56ebf76b/rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", size = 401264, upload-time = "2025-08-27T12:15:17.388Z" }, { url = "https://files.pythonhosted.org/packages/55/79/529ad07794e05cb0f38e2f965fc5bb20853d523976719400acecc447ec9d/rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", size = 418691, upload-time = "2025-08-27T12:15:19.144Z" }, { url = "https://files.pythonhosted.org/packages/33/39/6554a7fd6d9906fda2521c6d52f5d723dca123529fb719a5b5e074c15e01/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", size = 558989, upload-time = "2025-08-27T12:15:21.087Z" }, { url = "https://files.pythonhosted.org/packages/19/b2/76fa15173b6f9f445e5ef15120871b945fb8dd9044b6b8c7abe87e938416/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", size = 589835, upload-time = "2025-08-27T12:15:22.696Z" }, { url = "https://files.pythonhosted.org/packages/ee/9e/5560a4b39bab780405bed8a88ee85b30178061d189558a86003548dea045/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", size = 555227, upload-time = "2025-08-27T12:15:24.278Z" }, { url = "https://files.pythonhosted.org/packages/52/d7/cd9c36215111aa65724c132bf709c6f35175973e90b32115dedc4ced09cb/rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", size = 217899, upload-time = "2025-08-27T12:15:25.926Z" }, { url = "https://files.pythonhosted.org/packages/5b/e0/d75ab7b4dd8ba777f6b365adbdfc7614bbfe7c5f05703031dfa4b61c3d6c/rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", size = 228725, upload-time = "2025-08-27T12:15:27.398Z" }, { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" }, { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" }, { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" }, { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" }, { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" }, { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" }, { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" }, { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" }, { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" }, { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" }, { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" }, { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" }, { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" }, { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, { url = "https://files.pythonhosted.org/packages/4e/ea/5463cd5048a7a2fcdae308b6e96432802132c141bfb9420260142632a0f1/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", size = 371778, upload-time = "2025-08-27T12:16:13.851Z" }, { url = "https://files.pythonhosted.org/packages/0d/c8/f38c099db07f5114029c1467649d308543906933eebbc226d4527a5f4693/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", size = 354394, upload-time = "2025-08-27T12:16:15.609Z" }, { url = "https://files.pythonhosted.org/packages/7d/79/b76f97704d9dd8ddbd76fed4c4048153a847c5d6003afe20a6b5c3339065/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", size = 382348, upload-time = "2025-08-27T12:16:17.251Z" }, { url = "https://files.pythonhosted.org/packages/8a/3f/ef23d3c1be1b837b648a3016d5bbe7cfe711422ad110b4081c0a90ef5a53/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", size = 394159, upload-time = "2025-08-27T12:16:19.251Z" }, { url = "https://files.pythonhosted.org/packages/74/8a/9e62693af1a34fd28b1a190d463d12407bd7cf561748cb4745845d9548d3/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", size = 522775, upload-time = "2025-08-27T12:16:20.929Z" }, { url = "https://files.pythonhosted.org/packages/36/0d/8d5bb122bf7a60976b54c5c99a739a3819f49f02d69df3ea2ca2aff47d5c/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", size = 402633, upload-time = "2025-08-27T12:16:22.548Z" }, { url = "https://files.pythonhosted.org/packages/0f/0e/237948c1f425e23e0cf5a566d702652a6e55c6f8fbd332a1792eb7043daf/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", size = 384867, upload-time = "2025-08-27T12:16:24.29Z" }, { url = "https://files.pythonhosted.org/packages/d6/0a/da0813efcd998d260cbe876d97f55b0f469ada8ba9cbc47490a132554540/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", size = 401791, upload-time = "2025-08-27T12:16:25.954Z" }, { url = "https://files.pythonhosted.org/packages/51/78/c6c9e8a8aaca416a6f0d1b6b4a6ee35b88fe2c5401d02235d0a056eceed2/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", size = 419525, upload-time = "2025-08-27T12:16:27.659Z" }, { url = "https://files.pythonhosted.org/packages/a3/69/5af37e1d71487cf6d56dd1420dc7e0c2732c1b6ff612aa7a88374061c0a8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", size = 559255, upload-time = "2025-08-27T12:16:29.343Z" }, { url = "https://files.pythonhosted.org/packages/40/7f/8b7b136069ef7ac3960eda25d832639bdb163018a34c960ed042dd1707c8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", size = 590384, upload-time = "2025-08-27T12:16:31.005Z" }, { url = "https://files.pythonhosted.org/packages/d8/06/c316d3f6ff03f43ccb0eba7de61376f8ec4ea850067dddfafe98274ae13c/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", size = 555959, upload-time = "2025-08-27T12:16:32.73Z" }, { url = "https://files.pythonhosted.org/packages/60/94/384cf54c430b9dac742bbd2ec26c23feb78ded0d43d6d78563a281aec017/rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", size = 228784, upload-time = "2025-08-27T12:16:34.428Z" }, ] [[package]] name = "rsa" version = "4.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/b5/475c45a58650b0580421746504b680cd2db4e81bc941e94ca53785250269/rsa-4.7.2.tar.gz", hash = "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9", size = 39711, upload-time = "2021-02-24T10:55:05.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/93/0c0f002031f18b53af7a6166103c02b9c0667be528944137cc954ec921b3/rsa-4.7.2-py3-none-any.whl", hash = "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", size = 34505, upload-time = "2021-02-24T10:55:03.55Z" }, ] [[package]] name = "s3transfer" version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" }, ] [[package]] name = "setuptools" version = "80.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] [[package]] name = "sniffio" version = "1.3.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] [[package]] name = "sympy" version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] [[package]] name = "time-machine" version = "2.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/1b5fdd165f61b67f445fac2a7feb0c655118edef429cd09ff5a8067f7f1d/time_machine-2.19.0.tar.gz", hash = "sha256:7c5065a8b3f2bbb449422c66ef71d114d3f909c276a6469642ecfffb6a0fcd29", size = 14576, upload-time = "2025-08-19T17:22:08.402Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9d/8f/19125611ebbcb3a14da14cd982b9eb4573e2733db60c9f1fbf6a39534f40/time_machine-2.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5169018ef47206997b46086ce01881cd3a4666fd2998c9d76a87858ca3e49e9", size = 19659, upload-time = "2025-08-19T17:20:30.062Z" }, { url = "https://files.pythonhosted.org/packages/74/da/9b0a928321e7822a3ff96dbd1eae089883848e30e9e1b149b85fb96ba56b/time_machine-2.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85bb7ed440fccf6f6d0c8f7d68d849e7c3d1f771d5e0b2cdf871fa6561da569f", size = 15157, upload-time = "2025-08-19T17:20:31.931Z" }, { url = "https://files.pythonhosted.org/packages/36/ff/d7e943422038f5f2161fe2c2d791e64a45be691ef946020b20f3a6efc4d4/time_machine-2.19.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a3b12028af1cdc09ccd595be2168b7b26f206c1e190090b048598fbe278beb8e", size = 32860, upload-time = "2025-08-19T17:20:33.241Z" }, { url = "https://files.pythonhosted.org/packages/fc/80/2b0f1070ed9808ee7da7a6da62a4a0b776957cb4d861578348f86446e778/time_machine-2.19.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c261f073086cf081d1443cbf7684148c662659d3d139d06b772bfe3fe7cc71a6", size = 34510, upload-time = "2025-08-19T17:20:34.221Z" }, { url = "https://files.pythonhosted.org/packages/ef/b4/48038691c8d89924b36c83335a73adeeb68c884f5a1da08a5b17b8a956f3/time_machine-2.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:011954d951230a9f1079f22b39ed1a3a9abb50ee297dfb8c557c46351659d94d", size = 36204, upload-time = "2025-08-19T17:20:35.163Z" }, { url = "https://files.pythonhosted.org/packages/37/2e/60e8adb541df195e83cb74b720b2cfb1f22ed99c5a7f8abf2a9ab3442cb5/time_machine-2.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b0f83308b29c7872006803f2e77318874eb84d0654f2afe0e48e3822e7a2e39b", size = 34936, upload-time = "2025-08-19T17:20:36.61Z" }, { url = "https://files.pythonhosted.org/packages/5e/72/e8cee59c6cd99dd3b25b8001a0253e779a286aa8f44d5b40777cbd66210b/time_machine-2.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:39733ef844e2984620ec9382a42d00cccc4757d75a5dd572be8c2572e86e50b9", size = 32932, upload-time = "2025-08-19T17:20:37.901Z" }, { url = "https://files.pythonhosted.org/packages/2c/eb/83f300d93c1504965d944e03679f1c943a923bce2d0fdfadef0e2e22cc13/time_machine-2.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8db99f6334432e9ffbf00c215caf2ae9773f17cec08304d77e9e90febc3507b", size = 34010, upload-time = "2025-08-19T17:20:39.202Z" }, { url = "https://files.pythonhosted.org/packages/e1/77/f35f2500e04daac5033a22fbfd17e68467822b8406ee77966bf222ccaa26/time_machine-2.19.0-cp310-cp310-win32.whl", hash = "sha256:72bf66cd19e27ffd26516b9cbe676d50c2e0b026153289765dfe0cf406708128", size = 17121, upload-time = "2025-08-19T17:20:40.108Z" }, { url = "https://files.pythonhosted.org/packages/db/df/32d3e0404be1760a64a44caab2af34b07e952bfe00a23134fea9ddba3e8a/time_machine-2.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:46f1c945934ce3d6b4f388b8e581fce7f87ec891ea90d7128e19520e434f96f0", size = 17957, upload-time = "2025-08-19T17:20:41.079Z" }, { url = "https://files.pythonhosted.org/packages/66/df/598a71a1afb4b509a4587273b76590b16d9110a3e9106f01eedc68d02bb2/time_machine-2.19.0-cp310-cp310-win_arm64.whl", hash = "sha256:fb4897c7a5120a4fd03f0670f332d83b7e55645886cd8864a71944c4c2e5b35b", size = 16821, upload-time = "2025-08-19T17:20:41.967Z" }, { url = "https://files.pythonhosted.org/packages/1d/ed/4815ebcc9b6c14273f692b9be38a9b09eae52a7e532407cc61a51912b121/time_machine-2.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ee91664880434d98e41585c3446dac7180ec408c786347451ddfca110d19296", size = 19342, upload-time = "2025-08-19T17:20:43.207Z" }, { url = "https://files.pythonhosted.org/packages/ee/08/154cce8b11b60d8238b0b751b8901d369999f4e8f7c3a5f917caa5d95b0b/time_machine-2.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed3732b83a893d1c7b8cabde762968b4dc5680ee0d305b3ecca9bb516f4e3862", size = 14978, upload-time = "2025-08-19T17:20:44.134Z" }, { url = "https://files.pythonhosted.org/packages/c7/b7/b689d8c8eeca7af375cfcd64973e49e83aa817cc00f80f98548d42c0eb50/time_machine-2.19.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6ba0303e9cc9f7f947e344f501e26bedfb68fab521e3c2729d370f4f332d2d55", size = 30964, upload-time = "2025-08-19T17:20:45.366Z" }, { url = "https://files.pythonhosted.org/packages/80/91/38bf9c79674e95ce32e23c267055f281dff651eec77ed32a677db3dc011a/time_machine-2.19.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2851825b524a988ee459c37c1c26bdfaa7eff78194efb2b562ea497a6f375b0a", size = 32606, upload-time = "2025-08-19T17:20:46.693Z" }, { url = "https://files.pythonhosted.org/packages/19/4a/e9222d85d4de68975a5e799f539a9d32f3a134a9101fca0a61fa6aa33d68/time_machine-2.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68d32b09ecfd7fef59255c091e8e7c24dd117f882c4880b5c7ab8c5c32a98f89", size = 34405, upload-time = "2025-08-19T17:20:48.032Z" }, { url = "https://files.pythonhosted.org/packages/14/e2/09480d608d42d6876f9ff74593cfc9197a7eb2c31381a74fb2b145575b65/time_machine-2.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60c46ab527bf2fa144b530f639cc9e12803524c9e1f111dc8c8f493bb6586eeb", size = 33181, upload-time = "2025-08-19T17:20:48.937Z" }, { url = "https://files.pythonhosted.org/packages/84/64/f9359e000fad32d9066305c48abc527241d608bcdf77c19d67d66e268455/time_machine-2.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:56f26ab9f0201c453d18fe76bb7d1cf05fe58c1b9d9cb0c7d243d05132e01292", size = 31036, upload-time = "2025-08-19T17:20:50.276Z" }, { url = "https://files.pythonhosted.org/packages/71/0d/fab2aacec71e3e482bd7fce0589381f9414a4a97f8766bddad04ad047b7b/time_machine-2.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6c806cf3c1185baa1d807b7f51bed0db7a6506832c961d5d1b4c94c775749bc0", size = 32145, upload-time = "2025-08-19T17:20:51.449Z" }, { url = "https://files.pythonhosted.org/packages/44/fb/faeba2405fb27553f7b28db441a500e2064ffdb2dcba001ee315fdd2c121/time_machine-2.19.0-cp311-cp311-win32.whl", hash = "sha256:b30039dfd89855c12138095bee39c540b4633cbc3684580d684ef67a99a91587", size = 17004, upload-time = "2025-08-19T17:20:52.38Z" }, { url = "https://files.pythonhosted.org/packages/2f/84/87e483d660ca669426192969280366635c845c3154a9fe750be546ed3afc/time_machine-2.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:13ed8b34430f1de79905877f5600adffa626793ab4546a70a99fb72c6a3350d8", size = 17822, upload-time = "2025-08-19T17:20:53.348Z" }, { url = "https://files.pythonhosted.org/packages/41/f4/ebf7bbf5047854a528adaf54a5e8780bc5f7f0104c298ab44566a3053bf8/time_machine-2.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:cc29a50a0257d8750b08056b66d7225daab47606832dea1a69e8b017323bf511", size = 16680, upload-time = "2025-08-19T17:20:54.26Z" }, { url = "https://files.pythonhosted.org/packages/9b/aa/7e00614d339e4d687f6e96e312a1566022528427d237ec639df66c4547bc/time_machine-2.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c85cf437dc3c07429456d8d6670ac90ecbd8241dcd0fbf03e8db2800576f91ff", size = 19308, upload-time = "2025-08-19T17:20:55.25Z" }, { url = "https://files.pythonhosted.org/packages/ab/3c/bde3c757394f5bca2fbc1528d4117960a26c38f9b160bf471b38d2378d8f/time_machine-2.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d9238897e8ef54acdf59f5dff16f59ca0720e7c02d820c56b4397c11db5d3eb9", size = 15019, upload-time = "2025-08-19T17:20:56.204Z" }, { url = "https://files.pythonhosted.org/packages/c8/e0/8ca916dd918018352d377f1f5226ee071cfbeb7dbbde2b03d14a411ac2b1/time_machine-2.19.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e312c7d5d6bfffb96c6a7b39ff29e3046de100d7efaa3c01552654cfbd08f14c", size = 33079, upload-time = "2025-08-19T17:20:57.166Z" }, { url = "https://files.pythonhosted.org/packages/48/69/184a0209f02dd0cb5e01e8d13cd4c97a5f389c4e3d09b95160dd676ad1e7/time_machine-2.19.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:714c40b2c90d1c57cc403382d5a9cf16e504cb525bfe9650095317da3c3d62b5", size = 34925, upload-time = "2025-08-19T17:20:58.117Z" }, { url = "https://files.pythonhosted.org/packages/43/42/4bbf4309e8e57cea1086eb99052d97ff6ddecc1ab6a3b07aa4512f8bf963/time_machine-2.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2eaa1c675d500dc3ccae19e9fb1feff84458a68c132bbea47a80cc3dd2df7072", size = 36384, upload-time = "2025-08-19T17:20:59.108Z" }, { url = "https://files.pythonhosted.org/packages/b1/af/9f510dc1719157348c1a2e87423aed406589070b54b503cb237d9bf3a4fe/time_machine-2.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e77a414e9597988af53b2b2e67242c9d2f409769df0d264b6d06fda8ca3360d4", size = 34881, upload-time = "2025-08-19T17:21:00.116Z" }, { url = "https://files.pythonhosted.org/packages/ca/28/61764a635c70cc76c76ba582dfdc1a84834cddaeb96789023af5214426b2/time_machine-2.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd93996970e11c382b04d4937c3cd0b0167adeef14725ece35aae88d8a01733c", size = 32931, upload-time = "2025-08-19T17:21:01.095Z" }, { url = "https://files.pythonhosted.org/packages/b6/e0/f028d93b266e6ade8aca5851f76ebbc605b2905cdc29981a2943b43e1a6c/time_machine-2.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e20a6d8d6e23174bd7e931e134d9610b136db460b249d07e84ecdad029ec352", size = 34241, upload-time = "2025-08-19T17:21:02.052Z" }, { url = "https://files.pythonhosted.org/packages/7d/a6/36d1950ed1d3f613158024cf1dcc73db1d9ef0b9117cf51ef2e37dc06499/time_machine-2.19.0-cp312-cp312-win32.whl", hash = "sha256:95afc9bc65228b27be80c2756799c20b8eb97c4ef382a9b762b6d7888bc84099", size = 17021, upload-time = "2025-08-19T17:21:03.374Z" }, { url = "https://files.pythonhosted.org/packages/b1/0d/e2dce93355abda3cac69e77fe96566757e98b8fe7fdcbddce89c9ced3f5f/time_machine-2.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84909af950e2448f4e2562ea5759c946248c99ab380d2b47d79b62bd76fa236", size = 17857, upload-time = "2025-08-19T17:21:04.331Z" }, { url = "https://files.pythonhosted.org/packages/eb/28/50ae6fb83b7feeeca7a461c0dc156cf7ef5e6ef594a600d06634fde6a2cb/time_machine-2.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:0390a1ea9fa7e9d772a39b7c61b34fdcca80eb9ffac339cc0441c6c714c81470", size = 16677, upload-time = "2025-08-19T17:21:05.39Z" }, { url = "https://files.pythonhosted.org/packages/a9/b8/24ebce67aa531bae2cbe164bb3f4abc6467dc31f3aead35e77f5a075ea3e/time_machine-2.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5e172866753e6041d3b29f3037dc47c20525176a494a71bbd0998dfdc4f11f2f", size = 19373, upload-time = "2025-08-19T17:21:06.701Z" }, { url = "https://files.pythonhosted.org/packages/53/a5/c9a5240fd2f845d3ff9fa26f8c8eaa29f7239af9d65007e61d212250f15b/time_machine-2.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f70f68379bd6f542ae6775cce9a4fa3dcc20bf7959c42eaef871c14469e18097", size = 15056, upload-time = "2025-08-19T17:21:07.667Z" }, { url = "https://files.pythonhosted.org/packages/b9/92/66cce5d2fb2a5e68459aca85fd18a7e2d216f725988940cd83f96630f2f1/time_machine-2.19.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e69e0b0f694728a00e72891ef8dd00c7542952cb1c87237db594b6b27d504a96", size = 33172, upload-time = "2025-08-19T17:21:08.619Z" }, { url = "https://files.pythonhosted.org/packages/ae/20/b499e9ab4364cd466016c33dcdf4f56629ca4c20b865bd4196d229f31d92/time_machine-2.19.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3ae0a8b869574301ec5637e32c270c7384cca5cd6e230f07af9d29271a7fa293", size = 35042, upload-time = "2025-08-19T17:21:09.622Z" }, { url = "https://files.pythonhosted.org/packages/41/32/b252d3d32791eb16c07d553c820dbc33d9c7fa771de3d1c602190bded2b7/time_machine-2.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:554e4317de90e2f7605ff80d153c8bb56b38c0d0c0279feb17e799521e987b8c", size = 36535, upload-time = "2025-08-19T17:21:10.571Z" }, { url = "https://files.pythonhosted.org/packages/98/cf/4d0470062b9742e1b040ab81bad04d1a5d1de09806507bb6188989cfa1a7/time_machine-2.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6567a5ec5538ed550539ac29be11b3cb36af1f9894e2a72940cba0292cc7c3c9", size = 34945, upload-time = "2025-08-19T17:21:11.538Z" }, { url = "https://files.pythonhosted.org/packages/24/71/2f741b29d98b1c18f6777a32236497c3d3264b6077e431cea4695684c8a1/time_machine-2.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82e9ffe8dfff07b0d810a2ad015a82cd78c6a237f6c7cf185fa7f747a3256f8a", size = 33014, upload-time = "2025-08-19T17:21:12.858Z" }, { url = "https://files.pythonhosted.org/packages/e8/83/ca8dba6106562843fd99f672e5aaf95badbc10f4f13f7cfe8d8640a7019d/time_machine-2.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e1c4e578cdd69b3531d8dd3fbcb92a0cd879dadb912ee37af99c3a9e3c0d285", size = 34350, upload-time = "2025-08-19T17:21:13.923Z" }, { url = "https://files.pythonhosted.org/packages/21/7f/34fe540450e18d0a993240100e4b86e8d03d831b92af8bb6ddb2662dc6fc/time_machine-2.19.0-cp313-cp313-win32.whl", hash = "sha256:72dbd4cbc3d96dec9dd281ddfbb513982102776b63e4e039f83afb244802a9e5", size = 17047, upload-time = "2025-08-19T17:21:14.874Z" }, { url = "https://files.pythonhosted.org/packages/bf/5d/c8be73df82c7ebe7cd133279670e89b8b110af3ce1412c551caa9d08e625/time_machine-2.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:e17e3e089ac95f9a145ce07ff615e3c85674f7de36f2d92aaf588493a23ffb4b", size = 17868, upload-time = "2025-08-19T17:21:15.819Z" }, { url = "https://files.pythonhosted.org/packages/92/13/2dfd3b8fb285308f61cd7aa9bfa96f46ddf916e3549a0f0afd094c556599/time_machine-2.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:149072aff8e3690e14f4916103d898ea0d5d9c95531b6aa0995251c299533f7b", size = 16710, upload-time = "2025-08-19T17:21:16.748Z" }, { url = "https://files.pythonhosted.org/packages/05/c1/deebb361727d2c5790f9d4d874be1b19afd41f4375581df465e6718b46a2/time_machine-2.19.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f3589fee1ed0ab6ee424a55b0ea1ec694c4ba64cc26895bcd7d99f3d1bc6a28a", size = 20053, upload-time = "2025-08-19T17:21:17.704Z" }, { url = "https://files.pythonhosted.org/packages/45/e8/fe3376951e6118d8ec1d1f94066a169b791424fe4a26c7dfc069b153ee08/time_machine-2.19.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7887e85275c4975fe54df03dcdd5f38bd36be973adc68a8c77e17441c3b443d6", size = 15423, upload-time = "2025-08-19T17:21:18.668Z" }, { url = "https://files.pythonhosted.org/packages/9c/c7/f88d95cd1a87c650cf3749b4d64afdaf580297aa18ad7f4b44ec9d252dfc/time_machine-2.19.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ce0be294c209928563fcce1c587963e60ec803436cf1e181acd5bc1e425d554b", size = 39630, upload-time = "2025-08-19T17:21:19.645Z" }, { url = "https://files.pythonhosted.org/packages/cc/5d/65a5c48a65357e56ec6f032972e4abd1c02d4fca4b0717a3aaefd19014d4/time_machine-2.19.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a62fd1ab380012c86f4c042010418ed45eb31604f4bf4453e17c9fa60bc56a29", size = 41242, upload-time = "2025-08-19T17:21:20.979Z" }, { url = "https://files.pythonhosted.org/packages/f6/f9/fe5209e1615fde0a8cad6c4e857157b150333ed1fe31a7632b08cfe0ebdd/time_machine-2.19.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25ec853a4530a5800731257f93206b12cbdee85ede964ebf8011b66086a7914", size = 44278, upload-time = "2025-08-19T17:21:21.984Z" }, { url = "https://files.pythonhosted.org/packages/4a/3a/a5e5fe9c5d614cde0a9387ff35e8dfd12c5ef6384e4c1a21b04e6e0b905d/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a430e4d0e0556f021a9c78e9b9f68e5e8910bdace4aa34ed4d1a73e239ed9384", size = 42321, upload-time = "2025-08-19T17:21:23.755Z" }, { url = "https://files.pythonhosted.org/packages/a1/c5/56eca774e9162bc1ce59111d2bd69140dc8908c9478c92ec7bd15d547600/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2415b7495ec4364c8067071e964fbadfe746dd4cdb43983f2f0bd6ebed13315c", size = 39270, upload-time = "2025-08-19T17:21:26.009Z" }, { url = "https://files.pythonhosted.org/packages/9b/69/5dd0c420667578169a12acc8c8fd7452e8cfb181e41c9b4ac7e88fa36686/time_machine-2.19.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbfc6b90c10f288594e1bf89a728a98cc0030791fd73541bbdc6b090aff83143", size = 40193, upload-time = "2025-08-19T17:21:27.054Z" }, { url = "https://files.pythonhosted.org/packages/75/a7/de974d421bd55c9355583427c2a38fb0237bb5fd6614af492ba89dacb2f9/time_machine-2.19.0-cp313-cp313t-win32.whl", hash = "sha256:16f5d81f650c0a4d117ab08036dc30b5f8b262e11a4a0becc458e7f1c011b228", size = 17542, upload-time = "2025-08-19T17:21:28.674Z" }, { url = "https://files.pythonhosted.org/packages/76/0a/aa0d05becd5d06ae8d3f16d657dc8cc9400c8d79aef80299de196467ff12/time_machine-2.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:645699616ec14e147094f601e6ab9553ff6cea37fad9c42720a6d7ed04bcd5dc", size = 18703, upload-time = "2025-08-19T17:21:29.663Z" }, { url = "https://files.pythonhosted.org/packages/1f/c0/f785a4c7c73aa176510f7c48b84b49c26be84af0d534deb222e0327f750e/time_machine-2.19.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b32daa965d13237536ea3afaa5ad61ade2b2d9314bc3a20196a0d2e1d7b57c6a", size = 17020, upload-time = "2025-08-19T17:21:30.653Z" }, { url = "https://files.pythonhosted.org/packages/ed/97/c5fb51def06c0b2b6735332ad118ab35b4d9b85368792e5b638e99b1b686/time_machine-2.19.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:31cb43c8fd2d961f31bed0ff4e0026964d2b35e5de9e0fabbfecf756906d3612", size = 19360, upload-time = "2025-08-19T17:21:31.94Z" }, { url = "https://files.pythonhosted.org/packages/2d/4e/2d795f7d6b7f5205ffe737a05bb1cf19d8038233b797062b2ef412b8512b/time_machine-2.19.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:bdf481a75afc6bff3e520db594501975b652f7def21cd1de6aa971d35ba644e6", size = 15033, upload-time = "2025-08-19T17:21:32.934Z" }, { url = "https://files.pythonhosted.org/packages/dd/32/9bad501e360b4e758c58fae616ca5f8c7ad974b343f2463a15b2bf77a366/time_machine-2.19.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:00bee4bb950ac6a08d62af78e4da0cf2b4fc2abf0de2320d0431bf610db06e7c", size = 33379, upload-time = "2025-08-19T17:21:33.925Z" }, { url = "https://files.pythonhosted.org/packages/a3/45/eda0ca4d793dfd162478d6163759b1c6ce7f6e61daa7fd7d62b31f21f87f/time_machine-2.19.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f02199490906582302ce09edd32394fb393271674c75d7aa76c7a3245f16003", size = 35123, upload-time = "2025-08-19T17:21:34.945Z" }, { url = "https://files.pythonhosted.org/packages/f0/5a/97e16325442ae5731fcaac794f0a1ef9980eff8a5491e58201d7eb814a34/time_machine-2.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e35726c7ba625f844c13b1fc0d4f81f394eefaee1d3a094a9093251521f2ef15", size = 36588, upload-time = "2025-08-19T17:21:35.975Z" }, { url = "https://files.pythonhosted.org/packages/e8/9d/bf0b2ccc930cc4a316f26f1c78d3f313cd0fa13bb7480369b730a8f129db/time_machine-2.19.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:304315023999cd401ff02698870932b893369e1cfeb2248d09f6490507a92e97", size = 35013, upload-time = "2025-08-19T17:21:37.017Z" }, { url = "https://files.pythonhosted.org/packages/f0/5a/39ac6a3078174f9715d88364871348b249631f12e76de1b862433b3f8862/time_machine-2.19.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9765d4f003f263ea8bfd90d2d15447ca4b3dfa181922cf6cf808923b02ac180a", size = 33303, upload-time = "2025-08-19T17:21:38.352Z" }, { url = "https://files.pythonhosted.org/packages/b3/ac/d8646baf9f95f2e792a6d7a7b35e92fca253c4a992afff801beafae0e5c2/time_machine-2.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7837ef3fd5911eb9b480909bb93d922737b6bdecea99dfcedb0a03807de9b2d3", size = 34440, upload-time = "2025-08-19T17:21:39.382Z" }, { url = "https://files.pythonhosted.org/packages/ce/8b/8b6568c5ae966d80ead03ab537be3c6acf2af06fb501c2d466a3162c6295/time_machine-2.19.0-cp314-cp314-win32.whl", hash = "sha256:4bb5bd43b1bdfac3007b920b51d8e761f024ed465cfeec63ac4296922a4ec428", size = 17162, upload-time = "2025-08-19T17:21:40.381Z" }, { url = "https://files.pythonhosted.org/packages/46/a5/211c1ab4566eba5308b2dc001b6349e3a032e3f6afa67ca2f27ea6b27af5/time_machine-2.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:f583bbd0aa8ab4a7c45a684bf636d9e042d466e30bcbae1d13e7541e2cbe7207", size = 18040, upload-time = "2025-08-19T17:21:41.363Z" }, { url = "https://files.pythonhosted.org/packages/b8/fc/4c2fb705f6371cb83824da45a8b967514a922fc092a0ef53979334d97a70/time_machine-2.19.0-cp314-cp314-win_arm64.whl", hash = "sha256:f379c6f8a6575a8284592179cf528ce89373f060301323edcc44f1fa1d37be12", size = 16752, upload-time = "2025-08-19T17:21:42.336Z" }, { url = "https://files.pythonhosted.org/packages/79/ab/6437d18f31c666b5116c97572a282ac2590a82a0a9867746a6647eaf4613/time_machine-2.19.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a3b8981f9c663b0906b05ab4d0ca211fae4b63b47c6ec26de5374fe56c836162", size = 20057, upload-time = "2025-08-19T17:21:43.35Z" }, { url = "https://files.pythonhosted.org/packages/6c/a2/e03639ec2ba7200328bbcad8a2b2b1d5fccca9cceb9481b164a1cabdcb33/time_machine-2.19.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8e9c6363893e7f52c226afbebb23e825259222d100e67dfd24c8a6d35f1a1907", size = 15430, upload-time = "2025-08-19T17:21:44.725Z" }, { url = "https://files.pythonhosted.org/packages/5d/ff/39e63a48e840f3e36ce24846ee51dd99c6dba635659b1750a2993771e88e/time_machine-2.19.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:206fcd6c9a6f00cac83db446ad1effc530a8cec244d2780af62db3a2d0a9871b", size = 39622, upload-time = "2025-08-19T17:21:45.821Z" }, { url = "https://files.pythonhosted.org/packages/9a/2e/ee5ac79c4954768705801e54817c7d58e07e25a0bb227e775f501f3e2122/time_machine-2.19.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf33016a1403c123373ffaeff25e26e69d63bf2c63b6163932efed94160db7ef", size = 41235, upload-time = "2025-08-19T17:21:46.783Z" }, { url = "https://files.pythonhosted.org/packages/3a/3e/9af5f39525e779185c77285b8bbae15340eeeaa0afb33d458bc8b47d459b/time_machine-2.19.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9247c4bb9bbd3ff584ef4efbdec8efd9f37aa08bcfc4728bde1e489c2cb445bd", size = 44276, upload-time = "2025-08-19T17:21:47.759Z" }, { url = "https://files.pythonhosted.org/packages/59/fe/572c7443cc27140bbeae3947279bbd4a120f9e8622253a20637f260b7813/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:77f9bb0b86758d1f2d9352642c874946ad5815df53ef4ca22eb9d532179fe50d", size = 42330, upload-time = "2025-08-19T17:21:48.881Z" }, { url = "https://files.pythonhosted.org/packages/cf/24/1a81c2e08ee7dae13ec8ceed27a29afa980c3d63852e42f1e023bf0faa03/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0b529e262df3b9c449f427385f4d98250828c879168c2e00eec844439f40b370", size = 39281, upload-time = "2025-08-19T17:21:49.907Z" }, { url = "https://files.pythonhosted.org/packages/d2/60/6f0d6e5108978ca1a2a4ffb4d1c7e176d9199bb109fd44efe2680c60b52a/time_machine-2.19.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9199246e31cdc810e5d89cb71d09144c4d745960fdb0824da4994d152aca3303", size = 40201, upload-time = "2025-08-19T17:21:50.953Z" }, { url = "https://files.pythonhosted.org/packages/73/b9/3ea4951e8293b0643feb98c0b9a176fa822154f1810835db3f282968ab10/time_machine-2.19.0-cp314-cp314t-win32.whl", hash = "sha256:0fe81bae55b7aefc2c2a34eb552aa82e6c61a86b3353a3c70df79b9698cb02ca", size = 17743, upload-time = "2025-08-19T17:21:51.948Z" }, { url = "https://files.pythonhosted.org/packages/e4/8b/cd802884ca8a98e2b6cdc2397d57dd12ff8a7d1481e06fc3fad3d4e7e5ff/time_machine-2.19.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7253791b8d7e7399fbeed7a8193cb01bc004242864306288797056badbdaf80b", size = 18956, upload-time = "2025-08-19T17:21:52.997Z" }, { url = "https://files.pythonhosted.org/packages/c6/49/cabb1593896082fd55e34768029b8b0ca23c9be8b2dc127e0fc14796d33e/time_machine-2.19.0-cp314-cp314t-win_arm64.whl", hash = "sha256:536bd1ac31ab06a1522e7bf287602188f502dc19d122b1502c4f60b1e8efac79", size = 17068, upload-time = "2025-08-19T17:21:54.064Z" }, { url = "https://files.pythonhosted.org/packages/d6/05/0608376c3167afe6cf7cdfd2b05c142ea4c42616eee9ba06d1799965806a/time_machine-2.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8bb00b30ec9fe56d01e9812df1ffe39f331437cef9bfaebcc81c83f7f8f8ee2", size = 19659, upload-time = "2025-08-19T17:21:55.426Z" }, { url = "https://files.pythonhosted.org/packages/11/c4/72eb8c7b36830cf36c51d7bc2f1ac313d68881c3a58040fb6b42c4523d20/time_machine-2.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d821c60efc08a97cc11e5482798e6fd5eba5c0f22a02db246b50895dbdc0de41", size = 15153, upload-time = "2025-08-19T17:21:56.505Z" }, { url = "https://files.pythonhosted.org/packages/89/1a/0782e1f5c8ab8809ebd992709e1bb69d67600191baa023af7a5d32023a3c/time_machine-2.19.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb051aec7b3b6e96a200d911c225901e6133ff3da11e470e24111a53bbc13637", size = 32555, upload-time = "2025-08-19T17:21:57.74Z" }, { url = "https://files.pythonhosted.org/packages/94/b0/8ef58e2f6321851d5900ca3d18044938832c2ed42a2ac7570ca6aa29768a/time_machine-2.19.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe59909d95a2ef5e01ce3354fdea3908404c2932c2069f00f66dff6f27e9363e", size = 34185, upload-time = "2025-08-19T17:21:59.361Z" }, { url = "https://files.pythonhosted.org/packages/82/74/ce0c9867f788c1fb22c417ec1aae47a24117e53d51f6ff97d7c6ca5392f6/time_machine-2.19.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:29e84b8682645b16eb6f9e8ec11c35324ad091841a11cf4fc3fc7f6119094c89", size = 35917, upload-time = "2025-08-19T17:22:00.421Z" }, { url = "https://files.pythonhosted.org/packages/d2/70/6f97a8f552dbaa66feb10170b5726dab74bc531673d1ed9d6f271547e54c/time_machine-2.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a11f1c0e0d06023dc01614c964e256138913551d3ae6dca5148f79081156336", size = 34584, upload-time = "2025-08-19T17:22:01.447Z" }, { url = "https://files.pythonhosted.org/packages/48/c8/cf139088ce537c15d7f03cf56ec317d3a5cfb520e30aa711ea0248d0ae8a/time_machine-2.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:57a235a6307c54df50e69f1906e2f199e47da91bde4b886ee05aff57fe4b6bf6", size = 32608, upload-time = "2025-08-19T17:22:02.548Z" }, { url = "https://files.pythonhosted.org/packages/b1/17/0ec41ef7a30c6753fb226a28b74162b264b35724905ced4098f2f5076ded/time_machine-2.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:426aba552f7af9604adad9ef570c859af7c1081d878db78089fac159cd911b0a", size = 33686, upload-time = "2025-08-19T17:22:03.606Z" }, { url = "https://files.pythonhosted.org/packages/b0/19/586f15159083ec84f178d494c60758c46603b00c9641b04deb63f1950128/time_machine-2.19.0-cp39-cp39-win32.whl", hash = "sha256:67772c7197a3a712d1b970ed545c6e98db73524bd90e245fd3c8fa7ad7630768", size = 17133, upload-time = "2025-08-19T17:22:04.989Z" }, { url = "https://files.pythonhosted.org/packages/6a/c2/bfe4b906a9fe0bf2d011534314212ed752d6b8f392c9c82f6ac63dccc5ab/time_machine-2.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:011d7859089263204dc5fdf83dce7388f986fe833c9381d6106b4edfda2ebd3e", size = 17972, upload-time = "2025-08-19T17:22:06.026Z" }, { url = "https://files.pythonhosted.org/packages/5d/73/182343eba05aa5787732aaa68f3b3feb5e40ddf86b928ae941be45646393/time_machine-2.19.0-cp39-cp39-win_arm64.whl", hash = "sha256:e1af66550fa4685434f00002808a525f176f1f92746646c0019bb86fbff48b27", size = 16820, upload-time = "2025-08-19T17:22:07.227Z" }, ] [[package]] name = "tomli" version = "2.2.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] [[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 = "typing-inspection" version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] name = "urllib3" version = "1.26.20" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version < '3.10'", ] sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, ] [[package]] name = "urllib3" version = "2.5.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", "python_full_version == '3.10.*'", ] sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] name = "virtualenv" version = "20.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, ] [[package]] name = "werkzeug" version = "3.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, ] [[package]] name = "wrapt" version = "1.17.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, { url = "https://files.pythonhosted.org/packages/41/be/be9b3b0a461ee3e30278706f3f3759b9b69afeedef7fe686036286c04ac6/wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc", size = 53485, upload-time = "2025-08-12T05:51:53.11Z" }, { url = "https://files.pythonhosted.org/packages/b3/a8/8f61d6b8f526efc8c10e12bf80b4206099fea78ade70427846a37bc9cbea/wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9", size = 38675, upload-time = "2025-08-12T05:51:42.885Z" }, { url = "https://files.pythonhosted.org/packages/48/f1/23950c29a25637b74b322f9e425a17cc01a478f6afb35138ecb697f9558d/wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d", size = 38956, upload-time = "2025-08-12T05:52:03.149Z" }, { url = "https://files.pythonhosted.org/packages/43/46/dd0791943613885f62619f18ee6107e6133237a6b6ed8a9ecfac339d0b4f/wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a", size = 81745, upload-time = "2025-08-12T05:52:49.62Z" }, { url = "https://files.pythonhosted.org/packages/dd/ec/bb2d19bd1a614cc4f438abac13ae26c57186197920432d2a915183b15a8b/wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139", size = 82833, upload-time = "2025-08-12T05:52:27.738Z" }, { url = "https://files.pythonhosted.org/packages/8d/eb/66579aea6ad36f07617fedca8e282e49c7c9bab64c63b446cfe4f7f47a49/wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df", size = 81889, upload-time = "2025-08-12T05:52:29.023Z" }, { url = "https://files.pythonhosted.org/packages/04/9c/a56b5ac0e2473bdc3fb11b22dd69ff423154d63861cf77911cdde5e38fd2/wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b", size = 81344, upload-time = "2025-08-12T05:52:50.869Z" }, { url = "https://files.pythonhosted.org/packages/93/4c/9bd735c42641d81cb58d7bfb142c58f95c833962d15113026705add41a07/wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81", size = 36462, upload-time = "2025-08-12T05:53:19.623Z" }, { url = "https://files.pythonhosted.org/packages/f0/ea/0b72f29cb5ebc16eb55c57dc0c98e5de76fc97f435fd407f7d409459c0a6/wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f", size = 38740, upload-time = "2025-08-12T05:53:18.271Z" }, { url = "https://files.pythonhosted.org/packages/c3/8b/9eae65fb92321e38dbfec7719b87d840a4b92fde83fd1bbf238c5488d055/wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f", size = 36806, upload-time = "2025-08-12T05:52:58.765Z" }, { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] [[package]] name = "xmltodict" version = "1.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/aa/917ceeed4dbb80d2f04dbd0c784b7ee7bba8ae5a54837ef0e5e062cd3cfb/xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649", size = 25725, upload-time = "2025-09-17T21:59:26.459Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d", size = 13893, upload-time = "2025-09-17T21:59:24.859Z" }, ] [[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/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, { 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/94/fd/6480106702a79bcceda5fd9c63cb19a04a6506bd5ce7fd8d9b63742f0021/yarl-1.22.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3aa27acb6de7a23785d81557577491f6c38a5209a254d1191519d07d8fe51748", size = 141301, upload-time = "2025-10-06T14:12:19.01Z" }, { url = "https://files.pythonhosted.org/packages/42/e1/6d95d21b17a93e793e4ec420a925fe1f6a9342338ca7a563ed21129c0990/yarl-1.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:af74f05666a5e531289cb1cc9c883d1de2088b8e5b4de48004e5ca8a830ac859", size = 93864, upload-time = "2025-10-06T14:12:21.05Z" }, { url = "https://files.pythonhosted.org/packages/32/58/b8055273c203968e89808413ea4c984988b6649baabf10f4522e67c22d2f/yarl-1.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:62441e55958977b8167b2709c164c91a6363e25da322d87ae6dd9c6019ceecf9", size = 94706, upload-time = "2025-10-06T14:12:23.287Z" }, { url = "https://files.pythonhosted.org/packages/18/91/d7bfbc28a88c2895ecd0da6a874def0c147de78afc52c773c28e1aa233a3/yarl-1.22.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b580e71cac3f8113d3135888770903eaf2f507e9421e5697d6ee6d8cd1c7f054", size = 347100, upload-time = "2025-10-06T14:12:28.527Z" }, { url = "https://files.pythonhosted.org/packages/bd/e8/37a1e7b99721c0564b1fc7b0a4d1f595ef6fb8060d82ca61775b644185f7/yarl-1.22.0-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e81fda2fb4a07eda1a2252b216aa0df23ebcd4d584894e9612e80999a78fd95b", size = 318902, upload-time = "2025-10-06T14:12:30.528Z" }, { url = "https://files.pythonhosted.org/packages/1c/ef/34724449d7ef2db4f22df644f2dac0b8a275d20f585e526937b3ae47b02d/yarl-1.22.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:99b6fc1d55782461b78221e95fc357b47ad98b041e8e20f47c1411d0aacddc60", size = 363302, upload-time = "2025-10-06T14:12:32.295Z" }, { url = "https://files.pythonhosted.org/packages/8a/04/88a39a5dad39889f192cce8d66cc4c58dbeca983e83f9b6bf23822a7ed91/yarl-1.22.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:088e4e08f033db4be2ccd1f34cf29fe994772fb54cfe004bbf54db320af56890", size = 370816, upload-time = "2025-10-06T14:12:34.01Z" }, { url = "https://files.pythonhosted.org/packages/6b/1f/5e895e547129413f56c76be2c3ce4b96c797d2d0ff3e16a817d9269b12e6/yarl-1.22.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4e1f6f0b4da23e61188676e3ed027ef0baa833a2e633c29ff8530800edccba", size = 346465, upload-time = "2025-10-06T14:12:35.977Z" }, { url = "https://files.pythonhosted.org/packages/11/13/a750e9fd6f9cc9ed3a52a70fe58ffe505322f0efe0d48e1fd9ffe53281f5/yarl-1.22.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:84fc3ec96fce86ce5aa305eb4aa9358279d1aa644b71fab7b8ed33fe3ba1a7ca", size = 341506, upload-time = "2025-10-06T14:12:37.788Z" }, { url = "https://files.pythonhosted.org/packages/3c/67/bb6024de76e7186611ebe626aec5b71a2d2ecf9453e795f2dbd80614784c/yarl-1.22.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5dbeefd6ca588b33576a01b0ad58aa934bc1b41ef89dee505bf2932b22ddffba", size = 335030, upload-time = "2025-10-06T14:12:39.775Z" }, { url = "https://files.pythonhosted.org/packages/a2/be/50b38447fd94a7992996a62b8b463d0579323fcfc08c61bdba949eef8a5d/yarl-1.22.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14291620375b1060613f4aab9ebf21850058b6b1b438f386cc814813d901c60b", size = 358560, upload-time = "2025-10-06T14:12:41.547Z" }, { url = "https://files.pythonhosted.org/packages/e2/89/c020b6f547578c4e3dbb6335bf918f26e2f34ad0d1e515d72fd33ac0c635/yarl-1.22.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a4fcfc8eb2c34148c118dfa02e6427ca278bfd0f3df7c5f99e33d2c0e81eae3e", size = 357290, upload-time = "2025-10-06T14:12:43.861Z" }, { url = "https://files.pythonhosted.org/packages/8c/52/c49a619ee35a402fa3a7019a4fa8d26878fec0d1243f6968bbf516789578/yarl-1.22.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:029866bde8d7b0878b9c160e72305bbf0a7342bcd20b9999381704ae03308dc8", size = 350700, upload-time = "2025-10-06T14:12:46.868Z" }, { url = "https://files.pythonhosted.org/packages/ab/c9/f5042d87777bf6968435f04a2bbb15466b2f142e6e47fa4f34d1a3f32f0c/yarl-1.22.0-cp39-cp39-win32.whl", hash = "sha256:4dcc74149ccc8bba31ce1944acee24813e93cfdee2acda3c172df844948ddf7b", size = 82323, upload-time = "2025-10-06T14:12:48.633Z" }, { url = "https://files.pythonhosted.org/packages/fd/58/d00f7cad9eba20c4eefac2682f34661d1d1b3a942fc0092eb60e78cfb733/yarl-1.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:10619d9fdee46d20edc49d3479e2f8269d0779f1b031e6f7c2aa1c76be04b7ed", size = 87145, upload-time = "2025-10-06T14:12:50.241Z" }, { url = "https://files.pythonhosted.org/packages/c2/a3/70904f365080780d38b919edd42d224b8c4ce224a86950d2eaa2a24366ad/yarl-1.22.0-cp39-cp39-win_arm64.whl", hash = "sha256:dd7afd3f8b0bfb4e0d9fc3c31bfe8a4ec7debe124cfd90619305def3c8ca8cd2", size = 82173, upload-time = "2025-10-06T14:12:51.869Z" }, { 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" }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, ]