pax_global_header00006660000000000000000000000064147641631270014525gustar00rootroot0000000000000052 comment=e3bd5bdf44f5d187802de6dcb08d27e1ca6da048 aiohappyeyeballs-2.6.1/000077500000000000000000000000001476416312700150665ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/.all-contributorsrc000066400000000000000000000004561476416312700207240ustar00rootroot00000000000000{ "projectName": "aiohappyeyeballs", "projectOwner": "aio-libs", "repoType": "github", "repoHost": "https://github.com", "files": [ "README.md" ], "imageSize": 80, "commit": true, "commitConvention": "angular", "contributors": [], "contributorsPerLine": 7, "skipCi": true } aiohappyeyeballs-2.6.1/.copier-answers.yml000066400000000000000000000010131476416312700206230ustar00rootroot00000000000000# Changes here will be overwritten by Copier _commit: b09ed7d _src_path: gh:browniebroke/pypackage-template add_me_as_contributor: false copyright_year: '2023' documentation: true email: nick@koston.org full_name: J. Nick Koston github_username: aio-libs has_cli: false initial_commit: true open_source_license: PSF-2.0 package_name: aiohappyeyeballs project_name: aiohappyeyeballs project_short_description: Happy Eyeballs project_slug: aiohappyeyeballs run_poetry_install: true setup_github: true setup_pre_commit: true aiohappyeyeballs-2.6.1/.editorconfig000066400000000000000000000004441476416312700175450ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = space indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true charset = utf-8 end_of_line = lf [*.bat] indent_style = tab end_of_line = crlf [LICENSE] insert_final_newline = false [Makefile] indent_style = tab aiohappyeyeballs-2.6.1/.github/000077500000000000000000000000001476416312700164265ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/.github/FUNDING.yml000066400000000000000000000001321476416312700202370ustar00rootroot00000000000000--- # These are supported funding model platforms github: - webknjaz - Dreamsorcerer aiohappyeyeballs-2.6.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001476416312700206115ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/.github/ISSUE_TEMPLATE/1-bug_report.md000066400000000000000000000004221476416312700234370ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve labels: bug --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Additional context** Add any other context about the problem here. aiohappyeyeballs-2.6.1/.github/ISSUE_TEMPLATE/2-feature-request.md000066400000000000000000000006721476416312700244200ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project labels: enhancement --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Additional context** Add any other context or screenshots about the feature request here. aiohappyeyeballs-2.6.1/.github/dependabot.yml000066400000000000000000000013511476416312700212560ustar00rootroot00000000000000# 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/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" commit-message: prefix: "chore(deps-ci): " groups: github-actions: patterns: - "*" - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" aiohappyeyeballs-2.6.1/.github/labels.toml000066400000000000000000000035151476416312700205710ustar00rootroot00000000000000[breaking] color = "ffcc00" name = "breaking" description = "Breaking change." [bug] color = "d73a4a" name = "bug" description = "Something isn't working" [dependencies] color = "0366d6" name = "dependencies" description = "Pull requests that update a dependency file" [github_actions] color = "000000" name = "github_actions" description = "Update of github actions" [documentation] color = "1bc4a5" name = "documentation" description = "Improvements or additions to documentation" [duplicate] color = "cfd3d7" name = "duplicate" description = "This issue or pull request already exists" [enhancement] color = "a2eeef" name = "enhancement" description = "New feature or request" ["good first issue"] color = "7057ff" name = "good first issue" description = "Good for newcomers" ["help wanted"] color = "008672" name = "help wanted" description = "Extra attention is needed" [invalid] color = "e4e669" name = "invalid" description = "This doesn't seem right" [nochangelog] color = "555555" name = "nochangelog" description = "Exclude pull requests from changelog" [question] color = "d876e3" name = "question" description = "Further information is requested" [removed] color = "e99695" name = "removed" description = "Removed piece of functionalities." [tests] color = "bfd4f2" name = "tests" description = "CI, CD and testing related changes" [wontfix] color = "ffffff" name = "wontfix" description = "This will not be worked on" [discussion] color = "c2e0c6" name = "discussion" description = "Some discussion around the project" [hacktoberfest] color = "ffa663" name = "hacktoberfest" description = "Good issues for Hacktoberfest" [answered] color = "0ee2b6" name = "answered" description = "Automatically closes as answered after a delay" [waiting] color = "5f7972" name = "waiting" description = "Automatically closes if no answer after a delay" aiohappyeyeballs-2.6.1/.github/workflows/000077500000000000000000000000001476416312700204635ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/.github/workflows/ci-cd.yml000066400000000000000000000104021476416312700221620ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request: concurrency: group: ${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.x - uses: pre-commit/action@v3.0.1 # Make sure commit messages follow the conventional commits convention: # https://www.conventionalcommits.org commitlint: name: Lint Commit Messages runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@v6.2.1 test: strategy: fail-fast: false matrix: python-version: - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" os: - ubuntu-latest - windows-latest - macOS-latest include: # https://github.com/aio-libs/aiohappyeyeballs/issues/150 - os: ubuntu-20.04 python-version: "3.9.1" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - uses: snok/install-poetry@v1.4.1 - name: Install Dependencies run: poetry install shell: bash - name: Test with Pytest run: poetry run pytest --cov-report=xml shell: bash - name: Upload coverage to Codecov uses: codecov/codecov-action@v5.4.0 with: token: ${{ secrets.CODECOV_TOKEN }} test_release: needs: - test - lint - commitlint runs-on: ubuntu-latest environment: test_release concurrency: release if: github.ref_name != 'main' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.head_ref || github.ref_name }} # Dry run of PSR to build the distribution - name: Test release uses: python-semantic-release/python-semantic-release@v9.21.0 with: root_options: --noop - uses: snok/install-poetry@v1.4.1 - name: Install Dependencies run: poetry install --only main,test_build shell: bash - name: Test build of distribution packages shell: bash run: | poetry build poetry run python -Im twine check --strict dist/* build_release: needs: - test - lint - commitlint if: github.ref_name == 'main' && !startsWith(github.event.pull_request.title,'chore') && !startsWith(github.event.head_commit.message,'chore') runs-on: ubuntu-latest outputs: released: ${{ steps.release.outputs.released }} concurrency: release permissions: id-token: write contents: write steps: - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.head_ref || github.ref_name }} # On main branch: Call PSR to build the distribution - name: Release uses: python-semantic-release/python-semantic-release@v9.21.0 id: release with: github_token: ${{ secrets.GITHUB_TOKEN }} - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions path: dist/ release: needs: - build_release if: needs.build_release.outputs.released == 'true' runs-on: ubuntu-latest environment: pypi concurrency: release permissions: id-token: write contents: write steps: - name: Download all the dists uses: actions/download-artifact@v4 with: name: python-package-distributions path: dist/ - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.head_ref || github.ref_name }} - name: Publish package distributions to GitHub Releases uses: python-semantic-release/upload-to-gh-release@v9.8.9 with: github_token: ${{ secrets.GITHUB_TOKEN }} aiohappyeyeballs-2.6.1/.github/workflows/issue-manager.yml000066400000000000000000000013401476416312700237440ustar00rootroot00000000000000name: Issue Manager on: schedule: - cron: "0 0 * * *" issue_comment: types: - created issues: types: - labeled pull_request_target: types: - labeled workflow_dispatch: jobs: issue-manager: runs-on: ubuntu-latest steps: - uses: tiangolo/issue-manager@0.5.1 with: token: ${{ secrets.GITHUB_TOKEN }} config: > { "answered": { "message": "Assuming the original issue was solved, it will be automatically closed now." }, "waiting": { "message": "Automatically closing. To re-open, please provide the additional information requested." } } aiohappyeyeballs-2.6.1/.gitignore000066400000000000000000000041101476416312700170520ustar00rootroot00000000000000# Created by .ignore support plugin (hsz.mobi) ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # 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/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder {{package_name}} settings .spyderproject .spyproject # Rope {{package_name}} settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ aiohappyeyeballs-2.6.1/.gitpod.yml000066400000000000000000000003061476416312700171540ustar00rootroot00000000000000tasks: - command: | pip install poetry PIP_USER=false poetry install - command: | pip install pre-commit pre-commit install PIP_USER=false pre-commit install-hooks aiohappyeyeballs-2.6.1/.idea/000077500000000000000000000000001476416312700160465ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/.idea/aiohappyeyeballs.iml000066400000000000000000000005151476416312700221050ustar00rootroot00000000000000 aiohappyeyeballs-2.6.1/.idea/watcherTasks.xml000066400000000000000000000052531476416312700212400ustar00rootroot00000000000000 aiohappyeyeballs-2.6.1/.idea/workspace.xml000066400000000000000000000027361476416312700205760ustar00rootroot00000000000000 aiohappyeyeballs-2.6.1/.pre-commit-config.yaml000066400000000000000000000030271476416312700213510ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks exclude: "CHANGELOG.md|.copier-answers.yml|.all-contributorsrc" default_stages: [pre-commit] ci: autofix_commit_msg: "chore(pre-commit.ci): auto fixes" autoupdate_commit_msg: "chore(pre-commit.ci): pre-commit autoupdate" repos: - repo: https://github.com/commitizen-tools/commitizen rev: v4.4.1 hooks: - id: commitizen stages: [commit-msg] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: debug-statements - id: check-builtin-literals - id: check-case-conflict - id: check-docstring-first - id: check-json - id: check-toml - id: check-xml - id: check-yaml - id: detect-private-key - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-poetry/poetry rev: 2.1.1 hooks: - id: poetry-check - repo: https://github.com/pre-commit/mirrors-prettier rev: v4.0.0-alpha.8 hooks: - id: prettier args: ["--tab-width", "2"] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.10 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] # Run the formatter. - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.15.0 hooks: - id: mypy additional_dependencies: [] aiohappyeyeballs-2.6.1/.readthedocs.yml000066400000000000000000000011011476416312700201450ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-20.04 tools: python: "3.12" jobs: post_create_environment: # Install poetry - pip install poetry post_install: # Install dependencies, reusing RTD virtualenv - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs # Build documentation in the docs directory with Sphinx sphinx: configuration: docs/conf.py aiohappyeyeballs-2.6.1/CHANGELOG.md000066400000000000000000000242751476416312700167110ustar00rootroot00000000000000# Changelog ## v2.6.1 (2025-03-12) ### Bug fixes - Resolve typeerror on import for python < 3.9.2 (#151) ([`2042c82`](https://github.com/aio-libs/aiohappyeyeballs/commit/2042c82f9978f41c31b58aa4e3d8fc3b9c3ec2ec)) ## v2.6.0 (2025-03-11) ### Features - Publish documentation (#149) ([`4235273`](https://github.com/aio-libs/aiohappyeyeballs/commit/42352736d12c60d500c63b9598ffab05ef5e8829)) ## v2.5.0 (2025-03-06) ### Features - Add callback for users to customize socket creation (#147) ([`8e1bc6a`](https://github.com/aio-libs/aiohappyeyeballs/commit/8e1bc6a4bc6282ccf29db441c33dd8d806003ffd)) ## v2.4.8 (2025-03-04) ### Bug fixes - Close runner up sockets in the event there are multiple winners (#143) ([`476a05b`](https://github.com/aio-libs/aiohappyeyeballs/commit/476a05b956627700baa84eb6aac28c395da92a9f)) ## v2.4.7 (2025-03-04) ### Bug fixes - Resolve warnings when running tests (#144) ([`e96264a`](https://github.com/aio-libs/aiohappyeyeballs/commit/e96264aec89b9bd34d37413f610d039c56393a48)) ## v2.4.6 (2025-02-07) ### Bug fixes - Ensure all timers are cancelled when after staggered race finishes (#136) ([`f75891d`](https://github.com/aio-libs/aiohappyeyeballs/commit/f75891d8974693b24af9789a8981ed7f6bc55c5c)) ## v2.4.5 (2025-02-07) ### Bug fixes - Keep classifiers in project to avoid automatic enrichment (#134) ([`99edb20`](https://github.com/aio-libs/aiohappyeyeballs/commit/99edb20e9d3e53ead65b55cb3e93c22c03d06599)) - Move classifiers to prevent recalculation by poetry (#131) ([`66e1c90`](https://github.com/aio-libs/aiohappyeyeballs/commit/66e1c90ae81f71c7039cd62c60417a96202d906c)) ## v2.4.4 (2024-11-30) ### Bug fixes - Handle oserror on failure to close socket instead of raising indexerror (#114) ([`c542f68`](https://github.com/aio-libs/aiohappyeyeballs/commit/c542f684d329fed04093caa2b31d8f7f6e0e0949)) ## v2.4.3 (2024-09-30) ### Bug fixes - Rewrite staggered_race to be race safe (#101) ([`9db617a`](https://github.com/aio-libs/aiohappyeyeballs/commit/9db617a982ee27994bf13c805f9c4f054f05de47)) - Re-raise runtimeerror when uvloop raises runtimeerror during connect (#105) ([`c8f1fa9`](https://github.com/aio-libs/aiohappyeyeballs/commit/c8f1fa93d698f216f84de7074a6282777fbf0439)) ## v2.4.2 (2024-09-27) ### Bug fixes - Copy staggered from standard lib for python 3.12+ (#95) ([`c5a4023`](https://github.com/aio-libs/aiohappyeyeballs/commit/c5a4023d904b3e72f30b8a9f56913894dda4c9d0)) ## v2.4.1 (2024-09-26) ### Bug fixes - Avoid passing loop to staggered.staggered_race (#94) ([`5f80b79`](https://github.com/aio-libs/aiohappyeyeballs/commit/5f80b7951f32d727039d9db776a17a6eba8877cd)) ## v2.4.0 (2024-08-19) ### Features - Add support for python 3.13 (#86) ([`4f2152f`](https://github.com/aio-libs/aiohappyeyeballs/commit/4f2152fbb6b1d915c2fd68219339d998c47a71f9)) ### Documentation - Fix a trivial typo in readme.md (#84) ([`f5ae7d4`](https://github.com/aio-libs/aiohappyeyeballs/commit/f5ae7d4bce04ee0645257ac828745a3b989ef149)) ## v2.3.7 (2024-08-17) ### Bug fixes - Correct classifier for license python-2.0.1 (#83) ([`186be05`](https://github.com/aio-libs/aiohappyeyeballs/commit/186be056ea441bb3fa7620864f46c6f8431f3a34)) ## v2.3.6 (2024-08-16) ### Bug fixes - Adjust license to python-2.0.1 (#82) ([`30a2dc5`](https://github.com/aio-libs/aiohappyeyeballs/commit/30a2dc57c49d1000ebdafa8c81ecf4f79e35c9f3)) ## v2.3.5 (2024-08-07) ### Bug fixes - Remove upper bound on python requirement (#74) ([`0de1e53`](https://github.com/aio-libs/aiohappyeyeballs/commit/0de1e534fc5b7526e11bf203ab09b95b13f3070b)) - Preserve errno if all exceptions have the same errno (#77) ([`7bbb2bf`](https://github.com/aio-libs/aiohappyeyeballs/commit/7bbb2bf0feb3994953a52a1d606e682acad49cb8)) - Adjust license classifier to better reflect license terms (#78) ([`56e7ba6`](https://github.com/aio-libs/aiohappyeyeballs/commit/56e7ba612c5029364bb960b07022a2b720f0a967)) ### Documentation - Add link to happy eyeballs explanation (#73) ([`077710c`](https://github.com/aio-libs/aiohappyeyeballs/commit/077710c150b6c762ffe408e0ad418c506a2d6f31)) ## v2.3.4 (2024-07-31) ### Bug fixes - Add missing asyncio to fix truncated package description (#67) ([`2644df1`](https://github.com/aio-libs/aiohappyeyeballs/commit/2644df179e21e4513da857f2aea2aa64a3fb6316)) ## v2.3.3 (2024-07-31) ### Bug fixes - Add missing python version classifiers (#65) ([`489016f`](https://github.com/aio-libs/aiohappyeyeballs/commit/489016feb53d4fd5f9880f27dc40a5198d5b0be2)) - Update classifiers to include license (#60) ([`a746c29`](https://github.com/aio-libs/aiohappyeyeballs/commit/a746c296b324407efef272f422a990587b9d6057)) - Workaround broken `asyncio.staggered` on python < 3.8.2 (#61) ([`b16f107`](https://github.com/aio-libs/aiohappyeyeballs/commit/b16f107d9493817247c27ab83522901f086a13b5)) - Include tests in the source distribution package (#62) ([`53053b6`](https://github.com/aio-libs/aiohappyeyeballs/commit/53053b6a38ef868e0170940ced5e0611ebd1be4c)) ## v2.3.2 (2024-01-06) ### Bug fixes - Update urls for the new home for this library (#43) ([`c6d4358`](https://github.com/aio-libs/aiohappyeyeballs/commit/c6d43586d5ca56472892767d4a47d28348158544)) ## v2.3.1 (2023-12-14) ### Bug fixes - Remove test import from tests (#31) ([`c529b15`](https://github.com/aio-libs/aiohappyeyeballs/commit/c529b15fbead0aa5cde9dd5c460ff5abd15fc355)) ## v2.3.0 (2023-12-12) ### Features - Avoid _interleave_addrinfos when there is only a single addr_info (#29) ([`305f6f1`](https://github.com/aio-libs/aiohappyeyeballs/commit/305f6f13d028ab3ead7923870601175102c5970c)) ## v2.2.0 (2023-12-11) ### Features - Make interleave with pop_addr_infos_interleave optional to match cpython (#28) ([`adbc8ad`](https://github.com/aio-libs/aiohappyeyeballs/commit/adbc8adfaa44349ca83966787400413668f0b4b6)) ## v2.1.0 (2023-12-11) ### Features - Add addr_to_addr_info util for converting addr to addr_infos (#27) ([`2e25a98`](https://github.com/aio-libs/aiohappyeyeballs/commit/2e25a98f2339d84bc7951ad17f0b38c104a97a71)) ## v2.0.0 (2023-12-10) ### Features - Require the full address tuple for the remove_addr_infos util (#26) ([`d7e5df1`](https://github.com/aio-libs/aiohappyeyeballs/commit/d7e5df12a01838e81729af4c49938e98b3407e03)) ## v1.8.1 (2023-12-10) ### Bug fixes - Move types into a single file (#24) ([`8d4cfee`](https://github.com/aio-libs/aiohappyeyeballs/commit/8d4cfeeaa7862e028e941c49f8c84dcee0b9b1ac)) ## v1.8.0 (2023-12-10) ### Features - Add utils (#23) ([`d89311d`](https://github.com/aio-libs/aiohappyeyeballs/commit/d89311d1a433dde75863019a08717a531f68befa)) ## v1.7.0 (2023-12-09) ### Bug fixes - License should be psf-2.0 (#22) ([`ca9c1fc`](https://github.com/aio-libs/aiohappyeyeballs/commit/ca9c1fca4d63c54855fbe582132b5dcb229c7591)) ### Features - Add some more examples to the docs (#21) ([`6cd0b5d`](https://github.com/aio-libs/aiohappyeyeballs/commit/6cd0b5d10357a9d20fc5ee1c96db18c6994cd8fc)) ## v1.6.0 (2023-12-09) ### Features - Add coverage for multiple and same exceptions (#20) ([`2781b87`](https://github.com/aio-libs/aiohappyeyeballs/commit/2781b87c56aa1c08345d91dce5c1642f2b3e396d)) ## v1.5.0 (2023-12-09) ### Features - Add coverage for setblocking failing (#19) ([`f759a08`](https://github.com/aio-libs/aiohappyeyeballs/commit/f759a08180f0237cb68d353090f7ba0efe625074)) - Add cover for passing the loop (#18) ([`2d26911`](https://github.com/aio-libs/aiohappyeyeballs/commit/2d26911e9237691c168a705b2d6be2a68fa8b7ac)) ## v1.4.1 (2023-12-09) ### Bug fixes - Ensure exception error is stringified (#17) ([`747cf1d`](https://github.com/aio-libs/aiohappyeyeballs/commit/747cf1d231dc427b79ff1f8128779413a50be5d8)) ## v1.4.0 (2023-12-09) ### Features - Add coverage for unexpected exception (#16) ([`bad4874`](https://github.com/aio-libs/aiohappyeyeballs/commit/bad48745d3621fcbbe559d55180dc5f5856dc0fa)) ## v1.3.0 (2023-12-09) ### Features - Add coverage for bind failure with local addresses (#15) ([`f71ec23`](https://github.com/aio-libs/aiohappyeyeballs/commit/f71ec23228d4dad4bc2c3a6630e6e4361b54df44)) ## v1.2.0 (2023-12-09) ### Features - Add coverage for passing local addresses (#14) ([`72a92e3`](https://github.com/aio-libs/aiohappyeyeballs/commit/72a92e3a599cde082856354e806a793f2b9eff62)) ## v1.1.0 (2023-12-09) ### Features - Add example usage (#13) ([`707fddc`](https://github.com/aio-libs/aiohappyeyeballs/commit/707fddcd8e8aff27af2180af6271898003ca1782)) ## v1.0.0 (2023-12-09) ### Features - Rename create_connection to start_connection (#12) ([`f8b6038`](https://github.com/aio-libs/aiohappyeyeballs/commit/f8b60383d9b9f013baf421ad4e4e183559b7a705)) ## v0.9.0 (2023-12-09) ### Features - Add coverage for interleave (#11) ([`62817f1`](https://github.com/aio-libs/aiohappyeyeballs/commit/62817f1473bb5702f8fa9edc6f6b24139990cd01)) ## v0.8.0 (2023-12-09) ### Features - Add coverage for multi ipv6 (#10) ([`6dc8f89`](https://github.com/aio-libs/aiohappyeyeballs/commit/6dc8f89ff99a38c8ecaf8045c9afbe683d6f2c6e)) ## v0.7.0 (2023-12-09) ### Features - Add coverage for ipv6 failure (#9) ([`7aee8f6`](https://github.com/aio-libs/aiohappyeyeballs/commit/7aee8f64064cfc8d79f385c4dfee45036aacd6fd)) ## v0.6.0 (2023-12-09) ### Features - Improve test coverage (#8) ([`afcfe5a`](https://github.com/aio-libs/aiohappyeyeballs/commit/afcfe5a350acc50a098009617511cd9d21b22f47)) ## v0.5.0 (2023-12-09) ### Features - Improve doc strings (#7) ([`3d5f7fd`](https://github.com/aio-libs/aiohappyeyeballs/commit/3d5f7fde55c4bdd4f5e6cff589ae9b47b279d663)) ## v0.4.0 (2023-12-09) ### Features - Add more tests (#6) ([`4428c07`](https://github.com/aio-libs/aiohappyeyeballs/commit/4428c0714e3e100605f940eb6adee2e86788b4db)) ## v0.3.0 (2023-12-09) ### Features - Optimize for single case (#5) ([`c7d72f3`](https://github.com/aio-libs/aiohappyeyeballs/commit/c7d72f3cdd13149319fc9e4848146d23bddc619b)) ## v0.2.0 (2023-12-09) ### Features - Optimize for single case (#4) ([`d371c46`](https://github.com/aio-libs/aiohappyeyeballs/commit/d371c4687d3b3861a4f0287ac5229853f895807b)) ## v0.1.0 (2023-12-09) ### Features - Init (#2) ([`c9a9099`](https://github.com/aio-libs/aiohappyeyeballs/commit/c9a90994a40d5f49cb37d3e2708db4b4278649ef)) ## v0.0.1 (2023-12-09) ### Bug fixes - Reserve name on pypi (#1) ([`2207f8d`](https://github.com/aio-libs/aiohappyeyeballs/commit/2207f8d361af4ec0b853b07fb743eb957a0b368a)) aiohappyeyeballs-2.6.1/CONTRIBUTING.md000066400000000000000000000074451476416312700173310ustar00rootroot00000000000000# Contributing Contributions are welcome, and they are greatly appreciated! Every little helps, and credit will always be given. You can contribute in many ways: ## Types of Contributions ### Report Bugs Report bugs to [our issue page][gh-issues]. If you are reporting a bug, please include: - Your operating system name and version. - Any details about your local setup that might be helpful in troubleshooting. - Detailed steps to reproduce the bug. ### Fix Bugs Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it. ### Implement Features Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it. ### Write Documentation aiohappyeyeballs could always use more documentation, whether as part of the official aiohappyeyeballs docs, in docstrings, or even on the web in blog posts, articles, and such. ### Submit Feedback The best way to send feedback [our issue page][gh-issues] on GitHub. If you are proposing a feature: - Explain in detail how it would work. - Keep the scope as narrow as possible, to make it easier to implement. - Remember that this is a volunteer-driven project, and that contributions are welcome 😊 ## Get Started! Ready to contribute? Here's how to set yourself up for local development. 1. Fork the repo on GitHub. 2. Clone your fork locally: ```shell $ git clone git@github.com:your_name_here/aiohappyeyeballs.git ``` 3. Install the project dependencies with [Poetry](https://python-poetry.org): ```shell $ poetry install ``` 4. Create a branch for local development: ```shell $ git checkout -b name-of-your-bugfix-or-feature ``` Now you can make your changes locally. 5. When you're done making changes, check that your changes pass our tests: ```shell $ poetry run pytest ``` 6. Linting is done through [pre-commit](https://pre-commit.com). Provided you have the tool installed globally, you can run them all as one-off: ```shell $ pre-commit run -a ``` Or better, install the hooks once and have them run automatically each time you commit: ```shell $ pre-commit install ``` 7. Commit your changes and push your branch to GitHub: ```shell $ git add . $ git commit -m "feat(something): your detailed description of your changes" $ git push origin name-of-your-bugfix-or-feature ``` Note: the commit message should follow [the conventional commits](https://www.conventionalcommits.org). We run [`commitlint` on CI](https://github.com/marketplace/actions/commit-linter) to validate it, and if you've installed pre-commit hooks at the previous step, the message will be checked at commit time. 8. Submit a pull request through the GitHub website or using the GitHub CLI (if you have it installed): ```shell $ gh pr create --fill ``` ## Pull Request Guidelines We like to have the pull request open as soon as possible, that's a great place to discuss any piece of work, even unfinished. You can use draft pull request if it's still a work in progress. Here are a few guidelines to follow: 1. Include tests for feature or bug fixes. 2. Update the documentation for significant features. 3. Ensure tests are passing on CI. ## Tips To run a subset of tests: ```shell $ pytest tests ``` ## Making a new release The deployment should be automated and can be triggered from the Semantic Release workflow in GitHub. The next version will be based on [the commit logs](https://python-semantic-release.readthedocs.io/en/latest/commit-log-parsing.html#commit-log-parsing). This is done by [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/index.html) via a GitHub action. [gh-issues]: https://github.com/aio-libs/aiohappyeyeballs/issues aiohappyeyeballs-2.6.1/LICENSE000066400000000000000000000331601476416312700160760ustar00rootroot00000000000000A. HISTORY OF THE SOFTWARE ========================== Python was created in the early 1990s by Guido van Rossum at Stichting Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands as a successor of a language called ABC. Guido remains Python's principal author, although it includes many contributions from others. In 1995, Guido continued his work on Python at the Corporation for National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) in Reston, Virginia where he released several versions of the software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same year, the PythonLabs team moved to Digital Creations, which became Zope Corporation. In 2001, the Python Software Foundation (PSF, see https://www.python.org/psf/) was formed, a non-profit organization created specifically to own Python-related Intellectual Property. Zope Corporation was a sponsoring member of the PSF. All Python releases are Open Source (see https://opensource.org for the Open Source Definition). Historically, most, but not all, Python releases have also been GPL-compatible; the table below summarizes the various releases. Release Derived Year Owner GPL- from compatible? (1) 0.9.0 thru 1.2 1991-1995 CWI yes 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 1.6 1.5.2 2000 CNRI no 2.0 1.6 2000 BeOpen.com no 1.6.1 1.6 2001 CNRI yes (2) 2.1 2.0+1.6.1 2001 PSF no 2.0.1 2.0+1.6.1 2001 PSF yes 2.1.1 2.1+2.0.1 2001 PSF yes 2.1.2 2.1.1 2002 PSF yes 2.1.3 2.1.2 2002 PSF yes 2.2 and above 2.1.1 2001-now PSF yes Footnotes: (1) GPL-compatible doesn't mean that we're distributing Python under the GPL. All Python licenses, unlike the GPL, let you distribute a modified version without making your changes open source. The GPL-compatible licenses make it possible to combine Python with other software that is released under the GPL; the others don't. (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, because its license has a choice of law clause. According to CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 is "not incompatible" with the GPL. Thanks to the many outside volunteers who have worked under Guido's direction to make these releases possible. B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON =============================================================== Python software and documentation are licensed under the Python Software Foundation License Version 2. Starting with Python 3.8.6, examples, recipes, and other code in the documentation are dual licensed under the PSF License Version 2 and the Zero-Clause BSD license. Some software incorporated into Python is under different licenses. The licenses are listed with code falling under that license. PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 ------------------------------------------- BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 --------------------------------------- 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6.1 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6.1 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 1995-2001 Corporation for National Research Initiatives; All Rights Reserved" are retained in Python 1.6.1 alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with Python 1.6.1 may be located on the internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This Agreement may also be obtained from a proxy server on the internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6.1 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 1.6.1. 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by the federal intellectual property law of the United States, including without limitation the federal copyright law, and, to the extent such U.S. federal law does not apply, by the law of the Commonwealth of Virginia, excluding Virginia's conflict of law provisions. Notwithstanding the foregoing, with regard to derivative works based on Python 1.6.1 that incorporate non-separable material that was previously distributed under the GNU General Public License (GPL), the law of the Commonwealth of Virginia shall govern this License Agreement only as to issues arising under or with respect to Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6.1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 -------------------------------------------------- Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION ---------------------------------------------------------------------- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. aiohappyeyeballs-2.6.1/README.md000066400000000000000000000112261476416312700163470ustar00rootroot00000000000000# aiohappyeyeballs

CI Status Documentation Status Test coverage percentage

Poetry Ruff pre-commit

PyPI Version Supported Python versions License

--- **Documentation**: https://aiohappyeyeballs.readthedocs.io **Source Code**: https://github.com/aio-libs/aiohappyeyeballs --- [Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) ([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html)) ## Use case This library exists to allow connecting with [Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) ([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html)) when you already have a list of addrinfo and not a DNS name. The stdlib version of `loop.create_connection()` will only work when you pass in an unresolved name which is not a good fit when using DNS caching or resolving names via another method such as `zeroconf`. ## Installation Install this via pip (or your favourite package manager): `pip install aiohappyeyeballs` ## License [aiohappyeyeballs is licensed under the same terms as cpython itself.](https://github.com/python/cpython/blob/main/LICENSE) ## Example usage ```python addr_infos = await loop.getaddrinfo("example.org", 80) socket = await start_connection(addr_infos) socket = await start_connection(addr_infos, local_addr_infos=local_addr_infos, happy_eyeballs_delay=0.2) transport, protocol = await loop.create_connection( MyProtocol, sock=socket, ...) # Remove the first address for each family from addr_info pop_addr_infos_interleave(addr_info, 1) # Remove all matching address from addr_info remove_addr_infos(addr_info, "dead::beef::") # Convert a local_addr to local_addr_infos local_addr_infos = addr_to_addr_infos(("127.0.0.1",0)) ``` ## Credits This package contains code from cpython and is licensed under the same terms as cpython itself. This package was created with [Copier](https://copier.readthedocs.io/) and the [browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template) project template. aiohappyeyeballs-2.6.1/commitlint.config.mjs000066400000000000000000000003621476416312700212250ustar00rootroot00000000000000export default { extends: ["@commitlint/config-conventional"], rules: { "header-max-length": [0, "always", Infinity], "body-max-line-length": [0, "always", Infinity], "footer-max-line-length": [0, "always", Infinity], }, }; aiohappyeyeballs-2.6.1/docs/000077500000000000000000000000001476416312700160165ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/docs/Makefile000066400000000000000000000013721476416312700174610ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build .PHONY: help livehtml Makefile # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) # Build, watch and serve docs with live reload livehtml: sphinx-autobuild -b html -c . $(SOURCEDIR) $(BUILDDIR)/html # 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) aiohappyeyeballs-2.6.1/docs/_static/000077500000000000000000000000001476416312700174445ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/docs/_static/.gitkeep000066400000000000000000000000001476416312700210630ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/docs/api_reference.rst000066400000000000000000000017371476416312700213470ustar00rootroot00000000000000API Reference ============= .. autodata:: aiohappyeyeballs.AddrInfoType A tuple representing socket address information. Indexes: **[0]** ``Union[int, socket.AddressFamily]`` The address family, e.g. ``socket.AF_INET`` **[1]** ``Union[int, socket.SocketKind]`` The type of socket, e.g. ``socket.SOCK_STREAM``. **[2]** ``int`` The protocol number, e.g. ``socket.IPPROTO_TCP``. **[3]** ``str`` The canonical name of the address, e.g. ``"www.example.com"``. **[4]** ``Tuple`` The socket address tuple, e.g. ``("127.0.0.1", 443)``. .. autodata:: aiohappyeyeballs.SocketFactoryType A callable that creates a socket from an ``AddrInfoType``. :param AddrInfoType: Address info for creating the socket containing the address family, socket type, protocol, host address, and additional details. :rtype: ``socket.socket`` .. automodule:: aiohappyeyeballs :members: :undoc-members: :show-inheritance: aiohappyeyeballs-2.6.1/docs/changelog.md000066400000000000000000000000601476416312700202630ustar00rootroot00000000000000(changelog)= ```{include} ../CHANGELOG.md ``` aiohappyeyeballs-2.6.1/docs/conf.py000066400000000000000000000012561476416312700173210ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # Project information project = "aiohappyeyeballs" copyright = "2023, J. Nick Koston" author = "J. Nick Koston" release = "2.6.1" # General configuration extensions = [ "myst_parser", "sphinx.ext.autodoc", ] # The suffix of source filenames. source_suffix = [ ".rst", ".md", ] templates_path = [ "_templates", ] exclude_patterns = [ "_build", "Thumbs.db", ".DS_Store", ] # Options for HTML output html_theme = "furo" html_static_path = ["_static"] aiohappyeyeballs-2.6.1/docs/contributing.md000066400000000000000000000000661476416312700210510ustar00rootroot00000000000000(contributing)= ```{include} ../CONTRIBUTING.md ``` aiohappyeyeballs-2.6.1/docs/index.md000066400000000000000000000003731476416312700174520ustar00rootroot00000000000000# Welcome to aiohappyeyeballs documentation! ```{toctree} :caption: Installation & Usage :maxdepth: 2 api_reference installation usage ``` ```{toctree} :caption: Project Info :maxdepth: 2 changelog contributing ``` ```{include} ../README.md ``` aiohappyeyeballs-2.6.1/docs/installation.md000066400000000000000000000004271476416312700210440ustar00rootroot00000000000000(installation)= # Installation The package is published on [PyPI](https://pypi.org/project/aiohappyeyeballs/) and can be installed with `pip` (or any equivalent): ```bash pip install aiohappyeyeballs ``` Next, see the {ref}`section about usage ` to see how to use it. aiohappyeyeballs-2.6.1/docs/make.bat000066400000000000000000000013751476416312700174310ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) if "%1" == "" goto help %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd aiohappyeyeballs-2.6.1/docs/usage.md000066400000000000000000000015171476416312700174500ustar00rootroot00000000000000(usage)= # Usage Assuming that you've followed the {ref}`installations steps `, you're now ready to use this package. Start by importing it: ```python import aiohappyeyeballs addr_infos = await loop.getaddrinfo("example.org", 80) socket = await aiohappyeyeballs.start_connection(addr_infos) socket = await aiohappyeyeballs.start_connection(addr_infos, local_addr_infos=local_addr_infos, happy_eyeballs_delay=0.2) transport, protocol = await loop.create_connection( MyProtocol, sock=socket, ...) # Remove the first address for each family from addr_info aiohappyeyeballs.pop_addr_infos_interleave(addr_info, 1) # Remove all matching address from addr_info aiohappyeyeballs.remove_addr_infos(addr_info, "dead::beef::") # Convert a local_addr to local_addr_infos local_addr_infos = addr_to_addr_infos(("127.0.0.1",0)) ``` aiohappyeyeballs-2.6.1/poetry.lock000066400000000000000000004030011476416312700172600ustar00rootroot00000000000000# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] name = "anyio" version = "4.8.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] name = "babel" version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "backports-tarfile" version = "1.2.0" description = "Backport of CPython tarfile module" optional = false python-versions = ">=3.8" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\"" files = [ {file = "backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34"}, {file = "backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["jaraco.test", "pytest (!=8.0.*)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)"] [[package]] name = "beautifulsoup4" version = "4.13.3" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["docs"] files = [ {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, ] [package.dependencies] soupsieve = ">1.2" typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] chardet = ["chardet"] charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] [[package]] name = "certifi" version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["docs", "test_build"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] pycparser = "*" [[package]] name = "charset-normalizer" version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["docs", "test_build"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" groups = ["docs"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] markers = {dev = "sys_platform == \"win32\""} [[package]] name = "coverage" version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, ] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] [[package]] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "docutils" version = "0.21.2" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=3.9" groups = ["docs", "test_build"] files = [ {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, ] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev", "docs"] markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "furo" version = "2024.8.6" description = "A clean customisable Sphinx documentation theme." optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" sphinx = ">=6.0,<9.0" sphinx-basic-ng = ">=1.0.0.beta2" [[package]] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" groups = ["docs"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] [[package]] name = "id" version = "1.5.0" description = "A tool for generating OIDC identities" optional = false python-versions = ">=3.8" groups = ["test_build"] files = [ {file = "id-1.5.0-py3-none-any.whl", hash = "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658"}, {file = "id-1.5.0.tar.gz", hash = "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d"}, ] [package.dependencies] requests = "*" [package.extras] dev = ["build", "bump (>=1.3.2)", "id[lint,test]"] lint = ["bandit", "interrogate", "mypy", "ruff (<0.8.2)", "types-requests"] test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["docs", "test_build"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["docs"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] [[package]] name = "importlib-metadata" version = "8.6.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" groups = ["docs", "test_build"] files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ] markers = {docs = "python_version < \"3.10\"", test_build = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\" or python_version < \"3.10\""} [package.dependencies] zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] test = ["flufl.flake8", "importlib_resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "jaraco-classes" version = "3.4.0" description = "Utility functions for Python class constructs" optional = false python-versions = ">=3.8" groups = ["test_build"] markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, ] [package.dependencies] more-itertools = "*" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-context" version = "6.0.1" description = "Useful decorators and context managers" optional = false python-versions = ">=3.8" groups = ["test_build"] markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4"}, {file = "jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3"}, ] [package.dependencies] "backports.tarfile" = {version = "*", markers = "python_version < \"3.12\""} [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "jaraco-functools" version = "4.1.0" description = "Functools like those found in stdlib" optional = false python-versions = ">=3.8" groups = ["test_build"] markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"}, {file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"}, ] [package.dependencies] more-itertools = "*" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["jaraco.classes", "pytest (>=6,!=8.1.*)"] type = ["pytest-mypy"] [[package]] name = "jeepney" version = "0.9.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" files = [ {file = "jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683"}, {file = "jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732"}, ] [package.extras] test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] trio = ["trio"] [[package]] name = "jinja2" version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" groups = ["docs"] files = [ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "keyring" version = "25.6.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.9" groups = ["test_build"] markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd"}, {file = "keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66"}, ] [package.dependencies] importlib_metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} "jaraco.classes" = "*" "jaraco.context" = "*" "jaraco.functools" = "*" jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] completion = ["shtab (>=1.1.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["pyfakefs", "pytest (>=6,!=8.1.*)"] type = ["pygobject-stubs", "pytest-mypy", "shtab", "types-pywin32"] [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["docs", "test_build"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, ] [package.dependencies] mdurl = ">=0.1,<1.0" [package.extras] benchmarking = ["psutil", "pytest", "pytest-benchmark"] code-style = ["pre-commit (>=3.0,<4.0)"] compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] linkify = ["linkify-it-py (>=1,<3)"] plugins = ["mdit-py-plugins"] profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] [[package]] name = "mdit-py-plugins" version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636"}, {file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5"}, ] [package.dependencies] markdown-it-py = ">=1.0.0,<4.0.0" [package.extras] code-style = ["pre-commit"] rtd = ["myst-parser", "sphinx-book-theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["docs", "test_build"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] [[package]] name = "more-itertools" version = "10.6.0" description = "More routines for operating on iterables, beyond itertools" optional = false python-versions = ">=3.9" groups = ["test_build"] markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\"" files = [ {file = "more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b"}, {file = "more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89"}, ] [[package]] name = "myst-parser" version = "3.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, ] [package.dependencies] docutils = ">=0.18,<0.22" jinja2 = "*" markdown-it-py = ">=3.0,<4.0" mdit-py-plugins = ">=0.4,<1.0" pyyaml = "*" sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] name = "nh3" version = "0.2.21" description = "Python binding to Ammonia HTML sanitizer Rust crate" optional = false python-versions = ">=3.8" groups = ["test_build"] files = [ {file = "nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286"}, {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde"}, {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243"}, {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b"}, {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251"}, {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b"}, {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9"}, {file = "nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d"}, {file = "nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82"}, {file = "nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585"}, {file = "nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293"}, {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431"}, {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa"}, {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1"}, {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283"}, {file = "nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a"}, {file = "nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629"}, {file = "nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e"}, ] [[package]] name = "packaging" version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev", "docs", "test_build"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["docs", "test_build"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=1.5,<2" tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.25.3" description = "Pytest support for asyncio" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"}, {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"}, ] [package.dependencies] pytest = ">=8.2,<9" [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pywin32-ctypes" version = "0.2.3" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" optional = false python-versions = ">=3.6" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, ] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "readme-renderer" version = "44.0" description = "readme_renderer is a library for rendering readme descriptions for Warehouse" optional = false python-versions = ">=3.9" groups = ["test_build"] files = [ {file = "readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151"}, {file = "readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1"}, ] [package.dependencies] docutils = ">=0.21.2" nh3 = ">=0.2.14" Pygments = ">=2.5.1" [package.extras] md = ["cmarkgfm (>=0.8.0)"] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["docs", "test_build"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "requests-toolbelt" version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" groups = ["test_build"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, ] [package.dependencies] requests = ">=2.0.1,<3.0.0" [[package]] name = "rfc3986" version = "2.0.0" description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" groups = ["test_build"] files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, ] [package.extras] idna2008 = ["idna"] [[package]] name = "rich" version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" groups = ["test_build"] files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" groups = ["test_build"] markers = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and sys_platform == \"linux\"" files = [ {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, ] [package.dependencies] cryptography = ">=2.0" jeepney = ">=0.6" [[package]] name = "sniffio" version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["docs"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" groups = ["docs"] files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] [[package]] name = "soupsieve" version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" groups = ["docs"] files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] name = "sphinx" version = "7.4.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, ] [package.dependencies] alabaster = ">=0.7.14,<0.8.0" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} docutils = ">=0.20,<0.22" imagesize = ">=1.3" importlib-metadata = {version = ">=6.0", markers = "python_version < \"3.10\""} Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" snowballstemmer = ">=2.2" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "importlib-metadata (>=6.0)", "mypy (==1.10.1)", "pytest (>=6.0)", "ruff (==0.5.2)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-docutils (==0.21.0.20240711)", "types-requests (>=2.30.0)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] [[package]] name = "sphinx-autobuild" version = "2024.10.3" description = "Rebuild Sphinx documentation on changes, with hot reloading in the browser." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa"}, {file = "sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1"}, ] [package.dependencies] colorama = ">=0.4.6" sphinx = "*" starlette = ">=0.35" uvicorn = ">=0.25" watchfiles = ">=0.20" websockets = ">=11" [package.extras] test = ["httpx", "pytest (>=6)"] [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" description = "A modern skeleton for Sphinx themes." optional = false python-versions = ">=3.7" groups = ["docs"] files = [ {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, ] [package.dependencies] sphinx = ">=4.0" [package.extras] docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" groups = ["docs"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] [package.extras] test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["defusedxml (>=0.7.1)", "pytest"] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, ] [package.extras] lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "starlette" version = "0.46.0" description = "The little ASGI library that shines." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "starlette-0.46.0-py3-none-any.whl", hash = "sha256:913f0798bd90ba90a9156383bcf1350a17d6259451d0d8ee27fc0cf2db609038"}, {file = "starlette-0.46.0.tar.gz", hash = "sha256:b359e4567456b28d473d0193f34c0de0ed49710d75ef183a74a5ce0499324f50"}, ] [package.dependencies] anyio = ">=3.6.2,<5" typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] [[package]] name = "tomli" version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev", "docs"] files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] markers = {dev = "python_full_version <= \"3.11.0a6\"", docs = "python_version < \"3.11\""} [[package]] name = "twine" version = "6.1.0" description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.8" groups = ["test_build"] files = [ {file = "twine-6.1.0-py3-none-any.whl", hash = "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384"}, {file = "twine-6.1.0.tar.gz", hash = "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd"}, ] [package.dependencies] id = "*" importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} keyring = {version = ">=15.1", markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\""} packaging = ">=24.0" readme-renderer = ">=35.0" requests = ">=2.20" requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" rfc3986 = ">=1.4.0" rich = ">=12.0.0" urllib3 = ">=1.26.0" [package.extras] keyring = ["keyring (>=15.1)"] [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["docs", "test_build"] files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] markers = {test_build = "python_version < \"3.11\""} [[package]] name = "urllib3" version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["docs", "test_build"] files = [ {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" version = "0.34.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, ] [package.dependencies] click = ">=7.0" h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "watchfiles" version = "1.0.4" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "watchfiles-1.0.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ba5bb3073d9db37c64520681dd2650f8bd40902d991e7b4cfaeece3e32561d08"}, {file = "watchfiles-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f25d0ba0fe2b6d2c921cf587b2bf4c451860086534f40c384329fb96e2044d1"}, {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47eb32ef8c729dbc4f4273baece89398a4d4b5d21a1493efea77a17059f4df8a"}, {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:076f293100db3b0b634514aa0d294b941daa85fc777f9c698adb1009e5aca0b1"}, {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1eacd91daeb5158c598fe22d7ce66d60878b6294a86477a4715154990394c9b3"}, {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13c2ce7b72026cfbca120d652f02c7750f33b4c9395d79c9790b27f014c8a5a2"}, {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90192cdc15ab7254caa7765a98132a5a41471cf739513cc9bcf7d2ffcc0ec7b2"}, {file = "watchfiles-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278aaa395f405972e9f523bd786ed59dfb61e4b827856be46a42130605fd0899"}, {file = "watchfiles-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a462490e75e466edbb9fc4cd679b62187153b3ba804868452ef0577ec958f5ff"}, {file = "watchfiles-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8d0d0630930f5cd5af929040e0778cf676a46775753e442a3f60511f2409f48f"}, {file = "watchfiles-1.0.4-cp310-cp310-win32.whl", hash = "sha256:cc27a65069bcabac4552f34fd2dce923ce3fcde0721a16e4fb1b466d63ec831f"}, {file = "watchfiles-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:8b1f135238e75d075359cf506b27bf3f4ca12029c47d3e769d8593a2024ce161"}, {file = "watchfiles-1.0.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2a9f93f8439639dc244c4d2902abe35b0279102bca7bbcf119af964f51d53c19"}, {file = "watchfiles-1.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eea33ad8c418847dd296e61eb683cae1c63329b6d854aefcd412e12d94ee235"}, {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31f1a379c9dcbb3f09cf6be1b7e83b67c0e9faabed0471556d9438a4a4e14202"}, {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab594e75644421ae0a2484554832ca5895f8cab5ab62de30a1a57db460ce06c6"}, {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc2eb5d14a8e0d5df7b36288979176fbb39672d45184fc4b1c004d7c3ce29317"}, {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f68d8e9d5a321163ddacebe97091000955a1b74cd43724e346056030b0bacee"}, {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9ce064e81fe79faa925ff03b9f4c1a98b0bbb4a1b8c1b015afa93030cb21a49"}, {file = "watchfiles-1.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b77d5622ac5cc91d21ae9c2b284b5d5c51085a0bdb7b518dba263d0af006132c"}, {file = "watchfiles-1.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1941b4e39de9b38b868a69b911df5e89dc43767feeda667b40ae032522b9b5f1"}, {file = "watchfiles-1.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f8c4998506241dedf59613082d1c18b836e26ef2a4caecad0ec41e2a15e4226"}, {file = "watchfiles-1.0.4-cp311-cp311-win32.whl", hash = "sha256:4ebbeca9360c830766b9f0df3640b791be569d988f4be6c06d6fae41f187f105"}, {file = "watchfiles-1.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:05d341c71f3d7098920f8551d4df47f7b57ac5b8dad56558064c3431bdfc0b74"}, {file = "watchfiles-1.0.4-cp311-cp311-win_arm64.whl", hash = "sha256:32b026a6ab64245b584acf4931fe21842374da82372d5c039cba6bf99ef722f3"}, {file = "watchfiles-1.0.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:229e6ec880eca20e0ba2f7e2249c85bae1999d330161f45c78d160832e026ee2"}, {file = "watchfiles-1.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5717021b199e8353782dce03bd8a8f64438832b84e2885c4a645f9723bf656d9"}, {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0799ae68dfa95136dde7c472525700bd48777875a4abb2ee454e3ab18e9fc712"}, {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43b168bba889886b62edb0397cab5b6490ffb656ee2fcb22dec8bfeb371a9e12"}, {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb2c46e275fbb9f0c92e7654b231543c7bbfa1df07cdc4b99fa73bedfde5c844"}, {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:857f5fc3aa027ff5e57047da93f96e908a35fe602d24f5e5d8ce64bf1f2fc733"}, {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55ccfd27c497b228581e2838d4386301227fc0cb47f5a12923ec2fe4f97b95af"}, {file = "watchfiles-1.0.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c11ea22304d17d4385067588123658e9f23159225a27b983f343fcffc3e796a"}, {file = "watchfiles-1.0.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:74cb3ca19a740be4caa18f238298b9d472c850f7b2ed89f396c00a4c97e2d9ff"}, {file = "watchfiles-1.0.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c7cce76c138a91e720d1df54014a047e680b652336e1b73b8e3ff3158e05061e"}, {file = "watchfiles-1.0.4-cp312-cp312-win32.whl", hash = "sha256:b045c800d55bc7e2cadd47f45a97c7b29f70f08a7c2fa13241905010a5493f94"}, {file = "watchfiles-1.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:c2acfa49dd0ad0bf2a9c0bb9a985af02e89345a7189be1efc6baa085e0f72d7c"}, {file = "watchfiles-1.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:22bb55a7c9e564e763ea06c7acea24fc5d2ee5dfc5dafc5cfbedfe58505e9f90"}, {file = "watchfiles-1.0.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:8012bd820c380c3d3db8435e8cf7592260257b378b649154a7948a663b5f84e9"}, {file = "watchfiles-1.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa216f87594f951c17511efe5912808dfcc4befa464ab17c98d387830ce07b60"}, {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c9953cf85529c05b24705639ffa390f78c26449e15ec34d5339e8108c7c407"}, {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cf684aa9bba4cd95ecb62c822a56de54e3ae0598c1a7f2065d51e24637a3c5d"}, {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f44a39aee3cbb9b825285ff979ab887a25c5d336e5ec3574f1506a4671556a8d"}, {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38320582736922be8c865d46520c043bff350956dfc9fbaee3b2df4e1740a4b"}, {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39f4914548b818540ef21fd22447a63e7be6e24b43a70f7642d21f1e73371590"}, {file = "watchfiles-1.0.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f12969a3765909cf5dc1e50b2436eb2c0e676a3c75773ab8cc3aa6175c16e902"}, {file = "watchfiles-1.0.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0986902677a1a5e6212d0c49b319aad9cc48da4bd967f86a11bde96ad9676ca1"}, {file = "watchfiles-1.0.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:308ac265c56f936636e3b0e3f59e059a40003c655228c131e1ad439957592303"}, {file = "watchfiles-1.0.4-cp313-cp313-win32.whl", hash = "sha256:aee397456a29b492c20fda2d8961e1ffb266223625346ace14e4b6d861ba9c80"}, {file = "watchfiles-1.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:d6097538b0ae5c1b88c3b55afa245a66793a8fec7ada6755322e465fb1a0e8cc"}, {file = "watchfiles-1.0.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d3452c1ec703aa1c61e15dfe9d482543e4145e7c45a6b8566978fbb044265a21"}, {file = "watchfiles-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7b75fee5a16826cf5c46fe1c63116e4a156924d668c38b013e6276f2582230f0"}, {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e997802d78cdb02623b5941830ab06f8860038faf344f0d288d325cc9c5d2ff"}, {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0611d244ce94d83f5b9aff441ad196c6e21b55f77f3c47608dcf651efe54c4a"}, {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9745a4210b59e218ce64c91deb599ae8775c8a9da4e95fb2ee6fe745fc87d01a"}, {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4810ea2ae622add560f4aa50c92fef975e475f7ac4900ce5ff5547b2434642d8"}, {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:740d103cd01458f22462dedeb5a3382b7f2c57d07ff033fbc9465919e5e1d0f3"}, {file = "watchfiles-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbd912a61543a36aef85e34f212e5d2486e7c53ebfdb70d1e0b060cc50dd0bf"}, {file = "watchfiles-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0bc80d91ddaf95f70258cf78c471246846c1986bcc5fd33ccc4a1a67fcb40f9a"}, {file = "watchfiles-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab0311bb2ffcd9f74b6c9de2dda1612c13c84b996d032cd74799adb656af4e8b"}, {file = "watchfiles-1.0.4-cp39-cp39-win32.whl", hash = "sha256:02a526ee5b5a09e8168314c905fc545c9bc46509896ed282aeb5a8ba9bd6ca27"}, {file = "watchfiles-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:a5ae5706058b27c74bac987d615105da17724172d5aaacc6c362a40599b6de43"}, {file = "watchfiles-1.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdcc92daeae268de1acf5b7befcd6cfffd9a047098199056c72e4623f531de18"}, {file = "watchfiles-1.0.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8d3d9203705b5797f0af7e7e5baa17c8588030aaadb7f6a86107b7247303817"}, {file = "watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdef5a1be32d0b07dcea3318a0be95d42c98ece24177820226b56276e06b63b0"}, {file = "watchfiles-1.0.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:342622287b5604ddf0ed2d085f3a589099c9ae8b7331df3ae9845571586c4f3d"}, {file = "watchfiles-1.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9fe37a2de80aa785d340f2980276b17ef697ab8db6019b07ee4fd28a8359d2f3"}, {file = "watchfiles-1.0.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9d1ef56b56ed7e8f312c934436dea93bfa3e7368adfcf3df4c0da6d4de959a1e"}, {file = "watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b42cac65beae3a362629950c444077d1b44f1790ea2772beaea95451c086bb"}, {file = "watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42"}, {file = "watchfiles-1.0.4.tar.gz", hash = "sha256:6ba473efd11062d73e4f00c2b730255f9c1bdd73cd5f9fe5b5da8dbd4a717205"}, ] [package.dependencies] anyio = ">=3.0.0" [[package]] name = "websockets" version = "15.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" groups = ["docs"] files = [ {file = "websockets-15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5e6ee18a53dd5743e6155b8ff7e8e477c25b29b440f87f65be8165275c87fef0"}, {file = "websockets-15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ee06405ea2e67366a661ed313e14cf2a86e84142a3462852eb96348f7219cee3"}, {file = "websockets-15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8711682a629bbcaf492f5e0af72d378e976ea1d127a2d47584fa1c2c080b436b"}, {file = "websockets-15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94c4a9b01eede952442c088d415861b0cf2053cbd696b863f6d5022d4e4e2453"}, {file = "websockets-15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45535fead66e873f411c1d3cf0d3e175e66f4dd83c4f59d707d5b3e4c56541c4"}, {file = "websockets-15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e389efe46ccb25a1f93d08c7a74e8123a2517f7b7458f043bd7529d1a63ffeb"}, {file = "websockets-15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:67a04754d121ea5ca39ddedc3f77071651fb5b0bc6b973c71c515415b44ed9c5"}, {file = "websockets-15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bd66b4865c8b853b8cca7379afb692fc7f52cf898786537dfb5e5e2d64f0a47f"}, {file = "websockets-15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a4cc73a6ae0a6751b76e69cece9d0311f054da9b22df6a12f2c53111735657c8"}, {file = "websockets-15.0-cp310-cp310-win32.whl", hash = "sha256:89da58e4005e153b03fe8b8794330e3f6a9774ee9e1c3bd5bc52eb098c3b0c4f"}, {file = "websockets-15.0-cp310-cp310-win_amd64.whl", hash = "sha256:4ff380aabd7a74a42a760ee76c68826a8f417ceb6ea415bd574a035a111fd133"}, {file = "websockets-15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd24c4d256558429aeeb8d6c24ebad4e982ac52c50bc3670ae8646c181263965"}, {file = "websockets-15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f83eca8cbfd168e424dfa3b3b5c955d6c281e8fc09feb9d870886ff8d03683c7"}, {file = "websockets-15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4095a1f2093002c2208becf6f9a178b336b7572512ee0a1179731acb7788e8ad"}, {file = "websockets-15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb915101dfbf318486364ce85662bb7b020840f68138014972c08331458d41f3"}, {file = "websockets-15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45d464622314973d78f364689d5dbb9144e559f93dca11b11af3f2480b5034e1"}, {file = "websockets-15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace960769d60037ca9625b4c578a6f28a14301bd2a1ff13bb00e824ac9f73e55"}, {file = "websockets-15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7cd4b1015d2f60dfe539ee6c95bc968d5d5fad92ab01bb5501a77393da4f596"}, {file = "websockets-15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f7290295794b5dec470867c7baa4a14182b9732603fd0caf2a5bf1dc3ccabf3"}, {file = "websockets-15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3abd670ca7ce230d5a624fd3d55e055215d8d9b723adee0a348352f5d8d12ff4"}, {file = "websockets-15.0-cp311-cp311-win32.whl", hash = "sha256:110a847085246ab8d4d119632145224d6b49e406c64f1bbeed45c6f05097b680"}, {file = "websockets-15.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7bbbe2cd6ed80aceef2a14e9f1c1b61683194c216472ed5ff33b700e784e37"}, {file = "websockets-15.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cccc18077acd34c8072578394ec79563664b1c205f7a86a62e94fafc7b59001f"}, {file = "websockets-15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4c22992e24f12de340ca5f824121a5b3e1a37ad4360b4e1aaf15e9d1c42582d"}, {file = "websockets-15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1206432cc6c644f6fc03374b264c5ff805d980311563202ed7fef91a38906276"}, {file = "websockets-15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3cc75ef3e17490042c47e0523aee1bcc4eacd2482796107fd59dd1100a44bc"}, {file = "websockets-15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b89504227a5311610e4be16071465885a0a3d6b0e82e305ef46d9b064ce5fb72"}, {file = "websockets-15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56e3efe356416bc67a8e093607315951d76910f03d2b3ad49c4ade9207bf710d"}, {file = "websockets-15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f2205cdb444a42a7919690238fb5979a05439b9dbb73dd47c863d39640d85ab"}, {file = "websockets-15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aea01f40995fa0945c020228ab919b8dfc93fc8a9f2d3d705ab5b793f32d9e99"}, {file = "websockets-15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9f8e33747b1332db11cf7fcf4a9512bef9748cb5eb4d3f7fbc8c30d75dc6ffc"}, {file = "websockets-15.0-cp312-cp312-win32.whl", hash = "sha256:32e02a2d83f4954aa8c17e03fe8ec6962432c39aca4be7e8ee346b05a3476904"}, {file = "websockets-15.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc02b159b65c05f2ed9ec176b715b66918a674bd4daed48a9a7a590dd4be1aa"}, {file = "websockets-15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d2244d8ab24374bed366f9ff206e2619345f9cd7fe79aad5225f53faac28b6b1"}, {file = "websockets-15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a302241fbe825a3e4fe07666a2ab513edfdc6d43ce24b79691b45115273b5e7"}, {file = "websockets-15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10552fed076757a70ba2c18edcbc601c7637b30cdfe8c24b65171e824c7d6081"}, {file = "websockets-15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c53f97032b87a406044a1c33d1e9290cc38b117a8062e8a8b285175d7e2f99c9"}, {file = "websockets-15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1caf951110ca757b8ad9c4974f5cac7b8413004d2f29707e4d03a65d54cedf2b"}, {file = "websockets-15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf1ab71f9f23b0a1d52ec1682a3907e0c208c12fef9c3e99d2b80166b17905f"}, {file = "websockets-15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bfcd3acc1a81f106abac6afd42327d2cf1e77ec905ae11dc1d9142a006a496b6"}, {file = "websockets-15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8c5c8e1bac05ef3c23722e591ef4f688f528235e2480f157a9cfe0a19081375"}, {file = "websockets-15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:86bfb52a9cfbcc09aba2b71388b0a20ea5c52b6517c0b2e316222435a8cdab72"}, {file = "websockets-15.0-cp313-cp313-win32.whl", hash = "sha256:26ba70fed190708551c19a360f9d7eca8e8c0f615d19a574292b7229e0ae324c"}, {file = "websockets-15.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae721bcc8e69846af00b7a77a220614d9b2ec57d25017a6bbde3a99473e41ce8"}, {file = "websockets-15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c348abc5924caa02a62896300e32ea80a81521f91d6db2e853e6b1994017c9f6"}, {file = "websockets-15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5294fcb410ed0a45d5d1cdedc4e51a60aab5b2b3193999028ea94afc2f554b05"}, {file = "websockets-15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c24ba103ecf45861e2e1f933d40b2d93f5d52d8228870c3e7bf1299cd1cb8ff1"}, {file = "websockets-15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc8821a03bcfb36e4e4705316f6b66af28450357af8a575dc8f4b09bf02a3dee"}, {file = "websockets-15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc5ae23ada6515f31604f700009e2df90b091b67d463a8401c1d8a37f76c1d7"}, {file = "websockets-15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ac67b542505186b3bbdaffbc303292e1ee9c8729e5d5df243c1f20f4bb9057e"}, {file = "websockets-15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c86dc2068f1c5ca2065aca34f257bbf4f78caf566eb230f692ad347da191f0a1"}, {file = "websockets-15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:30cff3ef329682b6182c01c568f551481774c476722020b8f7d0daacbed07a17"}, {file = "websockets-15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:98dcf978d4c6048965d1762abd534c9d53bae981a035bfe486690ba11f49bbbb"}, {file = "websockets-15.0-cp39-cp39-win32.whl", hash = "sha256:37d66646f929ae7c22c79bc73ec4074d6db45e6384500ee3e0d476daf55482a9"}, {file = "websockets-15.0-cp39-cp39-win_amd64.whl", hash = "sha256:24d5333a9b2343330f0f4eb88546e2c32a7f5c280f8dd7d3cc079beb0901781b"}, {file = "websockets-15.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b499caef4bca9cbd0bd23cd3386f5113ee7378094a3cb613a2fa543260fe9506"}, {file = "websockets-15.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:17f2854c6bd9ee008c4b270f7010fe2da6c16eac5724a175e75010aacd905b31"}, {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89f72524033abbfde880ad338fd3c2c16e31ae232323ebdfbc745cbb1b3dcc03"}, {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1657a9eecb29d7838e3b415458cc494e6d1b194f7ac73a34aa55c6fb6c72d1f3"}, {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e413352a921f5ad5d66f9e2869b977e88d5103fc528b6deb8423028a2befd842"}, {file = "websockets-15.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8561c48b0090993e3b2a54db480cab1d23eb2c5735067213bb90f402806339f5"}, {file = "websockets-15.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:190bc6ef8690cd88232a038d1b15714c258f79653abad62f7048249b09438af3"}, {file = "websockets-15.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:327adab7671f3726b0ba69be9e865bba23b37a605b585e65895c428f6e47e766"}, {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd8ef197c87afe0a9009f7a28b5dc613bfc585d329f80b7af404e766aa9e8c7"}, {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789c43bf4a10cd067c24c321238e800b8b2716c863ddb2294d2fed886fa5a689"}, {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7394c0b7d460569c9285fa089a429f58465db930012566c03046f9e3ab0ed181"}, {file = "websockets-15.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ea4f210422b912ebe58ef0ad33088bc8e5c5ff9655a8822500690abc3b1232d"}, {file = "websockets-15.0-py3-none-any.whl", hash = "sha256:51ffd53c53c4442415b613497a34ba0aa7b99ac07f1e4a62db5dcd640ae6c3c3"}, {file = "websockets-15.0.tar.gz", hash = "sha256:ca36151289a15b39d8d683fd8b7abbe26fc50be311066c5f8dcf3cb8cee107ab"}, ] [[package]] name = "zipp" version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["docs", "test_build"] files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] markers = {docs = "python_version < \"3.10\"", test_build = "(platform_machine != \"ppc64le\" and platform_machine != \"s390x\") and python_version < \"3.12\" or python_version < \"3.10\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9" content-hash = "3da0e75a391f64c10736a8a5eb144f98a68c7cfb1bd9bd3c25333c06f2e96365" aiohappyeyeballs-2.6.1/pyproject.toml000066400000000000000000000100441476416312700200010ustar00rootroot00000000000000[project] name = "aiohappyeyeballs" version = "2.6.1" description = "Happy Eyeballs for asyncio" authors = [{ name = "J. Nick Koston", email = "nick@koston.org" }] readme = "README.md" requires-python = ">=3.9" dynamic = ["dependencies", "optional-dependencies"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries", "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", "License :: OSI Approved :: Python Software Foundation License" ] [project.urls] "Repository" = "https://github.com/aio-libs/aiohappyeyeballs" "Documentation" = "https://aiohappyeyeballs.readthedocs.io" "Bug Tracker" = "https://github.com/aio-libs/aiohappyeyeballs/issues" "Changelog" = "https://github.com/aio-libs/aiohappyeyeballs/blob/main/CHANGELOG.md" [tool.poetry] license = "PSF-2.0" packages = [ { include = "aiohappyeyeballs", from = "src" }, { include = "tests", format = "sdist" }, ] [tool.poetry.dependencies] python = ">=3.9" [tool.poetry.group.dev.dependencies] pytest = ">=7,<9" pytest-cov = ">=3,<7" pytest-asyncio = ">=0.23.2,<0.26.0" [tool.poetry.group.docs] optional = true [tool.poetry.group.docs.dependencies] myst-parser = ">=0.16" sphinx = ">=4.0" furo = ">=2023.5.20" sphinx-autobuild = ">=2021.3.14" [tool.poetry.group.test_build.dependencies] twine = ">=4.0.2,<7.0.0" [tool.semantic_release] version_toml = ["pyproject.toml:project.version"] version_variables = [ "src/aiohappyeyeballs/__init__.py:__version__", "docs/conf.py:release", ] build_command = "pip install poetry && poetry build" [tool.semantic_release.changelog] exclude_commit_patterns = [ "chore*", "ci*", ] [tool.semantic_release.changelog.environment] keep_trailing_newline = true [tool.semantic_release.branches.main] match = "main" [tool.semantic_release.branches.noop] match = "(?!main$)" prerelease = true [tool.pytest.ini_options] addopts = "-v -Wdefault --cov=aiohappyeyeballs --cov-report=term-missing:skip-covered" pythonpath = ["src"] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" [tool.coverage.run] branch = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@overload", "if TYPE_CHECKING", "raise NotImplementedError", 'if __name__ == "__main__":', ] [tool.ruff] target-version = "py38" line-length = 88 ignore = [ "D203", # 1 blank line required before class docstring "D212", # Multi-line docstring summary should start at the first line "D100", # Missing docstring in public module "D104", # Missing docstring in public package "D107", # Missing docstring in `__init__` "D401", # First line of docstring should be in imperative mood ] select = [ "B", # flake8-bugbear "D", # flake8-docstrings "C4", # flake8-comprehensions "S", # flake8-bandit "F", # pyflake "E", # pycodestyle "W", # pycodestyle "UP", # pyupgrade "I", # isort "RUF", # ruff specific ] [tool.ruff.per-file-ignores] "tests/**/*" = [ "D100", "D101", "D102", "D103", "D104", "S101", ] "setup.py" = ["D100"] "conftest.py" = ["D100"] "docs/conf.py" = ["D100"] [tool.ruff.isort] known-first-party = ["aiohappyeyeballs", "tests"] [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true mypy_path = "src/" no_implicit_optional = true show_error_codes = true warn_unreachable = true warn_unused_ignores = true exclude = [ 'docs/.*', 'setup.py', ] [[tool.mypy.overrides]] module = "tests.*" allow_untyped_defs = true [[tool.mypy.overrides]] module = "docs.*" ignore_errors = true [build-system] requires = ["poetry-core>=2.0.0"] build-backend = "poetry.core.masonry.api" [tool.codespell] skip = '*.lock' aiohappyeyeballs-2.6.1/renovate.json000066400000000000000000000001011476416312700175740ustar00rootroot00000000000000{ "extends": ["github>browniebroke/renovate-configs:python"] } aiohappyeyeballs-2.6.1/setup.py000066400000000000000000000003661476416312700166050ustar00rootroot00000000000000#!/usr/bin/env python # This is a shim to allow GitHub to detect the package, build is done with poetry # Taken from https://github.com/Textualize/rich import setuptools if __name__ == "__main__": setuptools.setup(name="aiohappyeyeballs") aiohappyeyeballs-2.6.1/src/000077500000000000000000000000001476416312700156555ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/000077500000000000000000000000001476416312700212105ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/__init__.py000066400000000000000000000005511476416312700233220ustar00rootroot00000000000000__version__ = "2.6.1" from .impl import start_connection from .types import AddrInfoType, SocketFactoryType from .utils import addr_to_addr_infos, pop_addr_infos_interleave, remove_addr_infos __all__ = ( "AddrInfoType", "SocketFactoryType", "addr_to_addr_infos", "pop_addr_infos_interleave", "remove_addr_infos", "start_connection", ) aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/_staggered.py000066400000000000000000000153641476416312700236770ustar00rootroot00000000000000import asyncio import contextlib # PY3.9: Import Callable from typing until we drop Python 3.9 support # https://github.com/python/cpython/issues/87131 from typing import ( TYPE_CHECKING, Any, Awaitable, Callable, Iterable, List, Optional, Set, Tuple, TypeVar, Union, ) _T = TypeVar("_T") RE_RAISE_EXCEPTIONS = (SystemExit, KeyboardInterrupt) def _set_result(wait_next: "asyncio.Future[None]") -> None: """Set the result of a future if it is not already done.""" if not wait_next.done(): wait_next.set_result(None) async def _wait_one( futures: "Iterable[asyncio.Future[Any]]", loop: asyncio.AbstractEventLoop, ) -> _T: """Wait for the first future to complete.""" wait_next = loop.create_future() def _on_completion(fut: "asyncio.Future[Any]") -> None: if not wait_next.done(): wait_next.set_result(fut) for f in futures: f.add_done_callback(_on_completion) try: return await wait_next finally: for f in futures: f.remove_done_callback(_on_completion) async def staggered_race( coro_fns: Iterable[Callable[[], Awaitable[_T]]], delay: Optional[float], *, loop: Optional[asyncio.AbstractEventLoop] = None, ) -> Tuple[Optional[_T], Optional[int], List[Optional[BaseException]]]: """ Run coroutines with staggered start times and take the first to finish. This method takes an iterable of coroutine functions. The first one is started immediately. From then on, whenever the immediately preceding one fails (raises an exception), or when *delay* seconds has passed, the next coroutine is started. This continues until one of the coroutines complete successfully, in which case all others are cancelled, or until all coroutines fail. The coroutines provided should be well-behaved in the following way: * They should only ``return`` if completed successfully. * They should always raise an exception if they did not complete successfully. In particular, if they handle cancellation, they should probably reraise, like this:: try: # do work except asyncio.CancelledError: # undo partially completed work raise Args: ---- coro_fns: an iterable of coroutine functions, i.e. callables that return a coroutine object when called. Use ``functools.partial`` or lambdas to pass arguments. delay: amount of time, in seconds, between starting coroutines. If ``None``, the coroutines will run sequentially. loop: the event loop to use. If ``None``, the running loop is used. Returns: ------- tuple *(winner_result, winner_index, exceptions)* where - *winner_result*: the result of the winning coroutine, or ``None`` if no coroutines won. - *winner_index*: the index of the winning coroutine in ``coro_fns``, or ``None`` if no coroutines won. If the winning coroutine may return None on success, *winner_index* can be used to definitively determine whether any coroutine won. - *exceptions*: list of exceptions returned by the coroutines. ``len(exceptions)`` is equal to the number of coroutines actually started, and the order is the same as in ``coro_fns``. The winning coroutine's entry is ``None``. """ loop = loop or asyncio.get_running_loop() exceptions: List[Optional[BaseException]] = [] tasks: Set[asyncio.Task[Optional[Tuple[_T, int]]]] = set() async def run_one_coro( coro_fn: Callable[[], Awaitable[_T]], this_index: int, start_next: "asyncio.Future[None]", ) -> Optional[Tuple[_T, int]]: """ Run a single coroutine. If the coroutine fails, set the exception in the exceptions list and start the next coroutine by setting the result of the start_next. If the coroutine succeeds, return the result and the index of the coroutine in the coro_fns list. If SystemExit or KeyboardInterrupt is raised, re-raise it. """ try: result = await coro_fn() except RE_RAISE_EXCEPTIONS: raise except BaseException as e: exceptions[this_index] = e _set_result(start_next) # Kickstart the next coroutine return None return result, this_index start_next_timer: Optional[asyncio.TimerHandle] = None start_next: Optional[asyncio.Future[None]] task: asyncio.Task[Optional[Tuple[_T, int]]] done: Union[asyncio.Future[None], asyncio.Task[Optional[Tuple[_T, int]]]] coro_iter = iter(coro_fns) this_index = -1 try: while True: if coro_fn := next(coro_iter, None): this_index += 1 exceptions.append(None) start_next = loop.create_future() task = loop.create_task(run_one_coro(coro_fn, this_index, start_next)) tasks.add(task) start_next_timer = ( loop.call_later(delay, _set_result, start_next) if delay else None ) elif not tasks: # We exhausted the coro_fns list and no tasks are running # so we have no winner and all coroutines failed. break while tasks or start_next: done = await _wait_one( (*tasks, start_next) if start_next else tasks, loop ) if done is start_next: # The current task has failed or the timer has expired # so we need to start the next task. start_next = None if start_next_timer: start_next_timer.cancel() start_next_timer = None # Break out of the task waiting loop to start the next # task. break if TYPE_CHECKING: assert isinstance(done, asyncio.Task) tasks.remove(done) if winner := done.result(): return *winner, exceptions finally: # We either have: # - a winner # - all tasks failed # - a KeyboardInterrupt or SystemExit. # # If the timer is still running, cancel it. # if start_next_timer: start_next_timer.cancel() # # If there are any tasks left, cancel them and than # wait them so they fill the exceptions list. # for task in tasks: task.cancel() with contextlib.suppress(asyncio.CancelledError): await task return None, None, exceptions aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/impl.py000066400000000000000000000227211476416312700225270ustar00rootroot00000000000000"""Base implementation.""" import asyncio import collections import contextlib import functools import itertools import socket from typing import List, Optional, Sequence, Set, Union from . import _staggered from .types import AddrInfoType, SocketFactoryType async def start_connection( addr_infos: Sequence[AddrInfoType], *, local_addr_infos: Optional[Sequence[AddrInfoType]] = None, happy_eyeballs_delay: Optional[float] = None, interleave: Optional[int] = None, loop: Optional[asyncio.AbstractEventLoop] = None, socket_factory: Optional[SocketFactoryType] = None, ) -> socket.socket: """ Connect to a TCP server. Create a socket connection to a specified destination. The destination is specified as a list of AddrInfoType tuples as returned from getaddrinfo(). The arguments are, in order: * ``family``: the address family, e.g. ``socket.AF_INET`` or ``socket.AF_INET6``. * ``type``: the socket type, e.g. ``socket.SOCK_STREAM`` or ``socket.SOCK_DGRAM``. * ``proto``: the protocol, e.g. ``socket.IPPROTO_TCP`` or ``socket.IPPROTO_UDP``. * ``canonname``: the canonical name of the address, e.g. ``"www.python.org"``. * ``sockaddr``: the socket address This method is a coroutine which will try to establish the connection in the background. When successful, the coroutine returns a socket. The expected use case is to use this method in conjunction with loop.create_connection() to establish a connection to a server:: socket = await start_connection(addr_infos) transport, protocol = await loop.create_connection( MyProtocol, sock=socket, ...) """ if not (current_loop := loop): current_loop = asyncio.get_running_loop() single_addr_info = len(addr_infos) == 1 if happy_eyeballs_delay is not None and interleave is None: # If using happy eyeballs, default to interleave addresses by family interleave = 1 if interleave and not single_addr_info: addr_infos = _interleave_addrinfos(addr_infos, interleave) sock: Optional[socket.socket] = None # uvloop can raise RuntimeError instead of OSError exceptions: List[List[Union[OSError, RuntimeError]]] = [] if happy_eyeballs_delay is None or single_addr_info: # not using happy eyeballs for addrinfo in addr_infos: try: sock = await _connect_sock( current_loop, exceptions, addrinfo, local_addr_infos, None, socket_factory, ) break except (RuntimeError, OSError): continue else: # using happy eyeballs open_sockets: Set[socket.socket] = set() try: sock, _, _ = await _staggered.staggered_race( ( functools.partial( _connect_sock, current_loop, exceptions, addrinfo, local_addr_infos, open_sockets, socket_factory, ) for addrinfo in addr_infos ), happy_eyeballs_delay, ) finally: # If we have a winner, staggered_race will # cancel the other tasks, however there is a # small race window where any of the other tasks # can be done before they are cancelled which # will leave the socket open. To avoid this problem # we pass a set to _connect_sock to keep track of # the open sockets and close them here if there # are any "runner up" sockets. for s in open_sockets: if s is not sock: with contextlib.suppress(OSError): s.close() open_sockets = None # type: ignore[assignment] if sock is None: all_exceptions = [exc for sub in exceptions for exc in sub] try: first_exception = all_exceptions[0] if len(all_exceptions) == 1: raise first_exception else: # If they all have the same str(), raise one. model = str(first_exception) if all(str(exc) == model for exc in all_exceptions): raise first_exception # Raise a combined exception so the user can see all # the various error messages. msg = "Multiple exceptions: {}".format( ", ".join(str(exc) for exc in all_exceptions) ) # If the errno is the same for all exceptions, raise # an OSError with that errno. if isinstance(first_exception, OSError): first_errno = first_exception.errno if all( isinstance(exc, OSError) and exc.errno == first_errno for exc in all_exceptions ): raise OSError(first_errno, msg) elif isinstance(first_exception, RuntimeError) and all( isinstance(exc, RuntimeError) for exc in all_exceptions ): raise RuntimeError(msg) # We have a mix of OSError and RuntimeError # so we have to pick which one to raise. # and we raise OSError for compatibility raise OSError(msg) finally: all_exceptions = None # type: ignore[assignment] exceptions = None # type: ignore[assignment] return sock async def _connect_sock( loop: asyncio.AbstractEventLoop, exceptions: List[List[Union[OSError, RuntimeError]]], addr_info: AddrInfoType, local_addr_infos: Optional[Sequence[AddrInfoType]] = None, open_sockets: Optional[Set[socket.socket]] = None, socket_factory: Optional[SocketFactoryType] = None, ) -> socket.socket: """ Create, bind and connect one socket. If open_sockets is passed, add the socket to the set of open sockets. Any failure caught here will remove the socket from the set and close it. Callers can use this set to close any sockets that are not the winner of all staggered tasks in the result there are runner up sockets aka multiple winners. """ my_exceptions: List[Union[OSError, RuntimeError]] = [] exceptions.append(my_exceptions) family, type_, proto, _, address = addr_info sock = None try: if socket_factory is not None: sock = socket_factory(addr_info) else: sock = socket.socket(family=family, type=type_, proto=proto) if open_sockets is not None: open_sockets.add(sock) sock.setblocking(False) if local_addr_infos is not None: for lfamily, _, _, _, laddr in local_addr_infos: # skip local addresses of different family if lfamily != family: continue try: sock.bind(laddr) break except OSError as exc: msg = ( f"error while attempting to bind on " f"address {laddr!r}: " f"{(exc.strerror or '').lower()}" ) exc = OSError(exc.errno, msg) my_exceptions.append(exc) else: # all bind attempts failed if my_exceptions: raise my_exceptions.pop() else: raise OSError(f"no matching local address with {family=} found") await loop.sock_connect(sock, address) return sock except (RuntimeError, OSError) as exc: my_exceptions.append(exc) if sock is not None: if open_sockets is not None: open_sockets.remove(sock) try: sock.close() except OSError as e: my_exceptions.append(e) raise raise except: if sock is not None: if open_sockets is not None: open_sockets.remove(sock) try: sock.close() except OSError as e: my_exceptions.append(e) raise raise finally: exceptions = my_exceptions = None # type: ignore[assignment] def _interleave_addrinfos( addrinfos: Sequence[AddrInfoType], first_address_family_count: int = 1 ) -> List[AddrInfoType]: """Interleave list of addrinfo tuples by family.""" # Group addresses by family addrinfos_by_family: collections.OrderedDict[int, List[AddrInfoType]] = ( collections.OrderedDict() ) for addr in addrinfos: family = addr[0] if family not in addrinfos_by_family: addrinfos_by_family[family] = [] addrinfos_by_family[family].append(addr) addrinfos_lists = list(addrinfos_by_family.values()) reordered: List[AddrInfoType] = [] if first_address_family_count > 1: reordered.extend(addrinfos_lists[0][: first_address_family_count - 1]) del addrinfos_lists[0][: first_address_family_count - 1] reordered.extend( a for a in itertools.chain.from_iterable(itertools.zip_longest(*addrinfos_lists)) if a is not None ) return reordered aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/py.typed000066400000000000000000000000001476416312700226750ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/types.py000066400000000000000000000006511476416312700227300ustar00rootroot00000000000000"""Types for aiohappyeyeballs.""" import socket # PY3.9: Import Callable from typing until we drop Python 3.9 support # https://github.com/python/cpython/issues/87131 from typing import Callable, Tuple, Union AddrInfoType = Tuple[ Union[int, socket.AddressFamily], Union[int, socket.SocketKind], int, str, Tuple, # type: ignore[type-arg] ] SocketFactoryType = Callable[[AddrInfoType], socket.socket] aiohappyeyeballs-2.6.1/src/aiohappyeyeballs/utils.py000066400000000000000000000057241476416312700227320ustar00rootroot00000000000000"""Utility functions for aiohappyeyeballs.""" import ipaddress import socket from typing import Dict, List, Optional, Tuple, Union from .types import AddrInfoType def addr_to_addr_infos( addr: Optional[ Union[Tuple[str, int, int, int], Tuple[str, int, int], Tuple[str, int]] ], ) -> Optional[List[AddrInfoType]]: """Convert an address tuple to a list of addr_info tuples.""" if addr is None: return None host = addr[0] port = addr[1] is_ipv6 = ":" in host if is_ipv6: flowinfo = 0 scopeid = 0 addr_len = len(addr) if addr_len >= 4: scopeid = addr[3] # type: ignore[misc] if addr_len >= 3: flowinfo = addr[2] # type: ignore[misc] addr = (host, port, flowinfo, scopeid) family = socket.AF_INET6 else: addr = (host, port) family = socket.AF_INET return [(family, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", addr)] def pop_addr_infos_interleave( addr_infos: List[AddrInfoType], interleave: Optional[int] = None ) -> None: """ Pop addr_info from the list of addr_infos by family up to interleave times. The interleave parameter is used to know how many addr_infos for each family should be popped of the top of the list. """ seen: Dict[int, int] = {} if interleave is None: interleave = 1 to_remove: List[AddrInfoType] = [] for addr_info in addr_infos: family = addr_info[0] if family not in seen: seen[family] = 0 if seen[family] < interleave: to_remove.append(addr_info) seen[family] += 1 for addr_info in to_remove: addr_infos.remove(addr_info) def _addr_tuple_to_ip_address( addr: Union[Tuple[str, int], Tuple[str, int, int, int]], ) -> Union[ Tuple[ipaddress.IPv4Address, int], Tuple[ipaddress.IPv6Address, int, int, int] ]: """Convert an address tuple to an IPv4Address.""" return (ipaddress.ip_address(addr[0]), *addr[1:]) def remove_addr_infos( addr_infos: List[AddrInfoType], addr: Union[Tuple[str, int], Tuple[str, int, int, int]], ) -> None: """ Remove an address from the list of addr_infos. The addr value is typically the return value of sock.getpeername(). """ bad_addrs_infos: List[AddrInfoType] = [] for addr_info in addr_infos: if addr_info[-1] == addr: bad_addrs_infos.append(addr_info) if bad_addrs_infos: for bad_addr_info in bad_addrs_infos: addr_infos.remove(bad_addr_info) return # Slow path in case addr is formatted differently match_addr = _addr_tuple_to_ip_address(addr) for addr_info in addr_infos: if match_addr == _addr_tuple_to_ip_address(addr_info[-1]): bad_addrs_infos.append(addr_info) if bad_addrs_infos: for bad_addr_info in bad_addrs_infos: addr_infos.remove(bad_addr_info) return raise ValueError(f"Address {addr} not found in addr_infos") aiohappyeyeballs-2.6.1/templates/000077500000000000000000000000001476416312700170645ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/templates/CHANGELOG.md.j2000066400000000000000000000011221476416312700212030ustar00rootroot00000000000000# Changelog {%- for version, release in context.history.released.items() %} ## {{ version.as_tag() }} ({{ release.tagged_date.strftime("%Y-%m-%d") }}) {%- for category, commits in release["elements"].items() %} {# Category title: Breaking, Fix, Documentation #} ### {{ category | capitalize }} {# List actual changes in the category #} {%- for commit in commits %} - {{ commit.descriptions[0] | capitalize }} ([`{{ commit.short_hash }}`]({{ commit.hexsha | commit_hash_url }})) {%- endfor %}{# for commit #} {%- endfor %}{# for category, commits #} {%- endfor %}{# for version, release #} aiohappyeyeballs-2.6.1/tests/000077500000000000000000000000001476416312700162305ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/tests/__init__.py000066400000000000000000000000001476416312700203270ustar00rootroot00000000000000aiohappyeyeballs-2.6.1/tests/conftest.py000066400000000000000000000035351476416312700204350ustar00rootroot00000000000000"""Configuration for the tests.""" import asyncio import reprlib import threading from asyncio.events import AbstractEventLoop, TimerHandle from contextlib import contextmanager from typing import Generator import pytest @pytest.fixture(autouse=True) def verify_threads_ended() -> Generator[None, None, None]: """Verify that the threads are not running after the test.""" threads_before = frozenset(threading.enumerate()) yield threads = frozenset(threading.enumerate()) - threads_before assert not threads def get_scheduled_timer_handles(loop: AbstractEventLoop) -> list[TimerHandle]: """Return a list of scheduled TimerHandles.""" handles: list[TimerHandle] = loop._scheduled # type: ignore[attr-defined] return handles @contextmanager def long_repr_strings() -> Generator[None, None, None]: """Increase reprlib maxstring and maxother to 300.""" arepr = reprlib.aRepr original_maxstring = arepr.maxstring original_maxother = arepr.maxother arepr.maxstring = 300 arepr.maxother = 300 try: yield finally: arepr.maxstring = original_maxstring arepr.maxother = original_maxother @pytest.fixture(autouse=True) def verify_no_lingering_tasks( event_loop: asyncio.AbstractEventLoop, ) -> Generator[None, None, None]: """Verify that all tasks are cleaned up.""" tasks_before = asyncio.all_tasks(event_loop) yield tasks = asyncio.all_tasks(event_loop) - tasks_before for task in tasks: pytest.fail(f"Task still running: {task!r}") task.cancel() if tasks: event_loop.run_until_complete(asyncio.wait(tasks)) for handle in get_scheduled_timer_handles(event_loop): if not handle.cancelled(): with long_repr_strings(): pytest.fail(f"Lingering timer after test {handle!r}") handle.cancel() aiohappyeyeballs-2.6.1/tests/test_impl.py000066400000000000000000001507171476416312700206150ustar00rootroot00000000000000import asyncio import socket from types import ModuleType from typing import List, Optional, Sequence, Set, Tuple, Union from unittest import mock import pytest from aiohappyeyeballs import ( AddrInfoType, SocketFactoryType, _staggered, impl, start_connection, ) def mock_socket_module(): m_socket = mock.MagicMock(spec=socket) for name in ( "AF_INET", "AF_INET6", "AF_UNSPEC", "IPPROTO_TCP", "IPPROTO_UDP", "SOCK_STREAM", "SOCK_DGRAM", "SOL_SOCKET", "SO_REUSEADDR", "inet_pton", ): if hasattr(socket, name): setattr(m_socket, name, getattr(socket, name)) else: delattr(m_socket, name) m_socket.socket = mock.MagicMock() m_socket.socket.return_value = mock_nonblocking_socket() return m_socket def mock_nonblocking_socket( proto=socket.IPPROTO_TCP, type=socket.SOCK_STREAM, family=socket.AF_INET ): """Create a mock of a non-blocking socket.""" sock = mock.create_autospec(socket.socket, spec_set=True, instance=True) sock.proto = proto sock.type = type sock.family = family sock.gettimeout.return_value = 0.0 return sock def patch_socket(f): return mock.patch("aiohappyeyeballs.impl.socket", new_callable=mock_socket_module)( f ) @pytest.mark.asyncio @patch_socket async def test_single_addr_info_errors(m_socket: ModuleType) -> None: idx = -1 errors = ["err1", "err2"] def _socket(*args, **kw): nonlocal idx, errors idx += 1 raise OSError(5, errors[idx]) m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ) ] with pytest.raises(OSError, match=errors[0]): await start_connection(addr_info) @pytest.mark.asyncio @patch_socket async def test_single_addr_success(m_socket: ModuleType) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) def _socket(*args, **kw): return mock_socket m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ) ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert await start_connection(addr_info) == mock_socket @pytest.mark.asyncio @patch_socket async def test_single_addr_success_passing_loop(m_socket: ModuleType) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) def _socket(*args, **kw): return mock_socket m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ) ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert ( await start_connection(addr_info, loop=asyncio.get_running_loop()) == mock_socket ) @pytest.mark.asyncio @patch_socket async def test_single_addr_socket_factory(m_socket: ModuleType) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) def factory(addr_info: AddrInfoType) -> socket.socket: return mock_socket addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ) ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert await start_connection(addr_info, socket_factory=factory) == mock_socket @pytest.mark.asyncio @patch_socket async def test_multiple_addr_success_second_one( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) idx = -1 errors = ["err1", "err2"] def _socket(*args, **kw): nonlocal idx, errors idx += 1 if idx == 1: raise OSError(5, errors[idx]) return mock_socket m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert await start_connection(addr_info) == mock_socket @pytest.mark.asyncio @patch_socket async def test_multiple_winners_cleaned_up( m_socket: ModuleType, ) -> None: loop = asyncio.get_running_loop() finish = loop.create_future() def _socket(*args, **kw): return mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) async def _connect_sock( loop: asyncio.AbstractEventLoop, exceptions: List[List[Union[OSError, RuntimeError]]], addr_info: AddrInfoType, local_addr_infos: Optional[Sequence[AddrInfoType]] = None, sockets: Optional[Set[socket.socket]] = None, socket_factory: Optional[SocketFactoryType] = None, ) -> socket.socket: await finish sock = _socket() assert sockets is not None sockets.add(sock) return sock m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.84", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.85", 80), ), ] with mock.patch.object(impl, "_connect_sock", _connect_sock): task = loop.create_task( start_connection(addr_info, happy_eyeballs_delay=0.0001, interleave=0) ) await asyncio.sleep(0.1) loop.call_soon(finish.set_result, None) await task @pytest.mark.asyncio @patch_socket async def test_multiple_addr_success_second_one_happy_eyeballs( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) idx = -1 errors = ["err1", "err2"] def _socket(*args, **kw): nonlocal idx, errors idx += 1 if idx == 1: raise OSError(5, errors[idx]) return mock_socket m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert ( await start_connection(addr_info, happy_eyeballs_delay=0.3) == mock_socket ) @pytest.mark.asyncio @patch_socket async def test_happy_eyeballs_socket_factory( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) idx = -1 errors = ["err1", "err2"] def factory(addr_info: AddrInfoType) -> socket.socket: nonlocal idx, errors idx += 1 if idx == 1: raise OSError(5, errors[idx]) return mock_socket addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, socket_factory=factory ) == mock_socket ) @pytest.mark.asyncio @patch_socket async def test_multiple_addr_all_fail_happy_eyeballs( m_socket: ModuleType, ) -> None: mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) idx = -1 errors = ["err1", "err2"] def _socket(*args, **kw): nonlocal idx, errors idx += 1 raise OSError(5, errors[idx]) m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ), ] asyncio.get_running_loop() with pytest.raises(OSError, match=errors[0]): await start_connection(addr_info, happy_eyeballs_delay=0.3) @pytest.mark.asyncio @patch_socket async def test_ipv6_and_ipv4_happy_eyeballs_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) def _socket(*args, **kw): if kw["family"] == socket.AF_INET6: raise OSError(5, "ipv6 fail") for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv4_addr_info] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert ( await start_connection(addr_info, happy_eyeballs_delay=0.3) == mock_socket ) assert mock_socket.family == socket.AF_INET @pytest.mark.asyncio @patch_socket async def test_ipv6_and_ipv4_happy_eyeballs_ipv4_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) def _socket(*args, **kw): if kw["family"] == socket.AF_INET: raise OSError(5, "ipv4 fail") for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket m_socket.socket = _socket # type: ignore ipv6_addr: Tuple[str, int, int, int] = ("dead:beef::", 80, 0, 0) ipv6_addr_info: Tuple[int, int, int, str, Tuple[str, int, int, int]] = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ipv6_addr, ) ipv4_addr: Tuple[str, int] = ("107.6.106.83", 80) ipv4_addr_info: Tuple[int, int, int, str, Tuple[str, int]] = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ipv4_addr, ) addr_info = [ipv6_addr_info, ipv4_addr_info] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", return_value=None): assert ( await start_connection(addr_info, happy_eyeballs_delay=0.3) == mock_socket ) assert mock_socket.family == socket.AF_INET6 @pytest.mark.asyncio @patch_socket async def test_ipv6_and_ipv4_happy_eyeballs_first_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection(addr_info, happy_eyeballs_delay=0.3) == mock_socket ) # IPv6 addresses are tried first, but the first one fails so IPv4 wins assert mock_socket.family == socket.AF_INET assert create_calls == [("dead:beef::", 80, 0, 0), ("107.6.106.83", 80)] @pytest.mark.asyncio @patch_socket async def test_ipv64_happy_eyeballs_interleave_2_first_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection(addr_info, happy_eyeballs_delay=0.3, interleave=2) == mock_socket ) # IPv6 addresses are tried first, but the first one fails so second IPv6 wins # because interleave is 2 assert mock_socket.family == socket.AF_INET6 assert create_calls == [("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0)] @pytest.mark.asyncio @patch_socket async def test_ipv6_only_happy_eyeballs_first_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection(addr_info, happy_eyeballs_delay=0.3) == mock_socket ) # IPv6 address are tried first, but the first one fails so second IPv6 wins assert mock_socket.family == socket.AF_INET6 assert create_calls == [("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0)] @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_eyeballs_interleave_2_first_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ) ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # IPv6 addresses are tried first, but the first one fails so second IPv6 wins # because interleave is 2 assert mock_socket.family == socket.AF_INET6 assert create_calls == [("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0)] @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_both__eyeballs_first_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, local_addr_infos=local_addr_infos, ) == mock_socket ) # IPv6 is tried first and fails, which means IPv4 is tried next and succeeds assert mock_socket.family == socket.AF_INET assert create_calls == [("dead:beef::", 80, 0, 0), ("107.6.106.83", 80)] @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_bind_fails_eyeballs_first_ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) if kw["family"] == socket.AF_INET: mock_socket.bind.side_effect = OSError(5, "bind fail") return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( OSError, match="ipv6 fail" ): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=1, local_addr_infos=local_addr_infos, ) == mock_socket ) # We only tried IPv6 since bind to IPv4 failed assert create_calls == [("dead:beef::", 80, 0, 0)] @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_bind_fails_eyeballs_interleave_first__ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) if kw["family"] == socket.AF_INET: mock_socket.bind.side_effect = OSError(5, "bind fail") return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # IPv6 is tried first and fails, which means IPv4 is tried next but the laddr # build fails so we move on to the next IPv6 and it succeeds assert create_calls == [("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0)] assert mock_socket.family == socket.AF_INET6 @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_socket_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): raise Exception("Something really went wrong") async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( Exception, match="Something really went wrong" ): assert ( await start_connection( addr_info, local_addr_infos=local_addr_infos, ) == mock_socket ) # All binds failed assert create_calls == [] @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_socket_blocking_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) mock_socket.setblocking.side_effect = Exception("Something really went wrong") return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( Exception, match="Something really went wrong" ): assert ( await start_connection( addr_info, local_addr_infos=local_addr_infos, ) == mock_socket ) # All binds failed assert create_calls == [] @pytest.mark.asyncio @patch_socket async def test_ipv64_laddr_eyeballs_ipv4_only_tried( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ) ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # Only IPv4 addresses are tried because local_addr_infos is IPv4 assert mock_socket.family == socket.AF_INET assert create_calls == [("107.6.106.83", 80)] @patch_socket @pytest.mark.asyncio async def test_ipv64_laddr_bind_fails_all_eyeballs_interleave_first__ipv6_fails( m_socket: ModuleType, ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) mock_socket.bind.side_effect = OSError(4, "bind fail") return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) if address[0] == "dead:beef::": raise OSError(5, "ipv6 fail") return None m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( OSError, match="Multiple exceptions" ): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # All binds failed assert create_calls == [] @patch_socket @pytest.mark.asyncio async def test_all_same_exception_and_same_errno( m_socket: ModuleType, ) -> None: """Test that all exceptions are the same and have the same errno.""" mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) raise OSError(5, "all fail") m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() # We should get the same exception raised if they are all the same with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( OSError, match="all fail" ) as exc_info: assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) assert exc_info.value.errno == 5 # All calls failed assert create_calls == [ ("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0), ("107.6.106.83", 80), ] @patch_socket @pytest.mark.asyncio async def test_all_same_exception_and_with_different_errno( m_socket: ModuleType, ) -> None: """Test no errno is set if all OSError have different errno.""" mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) raise OSError(len(create_calls), "all fail") m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() # We should get the same exception raised if they are all the same with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( OSError, match="all fail" ) as exc_info: assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # No errno is set if they are all different assert exc_info.value.errno is None # All calls failed assert create_calls == [ ("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0), ("107.6.106.83", 80), ] @patch_socket @pytest.mark.asyncio async def test_uvloop_runtime_error( m_socket: ModuleType, ) -> None: """ Test RuntimeError is handled when connecting a socket with uvloop. Connecting a socket can raise a RuntimeError, OSError or ValueError. - OSError: If the address is invalid or the connection fails. - ValueError: if a non-sock it passed (this should never happen). https://github.com/python/cpython/blob/e44eebfc1eccdaaebc219accbfc705c9a9de068d/Lib/asyncio/selector_events.py#L271 - RuntimeError: If the file descriptor is already in use by a transport. We should never get ValueError since we are using the correct types. selector_events.py never seems to raise a RuntimeError, but it is possible with uvloop. This test is to ensure that we handle it correctly. """ mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) raise RuntimeError("all fail") m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() # We should get the same exception raised if they are all the same with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( RuntimeError, match="all fail" ): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # All calls failed assert create_calls == [ ("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0), ("107.6.106.83", 80), ] @patch_socket @pytest.mark.asyncio async def test_uvloop_different_runtime_error( m_socket: ModuleType, ) -> None: """Test different RuntimeErrors are handled when connecting a socket with uvloop.""" mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] counter = 0 def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) nonlocal counter counter += 1 raise RuntimeError(counter) m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() # We should get the same exception raised if they are all the same with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( RuntimeError, match="Multiple exceptions: 1, 2, 3" ): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # All calls failed assert create_calls == [ ("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0), ("107.6.106.83", 80), ] @patch_socket @pytest.mark.asyncio async def test_uvloop_mixing_os_and_runtime_error( m_socket: ModuleType, ) -> None: """Test uvloop raising OSError and RuntimeError.""" mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] counter = 0 def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) nonlocal counter counter += 1 if counter == 1: raise RuntimeError(counter) raise OSError(counter, f"all fail {counter}") m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() # We should get the same exception raised if they are all the same with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( OSError, match="Multiple exceptions: 1" ): assert ( await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) == mock_socket ) # All calls failed assert create_calls == [ ("dead:beef::", 80, 0, 0), ("dead:aaaa::", 80, 0, 0), ("107.6.106.83", 80), ] @patch_socket @pytest.mark.asyncio async def test_handling_system_exit( m_socket: ModuleType, ) -> None: """Test handling SystemExit.""" class MockSystemExit(BaseException): """Mock SystemExit.""" mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) raise MockSystemExit m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() with pytest.raises(MockSystemExit), mock.patch.object( loop, "sock_connect", _sock_connect ), mock.patch.object(_staggered, "RE_RAISE_EXCEPTIONS", (MockSystemExit,)): await start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) # Stopped after the first call assert create_calls == [ ("dead:beef::", 80, 0, 0), ] @patch_socket @pytest.mark.asyncio async def test_cancellation_is_not_swallowed( m_socket: ModuleType, ) -> None: """Test that cancellation is not swallowed.""" mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) create_calls = [] def _socket(*args, **kw): for attr in kw: setattr(mock_socket, attr, kw[attr]) return mock_socket async def _sock_connect( sock: socket.socket, address: Tuple[str, int, int, int] ) -> None: create_calls.append(address) await asyncio.sleep(1000) m_socket.socket = _socket # type: ignore ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] local_addr_infos = [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("::1", 0, 0, 0), ), ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("127.0.0.1", 0), ), ] loop = asyncio.get_running_loop() # We should get the same exception raised if they are all the same with mock.patch.object(loop, "sock_connect", _sock_connect), pytest.raises( asyncio.CancelledError ): task = asyncio.create_task( start_connection( addr_info, happy_eyeballs_delay=0.3, interleave=2, local_addr_infos=local_addr_infos, ) ) await asyncio.sleep(0) task.cancel() await task # After calls are cancelled now more are made assert create_calls == [ ("dead:beef::", 80, 0, 0), ] @pytest.mark.asyncio @pytest.mark.parametrize( "connect_side_effect", [ OSError("during connect"), asyncio.CancelledError("during connect"), ], ) @patch_socket async def test_single_addr_info_close_errors( m_socket: ModuleType, connect_side_effect: BaseException ) -> None: mock_socket = mock.MagicMock( family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP, fileno=mock.MagicMock(return_value=1), ) mock_socket.configure_mock( **{ "connect.side_effect": connect_side_effect, "close.side_effect": OSError("during close"), } ) def _socket(*args, **kw): return mock_socket m_socket.socket = _socket # type: ignore addr_info = [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.82", 80), ) ] with pytest.raises(OSError, match="during close"): await start_connection(addr_info) aiohappyeyeballs-2.6.1/tests/test_init.py000066400000000000000000000001511476416312700206010ustar00rootroot00000000000000from aiohappyeyeballs import start_connection def test_init(): assert start_connection is not None aiohappyeyeballs-2.6.1/tests/test_staggered.py000066400000000000000000000053051476416312700216110ustar00rootroot00000000000000import asyncio import sys from collections.abc import Callable as CallableABC from functools import partial from typing import Callable as CallableTyping import pytest from aiohappyeyeballs._staggered import Callable, staggered_race @pytest.mark.asyncio async def test_one_winners(): """Test that there is only one winner when there is no await in the coro.""" winners = [] async def coro(idx): winners.append(idx) return idx coros = [partial(coro, idx) for idx in range(4)] winner, index, excs = await staggered_race( coros, delay=None, ) assert len(winners) == 1 assert winners == [0] assert winner == 0 assert index == 0 assert excs == [None] @pytest.mark.asyncio async def test_multiple_winners(): """Test multiple winners are handled correctly.""" loop = asyncio.get_running_loop() winners = [] finish = loop.create_future() async def coro(idx): await finish winners.append(idx) return idx coros = [partial(coro, idx) for idx in range(4)] task = loop.create_task(staggered_race(coros, delay=0.00001)) await asyncio.sleep(0.1) loop.call_soon(finish.set_result, None) winner, index, excs = await task assert len(winners) == 4 assert winners == [0, 1, 2, 3] assert winner == 0 assert index == 0 assert excs == [None, None, None, None] @pytest.mark.skipif(sys.version_info < (3, 12), reason="requires python3.12 or higher") def test_multiple_winners_eager_task_factory(): """Test multiple winners are handled correctly.""" loop = asyncio.new_event_loop() eager_task_factory = asyncio.create_eager_task_factory(asyncio.Task) loop.set_task_factory(eager_task_factory) asyncio.set_event_loop(None) async def run(): winners = [] finish = loop.create_future() async def coro(idx): await finish winners.append(idx) return idx coros = [partial(coro, idx) for idx in range(4)] task = loop.create_task(staggered_race(coros, delay=0.00001)) await asyncio.sleep(0.1) loop.call_soon(finish.set_result, None) winner, index, excs = await task assert len(winners) == 4 assert winners == [0, 1, 2, 3] assert winner == 0 assert index == 0 assert excs == [None, None, None, None] loop.run_until_complete(run()) loop.close() def test_callable_import_from_typing(): """ Test that Callable is imported from typing. PY3.9: https://github.com/python/cpython/issues/87131 Drop this test when we drop support for Python 3.9. """ assert Callable is CallableTyping assert Callable is not CallableABC aiohappyeyeballs-2.6.1/tests/test_staggered_cpython.py000066400000000000000000000077551476416312700233700ustar00rootroot00000000000000""" Tests for staggered_race. These tests are copied from cpython to ensure our implementation is compatible with the one in cpython. """ import asyncio import unittest from aiohappyeyeballs._staggered import staggered_race def tearDownModule(): asyncio.set_event_loop_policy(None) class StaggeredTests(unittest.IsolatedAsyncioTestCase): async def test_empty(self): winner, index, excs = await staggered_race( [], delay=None, ) self.assertIs(winner, None) self.assertIs(index, None) self.assertEqual(excs, []) async def test_one_successful(self): async def coro(index): return f"Res: {index}" winner, index, excs = await staggered_race( [ lambda: coro(0), lambda: coro(1), ], delay=None, ) self.assertEqual(winner, "Res: 0") self.assertEqual(index, 0) self.assertEqual(excs, [None]) async def test_first_error_second_successful(self): async def coro(index): if index == 0: raise ValueError(index) return f"Res: {index}" winner, index, excs = await staggered_race( [ lambda: coro(0), lambda: coro(1), ], delay=None, ) self.assertEqual(winner, "Res: 1") self.assertEqual(index, 1) self.assertEqual(len(excs), 2) self.assertIsInstance(excs[0], ValueError) self.assertIs(excs[1], None) async def test_first_timeout_second_successful(self): async def coro(index): if index == 0: await asyncio.sleep(10) # much bigger than delay return f"Res: {index}" winner, index, excs = await staggered_race( [ lambda: coro(0), lambda: coro(1), ], delay=0.1, ) self.assertEqual(winner, "Res: 1") self.assertEqual(index, 1) self.assertEqual(len(excs), 2) self.assertIsInstance(excs[0], asyncio.CancelledError) self.assertIs(excs[1], None) async def test_none_successful(self): async def coro(index): raise ValueError(index) for delay in [None, 0, 0.1, 1]: with self.subTest(delay=delay): winner, index, excs = await staggered_race( [ lambda: coro(0), lambda: coro(1), ], delay=delay, ) self.assertIs(winner, None) self.assertIs(index, None) self.assertEqual(len(excs), 2) self.assertIsInstance(excs[0], ValueError) self.assertIsInstance(excs[1], ValueError) async def test_long_delay_early_failure(self): async def coro(index): await asyncio.sleep(0) # Dummy coroutine for the 1 case if index == 0: await asyncio.sleep(0.1) # Dummy coroutine raise ValueError(index) return f"Res: {index}" winner, index, excs = await staggered_race( [ lambda: coro(0), lambda: coro(1), ], delay=10, ) self.assertEqual(winner, "Res: 1") self.assertEqual(index, 1) self.assertEqual(len(excs), 2) self.assertIsInstance(excs[0], ValueError) self.assertIsNone(excs[1]) def test_loop_argument(self): loop = asyncio.new_event_loop() async def coro(): self.assertEqual(loop, asyncio.get_running_loop()) return "coro" async def main(): winner, index, excs = await staggered_race([coro], delay=0.1, loop=loop) self.assertEqual(winner, "coro") self.assertEqual(index, 0) loop.run_until_complete(main()) loop.close() if __name__ == "__main__": unittest.main() aiohappyeyeballs-2.6.1/tests/test_staggered_cpython_eager_task_factory.py000066400000000000000000000057111476416312700272720ustar00rootroot00000000000000""" Tests staggered_race and eager_task_factory with asyncio.Task. These tests are copied from cpython to ensure our implementation is compatible with the one in cpython. """ import asyncio import sys import unittest from aiohappyeyeballs._staggered import staggered_race def tearDownModule(): asyncio.set_event_loop_policy(None) class EagerTaskFactoryLoopTests(unittest.TestCase): def close_loop(self, loop): loop.close() def set_event_loop(self, loop, *, cleanup=True): if loop is None: raise AssertionError("loop is None") # ensure that the event loop is passed explicitly in asyncio asyncio.set_event_loop(None) if cleanup: self.addCleanup(self.close_loop, loop) def tearDown(self): asyncio.set_event_loop(None) self.doCleanups() def setUp(self): if sys.version_info < (3, 12): self.skipTest("eager_task_factory is only available in Python 3.12+") super().setUp() self.loop = asyncio.new_event_loop() self.eager_task_factory = asyncio.create_eager_task_factory(asyncio.Task) self.loop.set_task_factory(self.eager_task_factory) self.set_event_loop(self.loop) def test_staggered_race_with_eager_tasks(self): # See https://github.com/python/cpython/issues/124309 async def fail(): await asyncio.sleep(0) raise ValueError("no good") async def run(): winner, index, excs = await staggered_race( [ lambda: asyncio.sleep(2, result="sleep2"), lambda: asyncio.sleep(1, result="sleep1"), lambda: fail(), ], delay=0.25, ) self.assertEqual(winner, "sleep1") self.assertEqual(index, 1) assert index is not None self.assertIsNone(excs[index]) self.assertIsInstance(excs[0], asyncio.CancelledError) self.assertIsInstance(excs[2], ValueError) self.loop.run_until_complete(run()) def test_staggered_race_with_eager_tasks_no_delay(self): # See https://github.com/python/cpython/issues/124309 async def fail(): raise ValueError("no good") async def run(): winner, index, excs = await staggered_race( [ lambda: fail(), lambda: asyncio.sleep(1, result="sleep1"), lambda: asyncio.sleep(0, result="sleep0"), ], delay=None, ) self.assertEqual(winner, "sleep1") self.assertEqual(index, 1) assert index is not None self.assertIsNone(excs[index]) self.assertIsInstance(excs[0], ValueError) self.assertEqual(len(excs), 2) self.loop.run_until_complete(run()) if __name__ == "__main__": if sys.version_info >= (3, 12): unittest.main() aiohappyeyeballs-2.6.1/tests/test_types.py000066400000000000000000000006011476416312700210020ustar00rootroot00000000000000from collections.abc import Callable as CallableABC from typing import Callable as CallableTyping from aiohappyeyeballs.types import Callable def test_callable_import_from_typing(): """ Test that Callable is imported from typing. PY3.9: https://github.com/python/cpython/issues/87131 """ assert Callable is CallableTyping assert Callable is not CallableABC aiohappyeyeballs-2.6.1/tests/test_utils.py000066400000000000000000000117661476416312700210140ustar00rootroot00000000000000import socket from typing import List import pytest from aiohappyeyeballs import ( AddrInfoType, addr_to_addr_infos, pop_addr_infos_interleave, remove_addr_infos, ) def test_pop_addr_infos_interleave(): """Test pop_addr_infos_interleave.""" ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info: List[AddrInfoType] = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] addr_info_copy = addr_info.copy() pop_addr_infos_interleave(addr_info_copy, 1) assert addr_info_copy == [ipv6_addr_info_2] pop_addr_infos_interleave(addr_info_copy, 1) assert addr_info_copy == [] addr_info_copy = addr_info.copy() pop_addr_infos_interleave(addr_info_copy, 2) assert addr_info_copy == [] addr_info_copy = addr_info.copy() pop_addr_infos_interleave(addr_info_copy) assert addr_info_copy == [ipv6_addr_info_2] def test_remove_addr_infos(): """Test remove_addr_infos.""" ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info: List[AddrInfoType] = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] addr_info_copy = addr_info.copy() remove_addr_infos( addr_info_copy, ("dead:beef::", 80, 0, 0), ) assert addr_info_copy == [ipv6_addr_info_2, ipv4_addr_info] remove_addr_infos(addr_info_copy, ("dead:aaaa::", 80, 0, 0)) assert addr_info_copy == [ipv4_addr_info] remove_addr_infos(addr_info_copy, ("107.6.106.83", 80)) assert addr_info_copy == [] def test_remove_addr_infos_slow_path(): """Test remove_addr_infos with mis-matched formatting.""" ipv6_addr_info = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:beef::", 80, 0, 0), ) ipv6_addr_info_2 = ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ipv4_addr_info = ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("107.6.106.83", 80), ) addr_info: List[AddrInfoType] = [ipv6_addr_info, ipv6_addr_info_2, ipv4_addr_info] addr_info_copy = addr_info.copy() remove_addr_infos( addr_info_copy, ("dead:beef:0000:0000:0000:0000:0000:0000", 80, 0, 0) ) assert addr_info_copy == [ipv6_addr_info_2, ipv4_addr_info] remove_addr_infos( addr_info_copy, ("dead:aaaa:0000:0000:0000:0000:0000:0000", 80, 0, 0) ) assert addr_info_copy == [ipv4_addr_info] with pytest.raises( ValueError, match=r"Address \('107.6.106.2', 80\) not found in addr_infos" ): remove_addr_infos(addr_info_copy, ("107.6.106.2", 80)) assert addr_info_copy == [ipv4_addr_info] def test_addr_to_addr_infos(): """Test addr_to_addr_infos.""" assert addr_to_addr_infos(("1.2.3.4", 43)) == [ ( socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("1.2.3.4", 43), ) ] assert addr_to_addr_infos( ("dead:aaaa:0000:0000:0000:0000:0000:0000", 80, 0, 0) ) == [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa:0000:0000:0000:0000:0000:0000", 80, 0, 0), ) ] assert addr_to_addr_infos(("dead:aaaa::", 80, 0, 0)) == [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ] assert addr_to_addr_infos(("dead:aaaa::", 80)) == [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 0, 0), ) ] assert addr_to_addr_infos(("dead:aaaa::", 80, 1)) == [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 1, 0), ) ] assert addr_to_addr_infos(("dead:aaaa::", 80, 1, 1)) == [ ( socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", ("dead:aaaa::", 80, 1, 1), ) ] assert addr_to_addr_infos(None) is None