diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 82cabe3b4..d8da62a08 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,20 +1,13 @@ -FROM mcr.microsoft.com/devcontainers/python:1-3.11-bookworm +ARG UV_VERSION=latest -RUN \ - apt update && apt install bash-completion -y && \ - pip install pre-commit && \ - curl -sSL https://install.python-poetry.org | POETRY_HOME=/home/vscode/.local python3 - +FROM ghcr.io/astral-sh/uv:$UV_VERSION AS uv -RUN \ - echo >> /home/vscode/.bashrc && \ - # add completions to bashrc - # see how ubuntu does it for reference: - # https://git.launchpad.net/ubuntu/+source/base-files/tree/share/dot.bashrc - # https://stackoverflow.com/a/68566555 - echo 'if [ -f /etc/bash_completion ] && ! shopt -oq posix; then' >> /home/vscode/.bashrc && \ - echo ' . /etc/bash_completion' >> /home/vscode/.bashrc && \ - echo 'fi' >> /home/vscode/.bashrc && \ - echo >> /home/vscode/.bashrc && \ - echo '. <(poetry completions)' >> /home/vscode/.bashrc +FROM mcr.microsoft.com/vscode/devcontainers/base:debian + +ENV PYTHONDONTWRITEBYTECODE=True \ + PYTHONUNBUFFERED=True \ + UV_LINK_MODE=copy + +COPY --from=uv --chown=vscode: /uv /uvx /bin/ diff --git a/.devcontainer/commands/post-create-command.sh b/.devcontainer/commands/post-create-command.sh index 58dc0f754..353a30bd5 100755 --- a/.devcontainer/commands/post-create-command.sh +++ b/.devcontainer/commands/post-create-command.sh @@ -1,4 +1,4 @@ echo "Running post-create-command.sh" pre-commit install -poetry install --all-extras +uv sync --all-extras diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index da44489a1..67cbe97f1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "build": { // Path is relative to the devcontainer.json file. - // We prebuild the image to get poetry into the image + // We prebuild the image to get uv into the image // This saves the user a bit of time, when re-opening containers "dockerfile": "Dockerfile" }, diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 0cd2cc7e5..cfd8ce2af 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.9.0" + ".": "4.15.0" } diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1b1f6b92e..f0e2346dd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,17 +23,15 @@ We recommend following these steps: You need to have the following tools available to you: - `make` - You'll need a GNU Make for common developer activities -- `poetry` - This is the primary package manager for the project -- `pyenv` **Recommended**: For installing python versions for your system. - Poetry infers the current latest version from what it can find on the `PATH` so you are still fine if you don't use `pyenv`. +- `uv` - This is the primary package manager for the project ### Build and test -- Run `make install` to get `poetry` to install all dependencies and set up `pre-commit` +- Run `make install` to get `uv` to install all dependencies and set up `pre-commit` - **Recommended**: Run `make` or `make help` to see other commands available to you. - After this, you should have a working virtual environment and proceed with writing code with your favourite IDE -- **TIP**: You can run `make core/tests` or `make module//tests` to run the tests specifically for that to speed up feedback cycles +- **TIP**: You can run `make core/tests` or `make community//tests` to run the tests specifically for that to speed up feedback cycles - You can also run `make lint` to run the `pre-commit` for the entire codebase. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index b05282d90..5ef9b0e82 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -31,9 +31,9 @@ uname -a echo "------" docker info echo "------" -poetry run python --version +uv run python --version echo "------" -poetry show --tree +uv tree ``` ```bash diff --git a/.github/PULL_REQUEST_TEMPLATE/new_container.md b/.github/PULL_REQUEST_TEMPLATE/new_container.md index c64d66785..5b028dc07 100644 --- a/.github/PULL_REQUEST_TEMPLATE/new_container.md +++ b/.github/PULL_REQUEST_TEMPLATE/new_container.md @@ -20,17 +20,13 @@ It helps reduce unnecessary work for you and the maintainers! So please use `fix(postgres):` or `fix(my_new_vector_db):` if you want to add or modify community modules. This may change in the future if we have a separate package released with community modules. - [ ] Your PR allows maintainers to edit your branch, this will speed up resolving minor issues! -- [ ] The new container is implemented under `modules/*` - - Your module follows [PEP 420](https://peps.python.org/pep-0420/) with implicit namespace packages - (if unsure, look at other existing community modules) - - Your package namespacing follows `testcontainers..*` - and you DO NOT have an `__init__.py` above your module's level. - - Your module has its own tests under `modules/*/tests` - - Your module has a `README.rst` and hooks in the `.. auto-class` and `.. title` of your container +- [ ] The new container is implemented under `src/testcontainers/community//` + - Your package namespacing follows `testcontainers.community..*`. + - Your module has its own tests under `tests/community//` + - Your module has a `docs/community/.rst` with the `.. autoclass` and `.. title` of your container - Implement the new feature (typically in `__init__.py`) and corresponding tests. - [ ] Your module is added in `pyproject.toml` - - it is declared under `tool.poetry.packages` - see other community modules - - it is declared under `tool.poetry.extras` with the same name as your module name, + - it is declared under `project.optional-dependencies` with the same name as your module name, we still prefer adding _NO EXTRA DEPENDENCIES_, meaning `mymodule = []` is the preferred addition (see the notes at the bottom) - [ ] Your branch is up-to-date (or your branch will be rebased with `git rebase`) diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml index de8700062..432454fb8 100644 --- a/.github/actions/setup-env/action.yml +++ b/.github/actions/setup-env/action.yml @@ -4,17 +4,13 @@ description: set up the python environment inputs: python-version: description: "The python version to install and use" - default: "3.12" # we default to latest supported + default: "3.14" # we default to latest supported required: false runs: using: composite steps: - - name: Setup Poetry - run: pipx install poetry - shell: bash - - name: Setup python ${{ inputs.python-version }} - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v7 with: python-version: ${{ inputs.python-version }} - cache: poetry diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..6483b1dd7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci-community.yml b/.github/workflows/ci-community.yml index caebace06..68d847f88 100644 --- a/.github/workflows/ci-community.yml +++ b/.github/workflows/ci-community.yml @@ -1,16 +1,22 @@ -# Contrinuous Integration for community modules +# Continuous Integration for community modules name: modules +env: + UV_FROZEN: "1" # use lock file as is + UV_NO_EDITABLE: "1" # test the build package, not the dev installation + on: push: branches: [ main ] paths: - - "modules/**" + - "src/testcontainers/community/**" + - "tests/community/**" pull_request: branches: [ main ] paths: - - "modules/**" + - "src/testcontainers/community/**" + - "tests/community/**" jobs: track-modules: @@ -19,24 +25,23 @@ jobs: - name: Checkout contents uses: actions/checkout@v4 with: - fetch-depth: 0 # recommended by tj-actions/changed-files + fetch-depth: 0 - name: Get changed files id: changed-files - uses: tj-actions/changed-files@v42 + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 with: - path: "./modules" - diff_relative: true - dir_names: true - dir_names_exclude_current_dir: true - json: true + base: ${{ github.ref }} + list-files: 'json' + filters: | + modules: + - '**' - name: Compute modules from files id: compute-changes run: | - modules=$(echo "${{ steps.changed-files.outputs.all_changed_files }}" | jq '.[] | split("/") | first' | jq -s -c '. | unique') - echo "computed_modules=$modules" - echo "computed_modules=$modules" >> $GITHUB_OUTPUT + python3 scripts/compute_modules.py '${{ steps.changed-files.outputs.modules_files }}' outputs: changed_modules: ${{ steps.compute-changes.outputs.computed_modules }} + test: runs-on: ubuntu-22.04 needs: [track-modules] @@ -44,7 +49,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.13", "3.14"] module: ${{ fromJSON(needs.track-modules.outputs.changed_modules) }} steps: - name: Checkout contents @@ -54,8 +59,8 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: poetry install -E ${{ matrix.module }} + run: uv sync --extra ${{ matrix.module }} - name: Run tests - run: make modules/${{ matrix.module }}/tests + run: make community/${{ matrix.module }}/tests - name: Run doctests - run: make modules/${{ matrix.module }}/doctests + run: make community/${{ matrix.module }}/doctests diff --git a/.github/workflows/ci-core.yml b/.github/workflows/ci-core.yml index 4ee4f4a59..2265feb73 100644 --- a/.github/workflows/ci-core.yml +++ b/.github/workflows/ci-core.yml @@ -1,7 +1,11 @@ -# Contrinuous Integration for the core package +# Continuous Integration for the core package name: core +env: + UV_FROZEN: "1" # use lock file as is + UV_NO_EDITABLE: "1" # test the build package, not the dev installation + on: push: branches: [main] @@ -14,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.13", "3.14"] steps: - uses: actions/checkout@v4 - name: Set up Python @@ -22,13 +26,11 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: poetry install --all-extras + run: uv sync --all-extras - name: Run twine check - run: poetry build && poetry run twine check dist/*.tar.gz + run: uv build && uv run twine check dist/* - name: Run tests run: make core/tests - - name: Rename coverage file - run: mv .coverage .coverage.${{ matrix.python-version}} - name: "Save coverage artifact" uses: actions/upload-artifact@v4 with: @@ -47,7 +49,7 @@ jobs: - name: Set up Python uses: ./.github/actions/setup-env - name: Install Python dependencies - run: poetry install --all-extras + run: uv sync --all-extras - name: "Download coverage artifacts" uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index f9da3b409..f87028dc0 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -1,7 +1,11 @@ -# Contrinuous Integration for the core package +# Continuous Integration for the core package name: lint +env: + UV_FROZEN: "1" # use lock file as is + UV_NO_EDITABLE: "1" # test the build package, not the dev installation + on: push: branches: [main] @@ -15,9 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Env uses: ./.github/actions/setup-env - with: - python-version: "3.9" # the pre-commit is hooked in as 3.9 - - name: Install Python dependencies - run: poetry install --no-interaction - name: Execute pre-commit handler - run: poetry run pre-commit run -a + continue-on-error: true + run: | + uv run pre-commit run --all-files diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index e27c89ad0..000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: docs - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: ./.github/actions/setup-env - with: - python-version: "3.11" - - name: Install Python dependencies - run: poetry install --all-extras - - name: Build documentation - run: make docs diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 9176c6747..7937fa439 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,5 +1,9 @@ name: Release Please +env: + UV_FROZEN: "1" # use lock file as is + UV_NO_EDITABLE: "1" # test the build package, not the dev installation + on: push: branches: [ main ] @@ -28,8 +32,8 @@ jobs: - name: Set up Python uses: ./.github/actions/setup-env - name: build package - run: poetry build - # this action uploads packages from the `dist/` directory, which poetry has built in the previous step + run: uv build + # this action uploads packages from the `dist/` directory, which uv has built in the previous step # usable once we set up trusted publishing, see https://docs.pypi.org/trusted-publishers/using-a-publisher/ - name: push package uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore index 18837562c..0ed5b1619 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,7 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ -.coverage +.coverage* .cache nosetests.xml coverage.xml @@ -72,3 +72,6 @@ venv .python-version .env .github-token + +# docs build +site/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5808a0000..46b2ee963 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: end-of-file-fixer - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.3.5' + rev: 'v0.11.5' hooks: - id: ruff # Explicitly setting config to prevent Ruff from using `pyproject.toml` in sub packages. @@ -18,13 +18,13 @@ repos: - id: ruff-format args: [ '--config', 'pyproject.toml' ] -# - repo: local -# hooks: -# - id: mypy -# name: mypy -# entry: poetry run mypy -# args: ["--config-file", "pyproject.toml"] -# files: "core" # start with the core being type checked -# language: system -# types: [ python ] -# require_serial: true + - repo: local + hooks: + - id: mypy + name: mypy + entry: uv run mypy + args: ["--config-file", "pyproject.toml"] + files: "core" # start with the core being type checked + language: system + types: [ python ] + require_serial: true diff --git a/.readthedocs.yml b/.readthedocs.yml index 8675d8c42..5747abd36 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,19 +1,21 @@ # Read the Docs configuration file (see https://docs.readthedocs.io/en/stable/config-file/v2.html # for details). - +# https://docs.readthedocs.com/platform/stable/build-customization.html#install-dependencies-with-uv version: 2 sphinx: - configuration: conf.py + configuration: docs/conf.py build: - os: ubuntu-22.04 - tools: - python: "3.10" - - # https://github.com/readthedocs/readthedocs.org/issues/4912#issuecomment-1143587902s - jobs: - post_install: - - pip install poetry==1.7.1 # match version from poetry.lock - - poetry config virtualenvs.create false - - poetry install --all-extras + os: ubuntu-24.04 + tools: + python: "3.13" + jobs: + pre_create_environment: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + create_environment: + - uv venv "${READTHEDOCS_VIRTUALENV_PATH}" + install: + - UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --frozen --all-extras --group docs diff --git a/CHANGELOG.md b/CHANGELOG.md index c6cb68563..18e6343e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,271 @@ # Changelog +## [4.15.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.15.0-rc4...testcontainers-v4.15.0) (2026-07-24) + + +### Bug Fixes + +* **cratedb:** add CrateDB community module ([#1051](https://github.com/testcontainers/testcontainers-python/issues/1051)) ([0976c7e](https://github.com/testcontainers/testcontainers-python/commit/0976c7e7ec3455e90731c84d4ab65de378d350d7)) + +## [4.15.0-rc4](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.15.0-rc3...testcontainers-v4.15.0-rc4) (2026-06-11) + + +### Features + +* extended configuration options for container.exec method ([#1050](https://github.com/testcontainers/testcontainers-python/issues/1050)) ([7dee471](https://github.com/testcontainers/testcontainers-python/commit/7dee471547a9b659728ca6f87c20fa5fa35fbe58)) +* **main:** enable typing for complete package ([237be27](https://github.com/testcontainers/testcontainers-python/commit/237be275fdca346f5ee53cee336fda2501e2b578)) +* **main:** make legacy imports available with deprecation notice ([ab6cca8](https://github.com/testcontainers/testcontainers-python/commit/ab6cca8e99e9b564aefddfa14edbc48a27d5a7bc)) +* **test:** start working on parallel-running tests (WIP!) ([2d24429](https://github.com/testcontainers/testcontainers-python/commit/2d2442929334dd8f1e6128df0ec6172ca08451af)) + + +### Bug Fixes + +* **arangodb:** replace deprecated add_hash_index with add_persistent_index ([50bb202](https://github.com/testcontainers/testcontainers-python/commit/50bb20251ca09dac715727f486b09b7d810e93fd)) +* **aws:** wrong path of test ([38089b0](https://github.com/testcontainers/testcontainers-python/commit/38089b06ac0ed69804807992c26fff6a03528b14)) +* **ci:** correct coverage paths ([b21eccd](https://github.com/testcontainers/testcontainers-python/commit/b21eccdee23504a5a28159f62c39f126c02b544e)) +* **ci:** fix community test selection ([5287fc8](https://github.com/testcontainers/testcontainers-python/commit/5287fc81d4a90c5c4a447fb19c18b0e37a4d3c12)) +* **ci:** ignore [@overload](https://github.com/overload) in coverage ([592c6d1](https://github.com/testcontainers/testcontainers-python/commit/592c6d17e50ee83b7e390dc56197d2b214c856e5)) +* **ci:** ignore TYPE_CHECKING block in coverage ([3bdc561](https://github.com/testcontainers/testcontainers-python/commit/3bdc561c0c03aad955e13b4e357cec74a0ebf5f3)) +* **core/registry:** use relative import for _LocalRegistryContainer ([961e3d1](https://github.com/testcontainers/testcontainers-python/commit/961e3d1f30548f6408dc5a380badab13ec42568f)) +* **core:** make is_podman respect resolved docker host ([#1048](https://github.com/testcontainers/testcontainers-python/issues/1048)) ([6018da3](https://github.com/testcontainers/testcontainers-python/commit/6018da37338a24a2407ee16620c78755533a8358)) +* **docs:** update docs reflecting new structure ([9427055](https://github.com/testcontainers/testcontainers-python/commit/9427055a6b832d99246d1ef8f304c4444e7e3a4e)) +* **doctests:** Ensure paths are correct ([f777673](https://github.com/testcontainers/testcontainers-python/commit/f777673df0a1e6a7405e30288d73a3a5a38f6d81)) +* **k3s:** replace tmpfs dict kwarg with with_tmpfs_mount() ([4d1da0c](https://github.com/testcontainers/testcontainers-python/commit/4d1da0cc99200c4224d8788b9733fc72364d91d6)) +* **keycloak:** disable SSL requirement for master realm after start ([2a85595](https://github.com/testcontainers/testcontainers-python/commit/2a855958f2ae5e50f02fea708090cdde74dfdca8)) +* **main:** adopt ci to new src structure ([dae421d](https://github.com/testcontainers/testcontainers-python/commit/dae421d08dc7c2b02850fc377546576fbbabbf5d)) +* **main:** adopt imports to new structure ([9155677](https://github.com/testcontainers/testcontainers-python/commit/9155677ce124ffe5b5f5a4a4520d8b1d260729e7)) +* **rabbitmq:** declare queue as durable ([2622736](https://github.com/testcontainers/testcontainers-python/commit/2622736a9a23e7ede94ee3f7d879e29b33da0695)) +* **ruff:** add **/*_example.py to per-file-ignores for T201 ([01dbd16](https://github.com/testcontainers/testcontainers-python/commit/01dbd16816852d254bbc2e1c7aafce12738814e7)) +* **sftp:** remove redundant start() calls and fix SSH connection options ([54cef56](https://github.com/testcontainers/testcontainers-python/commit/54cef56ffc53d43300908be5f1353cab02ba066b)) +* **trino:** increase default startup timeout from 30s to 60s ([2940501](https://github.com/testcontainers/testcontainers-python/commit/2940501dcccab30e505978bb4c8e477a884cac16)) +* **weaviate:** update and fix module ([5dd2aa1](https://github.com/testcontainers/testcontainers-python/commit/5dd2aa1892e345410a2b9f4a96c0b2c9eaeed3f7)) + +## [4.15.0-rc3](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.15.0-rc2...testcontainers-v4.15.0-rc3) (2026-06-03) + + +### Features + +* **core:** add Podman compatibility ([#1028](https://github.com/testcontainers/testcontainers-python/issues/1028)) ([37093ab](https://github.com/testcontainers/testcontainers-python/commit/37093ab8e1a86d1aecbad915f1af20934861ac35)) +* **core:** auto-detect DOCKER_HOST from current docker context ([#1026](https://github.com/testcontainers/testcontainers-python/issues/1026)) ([d4b8ff6](https://github.com/testcontainers/testcontainers-python/commit/d4b8ff66197e7eb57708c9579fa1fb41d6988f5e)), closes [#1025](https://github.com/testcontainers/testcontainers-python/issues/1025) + + +### Bug Fixes + +* **core:** add container logs to TimeoutError when wait strategy times out ([#1007](https://github.com/testcontainers/testcontainers-python/issues/1007)) ([1fadb40](https://github.com/testcontainers/testcontainers-python/commit/1fadb40c456c3ccd4f13b1b82cd19c3ab22c2687)) +* **core:** close Reaper socket at interpreter exit to prevent ResourceWarning ([#1017](https://github.com/testcontainers/testcontainers-python/issues/1017)) ([d3a1517](https://github.com/testcontainers/testcontainers-python/commit/d3a1517f2c5c2e2a48a4c051b2203b80d9141759)) +* **cosmosdb:** replace distutils.strtobool removed in Python 3.12 ([#1018](https://github.com/testcontainers/testcontainers-python/issues/1018)) ([3e487f4](https://github.com/testcontainers/testcontainers-python/commit/3e487f4bab679204820826cfcbc282cb8fc60008)) +* **mongodb:** Use wait strategy instead of deprecated wait_for_logs ([#943](https://github.com/testcontainers/testcontainers-python/issues/943)) ([c8118e7](https://github.com/testcontainers/testcontainers-python/commit/c8118e7c9832743486115221fd8253bdc8c2a5ec)) +* **mssql:** use `ExecWaitStrategy` instead of deprecated `wait_container_is_ready` ([#1016](https://github.com/testcontainers/testcontainers-python/issues/1016)) ([4c53b89](https://github.com/testcontainers/testcontainers-python/commit/4c53b89a1faad9a592d2bb49123ef3e6bf33fdb9)), closes [#874](https://github.com/testcontainers/testcontainers-python/issues/874) +* **neo4j:** Swap out use of depricated wait utils for wait_strategies ([#1012](https://github.com/testcontainers/testcontainers-python/issues/1012)) ([6deaf3b](https://github.com/testcontainers/testcontainers-python/commit/6deaf3b27e5f8d54ff9ae404dbd092e8014df079)) + +## [4.15.0-rc2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.15.0-rc.1...testcontainers-v4.15.0-rc2) (2026-04-30) + + +### Features + +* **core:** support TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX ([#961](https://github.com/testcontainers/testcontainers-python/issues/961)) ([be9a0a6](https://github.com/testcontainers/testcontainers-python/commit/be9a0a612d934c77bdde20defd4d9f7d5228fb0c)) +* **mongodb:** Add Atlas Local for MongoDb ([#873](https://github.com/testcontainers/testcontainers-python/issues/873)) ([73aeb43](https://github.com/testcontainers/testcontainers-python/commit/73aeb43c18d56993d7c2626fb598a01842a91c35)) +* support with_copy_to ([#976](https://github.com/testcontainers/testcontainers-python/issues/976)) ([59ec1ce](https://github.com/testcontainers/testcontainers-python/commit/59ec1ce6dc7d54fa7f4b3c69f5bf674dfd19bfc0)) +* **valkey:** add Valkey module ([#947](https://github.com/testcontainers/testcontainers-python/issues/947)) ([fc09dc1](https://github.com/testcontainers/testcontainers-python/commit/fc09dc17bccd45d57d92f12c0de26b99ab1ccecf)) + + +### Bug Fixes + +* **azurite:** use `HttpWaitStrategy` instead of deprecated `wait_container_is_ready` ([#1003](https://github.com/testcontainers/testcontainers-python/issues/1003)) ([9fe6b07](https://github.com/testcontainers/testcontainers-python/commit/9fe6b074852e5d6f1df2942bda52ee0557e5cb32)), closes [#874](https://github.com/testcontainers/testcontainers-python/issues/874) +* fix pr [#961](https://github.com/testcontainers/testcontainers-python/issues/961) ([#1011](https://github.com/testcontainers/testcontainers-python/issues/1011)) ([8eff908](https://github.com/testcontainers/testcontainers-python/commit/8eff90851eecaf5720021d63e852a927c47f978c)) + +## [4.15.0-rc.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.14.2...testcontainers-v4.15.0-rc.1) (2026-04-07) + + +### Features + +* **compose:** add structured container inspect information ([#897](https://github.com/testcontainers/testcontainers-python/issues/897)) ([58459a1](https://github.com/testcontainers/testcontainers-python/commit/58459a13a1523c5dec8b21b0e16ae1afdce48156)) +* **core:** support SSH-based DOCKER_HOST ([#993](https://github.com/testcontainers/testcontainers-python/issues/993)) ([d48115d](https://github.com/testcontainers/testcontainers-python/commit/d48115def127644964d4d2b09a38e3f4492cc43c)) +* **generic:** Reintroducing the generic SQL module ([#892](https://github.com/testcontainers/testcontainers-python/issues/892)) ([2ca2321](https://github.com/testcontainers/testcontainers-python/commit/2ca2321ada12e09d491280c8ec855bf8511de7c2)) +* **keycloak:** support for relative path and management relative path ([#982](https://github.com/testcontainers/testcontainers-python/issues/982)) ([898faf6](https://github.com/testcontainers/testcontainers-python/commit/898faf6a5955698958be6e8cfd32b87323d62a44)) +* **mqtt:** MosquittoContainer: Add version 2.1.2 ([#978](https://github.com/testcontainers/testcontainers-python/issues/978)) ([af382f7](https://github.com/testcontainers/testcontainers-python/commit/af382f74e82bdcb14eac3f4e04a83432ae9beeba)) + + +### Bug Fixes + +* **azurite:** make visible to type checkers ([#927](https://github.com/testcontainers/testcontainers-python/issues/927)) ([baa5668](https://github.com/testcontainers/testcontainers-python/commit/baa566814b22fa922094a625ff92037cbe8bd93f)) +* **clickhouse:** add `HttpWaitStrategy` instead of deprecated `wait_container_is_ready` ([#962](https://github.com/testcontainers/testcontainers-python/issues/962)) ([8034541](https://github.com/testcontainers/testcontainers-python/commit/803454147c03418b7b06601d251eb491a2cd79cf)) +* **compose:** return type in get_service_port docstring ([#939](https://github.com/testcontainers/testcontainers-python/issues/939)) ([fed65fe](https://github.com/testcontainers/testcontainers-python/commit/fed65fe14507020007c115c535364c90d4bbdde9)) +* **core:** Refactor copy file ([#996](https://github.com/testcontainers/testcontainers-python/issues/996)) ([0e0bb24](https://github.com/testcontainers/testcontainers-python/commit/0e0bb24a2bddfd8a03bebdfc3b9ff8cf8c78092b)) +* **core:** wait for ryuk more reliably, improve tests: long_running, filter logs ([#984](https://github.com/testcontainers/testcontainers-python/issues/984)) ([b12ae13](https://github.com/testcontainers/testcontainers-python/commit/b12ae13e589a4ffe326c162a38df56eb30521d69)) +* **generic:** Migrate ServerContainer from deprecated decorator to HttpWaitStrategy ([#971](https://github.com/testcontainers/testcontainers-python/issues/971)) ([460b0d8](https://github.com/testcontainers/testcontainers-python/commit/460b0d8a09635068815ea8c5c5a4e4cc1e3dfea7)) +* **kafka:** Use wait strategy instead of deprecated wait_for_logs ([#903](https://github.com/testcontainers/testcontainers-python/issues/903)) ([87332c1](https://github.com/testcontainers/testcontainers-python/commit/87332c1332a30b673aac919b48e296e21f2c1baf)) +* **postgres:** add py.typed marker to postgres module ([#849](https://github.com/testcontainers/testcontainers-python/issues/849)) ([c8a5bbd](https://github.com/testcontainers/testcontainers-python/commit/c8a5bbdbab137e6dc5af9a7224e65972665ec84d)) +* **qdrant:** migrate Qdrant from deprecated decorator. ([#963](https://github.com/testcontainers/testcontainers-python/issues/963)) ([407f798](https://github.com/testcontainers/testcontainers-python/commit/407f79825be97865010dc0119cdfe3498a609a08)) +* **redis:** Use wait strategy instead of deprecated decorator ([#914](https://github.com/testcontainers/testcontainers-python/issues/914)) ([e25713a](https://github.com/testcontainers/testcontainers-python/commit/e25713a300eda6a14973d2465590d2318dcc375d)) +* **sftp:** Avoid using wait_for_logs in module. ([#995](https://github.com/testcontainers/testcontainers-python/issues/995)) ([83157eb](https://github.com/testcontainers/testcontainers-python/commit/83157eb4acd931949cfec3d2a84db0a61685e739)) + +## [4.14.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.14.1...testcontainers-v4.14.2) (2026-03-18) + + +### Features + +* **kafka:** allow configurable listener name and security protocol ([#966](https://github.com/testcontainers/testcontainers-python/issues/966)) ([44dd40b](https://github.com/testcontainers/testcontainers-python/commit/44dd40b48c3a5020b487bae5d460124d9e594ac3)) + +## [4.14.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.14.0...testcontainers-v4.14.1) (2026-01-31) + + +### Bug Fixes + +* Allow passing in a custom wait strategy string in MySQL, Cassandra, Kafka and Trino ([#953](https://github.com/testcontainers/testcontainers-python/issues/953)) ([be4d09e](https://github.com/testcontainers/testcontainers-python/commit/be4d09ecb3f65089d06fbd1ab9d4f12e9009ed8b)) +* **compose:** expose useful compose options ([#951](https://github.com/testcontainers/testcontainers-python/issues/951)) ([183e1aa](https://github.com/testcontainers/testcontainers-python/commit/183e1aa1bcd684d36d3f5b52b28965c851f5436f)) +* **core:** bring back dind tests ([7337266](https://github.com/testcontainers/testcontainers-python/commit/7337266c73f05f003378ff483a5e3b565a1e86c5)) +* **core:** Use WaitStrategy internally for wait_for function ([#942](https://github.com/testcontainers/testcontainers-python/issues/942)) ([e323317](https://github.com/testcontainers/testcontainers-python/commit/e323317838552a9f8046b2a8e24a03c07ff8890e)) +* **nats:** add support for jetstream ([#938](https://github.com/testcontainers/testcontainers-python/issues/938)) ([49c9af8](https://github.com/testcontainers/testcontainers-python/commit/49c9af8cf542feb5df9ec389d554edd7645a4dc4)) +* Support Elasticsearch 9.x ([#881](https://github.com/testcontainers/testcontainers-python/issues/881)) ([f690e88](https://github.com/testcontainers/testcontainers-python/commit/f690e88e866ef3ff30ba2cd18958fc1fc07f89c8)), closes [#860](https://github.com/testcontainers/testcontainers-python/issues/860) + +## [4.14.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.13.3...testcontainers-v4.14.0) (2026-01-07) + + +### Features + +* Add ExecWaitStrategy and migrate Postgres from deprecated decorator ([#935](https://github.com/testcontainers/testcontainers-python/issues/935)) ([2d9eee3](https://github.com/testcontainers/testcontainers-python/commit/2d9eee30442ec8adbf4a42fcd308cd6377b41c06)) + + +### Bug Fixes + +* add ruff to deps ([#919](https://github.com/testcontainers/testcontainers-python/issues/919)) ([5853d32](https://github.com/testcontainers/testcontainers-python/commit/5853d326bb4e9631b7c58355c53ff7fc3ecab92d)) +* **cassandra,mysqk,kafka:** Use wait strategy instead of deprecated wait_for_logs ([#945](https://github.com/testcontainers/testcontainers-python/issues/945)) ([b7791b9](https://github.com/testcontainers/testcontainers-python/commit/b7791b945134940c3185baa3eab009f06d0338a9)) +* **core:** recreate poetry lockfile with latest versions of libraries ([#946](https://github.com/testcontainers/testcontainers-python/issues/946)) ([9a97385](https://github.com/testcontainers/testcontainers-python/commit/9a9738575ec3f831c78512b10b990e416eacad03)) +* **elasticsearch:** Use wait strategy instead of deprecated decorator ([#915](https://github.com/testcontainers/testcontainers-python/issues/915)) ([c785ecd](https://github.com/testcontainers/testcontainers-python/commit/c785ecdca20b51e077ab23ed61ae123c643a0627)) +* **minio:** minio client requires kwargs now ([#933](https://github.com/testcontainers/testcontainers-python/issues/933)) ([37f5902](https://github.com/testcontainers/testcontainers-python/commit/37f590278f23851c4f8244d4add7aa6f8ed3bc62)) +* **minio:** Use wait strategy instead of deprecated decorator ([#899](https://github.com/testcontainers/testcontainers-python/issues/899)) ([febccb7](https://github.com/testcontainers/testcontainers-python/commit/febccb78b5b4b00d2a3bda27f09e6b4d4c9dfde3)) + +## [4.13.3](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.13.2...testcontainers-v4.13.3) (2025-11-14) + + +### Bug Fixes + +* do not require consumer of library to state nonsupport for py4 ([#912](https://github.com/testcontainers/testcontainers-python/issues/912)) ([f608df9](https://github.com/testcontainers/testcontainers-python/commit/f608df908f87674484b106831d8e8019fdc1927c)) +* **docs:** Update dependencies for docs ([#900](https://github.com/testcontainers/testcontainers-python/issues/900)) ([3f66784](https://github.com/testcontainers/testcontainers-python/commit/3f667847a0d9a893e4f15481d81d131817382d5c)) +* support python 3.14!!! - ([#917](https://github.com/testcontainers/testcontainers-python/issues/917)) ([f76e982](https://github.com/testcontainers/testcontainers-python/commit/f76e982ca6f40d185d6f430be0a62cd26afbf7e6)) + +## [4.13.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.13.1...testcontainers-v4.13.2) (2025-10-07) + + +### Bug Fixes + +* **core:** Fix issues with doctests ([#893](https://github.com/testcontainers/testcontainers-python/issues/893)) ([2e4d80a](https://github.com/testcontainers/testcontainers-python/commit/2e4d80ade5a2048c8bc79d7a2438004b8e0954e4)) +* **core:** waiting improvements + remove decorators in core ([#894](https://github.com/testcontainers/testcontainers-python/issues/894)) ([f93f379](https://github.com/testcontainers/testcontainers-python/commit/f93f379380a9de769fe6a1e1168622865cdf613d)) +* issue [#889](https://github.com/testcontainers/testcontainers-python/issues/889) by changing the annotated return type of `waiting_for` to `Self`. ([#890](https://github.com/testcontainers/testcontainers-python/issues/890)) ([fe941b1](https://github.com/testcontainers/testcontainers-python/commit/fe941b17bb97aad15dc4844996f166c9308f4476)) +* **mongo:** mongo start waiting forever for old mongo versions ([#783](https://github.com/testcontainers/testcontainers-python/issues/783)) ([1388612](https://github.com/testcontainers/testcontainers-python/commit/13886120e9cb72666a40ea3691ea13e584805918)) +* **redpanda:** copy the startup script to a path that can be written … ([#867](https://github.com/testcontainers/testcontainers-python/issues/867)) ([e6b976d](https://github.com/testcontainers/testcontainers-python/commit/e6b976de43fc6fe90d7131fd25983f9c6b08429b)) +* **trino:** Remove deprecated class and decorator from Trino container ([#895](https://github.com/testcontainers/testcontainers-python/issues/895)) ([bb646e9](https://github.com/testcontainers/testcontainers-python/commit/bb646e903236a1df72bc38dbb47d1dba95527198)) + +## [4.13.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.13.0...testcontainers-v4.13.1) (2025-09-24) + + +### Bug Fixes + +* **core:** Added 'compose' in compose_command_property ([#879](https://github.com/testcontainers/testcontainers-python/issues/879)) ([769b7b6](https://github.com/testcontainers/testcontainers-python/commit/769b7b688fe254cb8e38e05f453c4e3fe7999ad5)) +* **core:** make sure context manager exits ([#876](https://github.com/testcontainers/testcontainers-python/issues/876)) ([10089f6](https://github.com/testcontainers/testcontainers-python/commit/10089f6e2fe07e53cc47a69521e67a1bf3310065)) + +## [4.13.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.12.0...testcontainers-v4.13.0) (2025-08-27) + + +### Features + +* **azurite:** Enhance connection string generation for network and local access ([#859](https://github.com/testcontainers/testcontainers-python/issues/859)) ([b21e5e3](https://github.com/testcontainers/testcontainers-python/commit/b21e5e38075ddbd71fb4f97e843abc104dec6beb)) +* **core:** add enhanced wait strategies ([#855](https://github.com/testcontainers/testcontainers-python/issues/855)) ([60d21f8](https://github.com/testcontainers/testcontainers-python/commit/60d21f875f49f52e170b0714e8790080a6cb4c71)) +* **core:** DockerCompose: support list of env_files ([#847](https://github.com/testcontainers/testcontainers-python/issues/847)) ([fe206eb](https://github.com/testcontainers/testcontainers-python/commit/fe206eb48ee9e18623761926900bfc33a8a869a7)) + + +### Bug Fixes + +* assert-in-get_container_host_ip-before-start ([#862](https://github.com/testcontainers/testcontainers-python/issues/862)) ([fc4155e](https://github.com/testcontainers/testcontainers-python/commit/fc4155eb70509ba236fff771c2f8973667acb098)) +* **core:** improper reading of .testcontainers.properties ([#863](https://github.com/testcontainers/testcontainers-python/issues/863)) ([350f246](https://github.com/testcontainers/testcontainers-python/commit/350f246a3b6367d727046b8967a63d1c055cf324)) +* **core:** Make TC_POOLING_INTERVAL/sleep_time a float ([#839](https://github.com/testcontainers/testcontainers-python/issues/839)) ([a072f3f](https://github.com/testcontainers/testcontainers-python/commit/a072f3fad46b3b3e7c5bea6255f27b79826aaf5f)) + +## [4.12.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.11.0...testcontainers-v4.12.0) (2025-07-21) + + +### Features + +* **main:** New Testcontainers Python Docs Site ([#822](https://github.com/testcontainers/testcontainers-python/issues/822)) ([a6bdf0e](https://github.com/testcontainers/testcontainers-python/commit/a6bdf0ef84643074fbc7edf3a75936ce3f1d0880)) +* make config monkeypatchable, fix config related startup issues ([#833](https://github.com/testcontainers/testcontainers-python/issues/833)) ([ff6a32d](https://github.com/testcontainers/testcontainers-python/commit/ff6a32db803046db8d89ba5a7157bf573d9f25c2)) +* **modules:** add OpenFGA module ([#762](https://github.com/testcontainers/testcontainers-python/issues/762)) ([0b7b482](https://github.com/testcontainers/testcontainers-python/commit/0b7b482f9ec807e87fd43d1372226fa43eb4ed7c)) +* set multiple variables via keyword args ([#804](https://github.com/testcontainers/testcontainers-python/issues/804)) ([1532df5](https://github.com/testcontainers/testcontainers-python/commit/1532df5e9094d15b9f3e9233e7f5843d8bc24386)) + + +### Bug Fixes + +* **core:** mypy ([#810](https://github.com/testcontainers/testcontainers-python/issues/810)) ([b816762](https://github.com/testcontainers/testcontainers-python/commit/b816762b9a548033b065c3f46267c289a560f6ed)) +* Enable mypy in the CI ([#842](https://github.com/testcontainers/testcontainers-python/issues/842)) ([ef65bd1](https://github.com/testcontainers/testcontainers-python/commit/ef65bd113b564bce614aaf6df13bbf5339b9bc58)) +* just use the getLogger API and do not override logger settings ([#836](https://github.com/testcontainers/testcontainers-python/issues/836)) ([f467c84](https://github.com/testcontainers/testcontainers-python/commit/f467c842b851613b9a087bd5f9a08d8c39577cb8)) + + +### Documentation + +* missing compose html from old docs ([#776](https://github.com/testcontainers/testcontainers-python/issues/776)) ([d749fc6](https://github.com/testcontainers/testcontainers-python/commit/d749fc69b32715742d834c003ee6893e2077753a)) + +## [4.11.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.10.0...testcontainers-v4.11.0) (2025-06-15) + + +### Features + +* **core:** Protocol support for container port bind and expose ([#690](https://github.com/testcontainers/testcontainers-python/issues/690)) ([a0d4317](https://github.com/testcontainers/testcontainers-python/commit/a0d4317643005dde4f344eccbfc56c062e83bf05)) +* DockerContainer initializer to accept its private members as kwargs ([#809](https://github.com/testcontainers/testcontainers-python/issues/809)) ([e7feb53](https://github.com/testcontainers/testcontainers-python/commit/e7feb53fe532b6d32d5d0c5a5d517249f8e7de50)) + + +### Bug Fixes + +* **compose:** use provided docker command instead of default ([#785](https://github.com/testcontainers/testcontainers-python/issues/785)) ([0ae704a](https://github.com/testcontainers/testcontainers-python/commit/0ae704a24de440b715d5f3c11eaa4f18ccd437b5)) +* **core:** Add kwargs to image build ([#708](https://github.com/testcontainers/testcontainers-python/issues/708)) ([cc02f94](https://github.com/testcontainers/testcontainers-python/commit/cc02f9444b41efa62836b21210b07aee1da94d0b)) +* **core:** change with_command type to include list of strings ([#789](https://github.com/testcontainers/testcontainers-python/issues/789)) ([f7c29cb](https://github.com/testcontainers/testcontainers-python/commit/f7c29cb913e4d42d535783c3aa0f3566d4e543bf)) +* **core:** Determine docker socket for rootless docker ([#779](https://github.com/testcontainers/testcontainers-python/issues/779)) ([6817582](https://github.com/testcontainers/testcontainers-python/commit/6817582bf67ed36448b69019ab897c50ae80e7e1)) +* **core:** Typing in docker_client ([#702](https://github.com/testcontainers/testcontainers-python/issues/702)) ([e8bf224](https://github.com/testcontainers/testcontainers-python/commit/e8bf2244c7210e31b34e5fecf2602fdd1b8c0834)) +* **core:** Typing in generic + network ([#700](https://github.com/testcontainers/testcontainers-python/issues/700)) ([2061912](https://github.com/testcontainers/testcontainers-python/commit/2061912e67705be801136f349f372f542a1f262f)) +* **core:** Typing in version ([#701](https://github.com/testcontainers/testcontainers-python/issues/701)) ([9dc2a02](https://github.com/testcontainers/testcontainers-python/commit/9dc2a02ca9b9ffbaacfd7de79ec9f78175758ec0)) +* **core:** wait in test core registry ([#812](https://github.com/testcontainers/testcontainers-python/issues/812)) ([b574c0e](https://github.com/testcontainers/testcontainers-python/commit/b574c0e0a11d57c8c56aef448292f8c2fc233078)) +* **modules:** fix cosmosdb failure ([#827](https://github.com/testcontainers/testcontainers-python/issues/827)) ([dafcbed](https://github.com/testcontainers/testcontainers-python/commit/dafcbed7608e857bebcdd0b4638bec27abadc693)) +* **modules:** update chroma version ([#826](https://github.com/testcontainers/testcontainers-python/issues/826)) ([b7d41dd](https://github.com/testcontainers/testcontainers-python/commit/b7d41ddc5742dd380b6e01c712a02b044a64cbb3)) +* **rabbitmq:** correct pika pypi reference ([#817](https://github.com/testcontainers/testcontainers-python/issues/817)) ([e90d308](https://github.com/testcontainers/testcontainers-python/commit/e90d30826fb7d7cf3cc7db39a86465d448aaa6e0)) +* **registry:** module typed ([#811](https://github.com/testcontainers/testcontainers-python/issues/811)) ([6b11268](https://github.com/testcontainers/testcontainers-python/commit/6b1126884c82529a93bd55030374d322dd0870bc)) +* use connection mode override function in config ([#775](https://github.com/testcontainers/testcontainers-python/issues/775)) ([ab2a1ab](https://github.com/testcontainers/testcontainers-python/commit/ab2a1abd957ffb35719f673a7674df83287f1545)), closes [#774](https://github.com/testcontainers/testcontainers-python/issues/774) + +## [4.10.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.9.2...testcontainers-v4.10.0) (2025-04-02) + + +### Features + +* Add SocatContainer ([#795](https://github.com/testcontainers/testcontainers-python/issues/795)) ([2f9139c](https://github.com/testcontainers/testcontainers-python/commit/2f9139ca3ea9fba36325373b63635a5f539a3003)) + + +### Bug Fixes + +* **ollama:** make device request a list ([#799](https://github.com/testcontainers/testcontainers-python/issues/799)) ([9497a45](https://github.com/testcontainers/testcontainers-python/commit/9497a45c39d13761aa3dd30dd5605676cbbe4b46)) +* **security:** Update track-modules job ([#787](https://github.com/testcontainers/testcontainers-python/issues/787)) ([f979525](https://github.com/testcontainers/testcontainers-python/commit/f97952505eba089f9cbbc979f8091dafbf520669)) + +## [4.9.2](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.9.1...testcontainers-v4.9.2) (2025-02-26) + + +### Bug Fixes + +* Change env var disabling OpenSearch security plugin ([#773](https://github.com/testcontainers/testcontainers-python/issues/773)) ([2620d7f](https://github.com/testcontainers/testcontainers-python/commit/2620d7fb1157caa18c3bef4bf2f9b3b79cd2f075)) +* **core:** create_label test ([#771](https://github.com/testcontainers/testcontainers-python/issues/771)) ([7517297](https://github.com/testcontainers/testcontainers-python/commit/751729722a013b46f67c09b4318b1b3d92b98008)) +* **core:** multiple container start invocations with custom labels ([#769](https://github.com/testcontainers/testcontainers-python/issues/769)) ([3e783a8](https://github.com/testcontainers/testcontainers-python/commit/3e783a80aa11b9c87201404a895d922624f0d451)) +* **keycloak:** Fixed Keycloak testcontainer for latest version v26.1.0 ([#766](https://github.com/testcontainers/testcontainers-python/issues/766)) ([b1642e9](https://github.com/testcontainers/testcontainers-python/commit/b1642e98c4d349564c4365782d1b58c9810b719a)) +* **scylla:** scylla get cluster method ([#778](https://github.com/testcontainers/testcontainers-python/issues/778)) ([46913c1](https://github.com/testcontainers/testcontainers-python/commit/46913c18a8b6f37bf8dc193828148926b6fc56a8)) + + +### Documentation + +* Fixed typo in CONTRIBUTING.md ([#767](https://github.com/testcontainers/testcontainers-python/issues/767)) ([f0bb0f5](https://github.com/testcontainers/testcontainers-python/commit/f0bb0f54bea83885698bd137e24c397498709362)) + +## [4.9.1](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.9.0...testcontainers-v4.9.1) (2025-01-21) + + +### Bug Fixes + +* milvus healthcheck: use correct requests errors ([#759](https://github.com/testcontainers/testcontainers-python/issues/759)) ([78b137c](https://github.com/testcontainers/testcontainers-python/commit/78b137cfe53fc81eb8d5d858e98610fb6a8792ad)) +* **mysql:** add dialect parameter instead of hardcoded mysql dialect ([#739](https://github.com/testcontainers/testcontainers-python/issues/739)) ([8d77bd3](https://github.com/testcontainers/testcontainers-python/commit/8d77bd3541e1c5e73c7ed5d5bd3c0d7bb617f5c0)) +* **tests:** replace dind-test direct docker usage with sdk ([#750](https://github.com/testcontainers/testcontainers-python/issues/750)) ([ace2a7d](https://github.com/testcontainers/testcontainers-python/commit/ace2a7d143fb80576ddc0859a9106aa8652f2356)) + ## [4.9.0](https://github.com/testcontainers/testcontainers-python/compare/testcontainers-v4.8.2...testcontainers-v4.9.0) (2024-11-26) diff --git a/Dockerfile b/Dockerfile index 865771fa1..2cf139e2e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,11 @@ ARG PYTHON_VERSION=3.10 -FROM python:${PYTHON_VERSION}-slim-bookworm - -ENV POETRY_NO_INTERACTION=1 \ - POETRY_VIRTUALENVS_IN_PROJECT=1 \ - POETRY_VIRTUALENVS_CREATE=1 \ - POETRY_CACHE_DIR=/tmp/poetry_cache +# see https://docs.astral.sh/uv/guides/integration/docker/ for available images +FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-bookworm-slim WORKDIR /workspace -RUN pip install --upgrade pip \ - && apt-get update \ - && apt-get install -y freetds-dev \ - && apt-get install -y make \ - # no real need for keeping this image small at the moment - && :; # rm -rf /var/lib/apt/lists/* - -# install poetry -RUN bash -c 'python -m venv /opt/poetry-venv && source $_/bin/activate && pip install poetry && ln -s $(which poetry) /usr/bin' - -# install dependencies with poetry -COPY pyproject.toml . -COPY poetry.lock . -RUN poetry install --all-extras --with dev --no-root # copy project source COPY . . -# install project with poetry -RUN poetry install --all-extras --with dev +# install dependencies with uv +RUN uv sync --frozen --all-extras diff --git a/Makefile b/Makefile index b5dbe88ea..50540308c 100644 --- a/Makefile +++ b/Makefile @@ -3,46 +3,55 @@ PYTHON_VERSION ?= 3.10 IMAGE = testcontainers-python:${PYTHON_VERSION} -PACKAGES = core $(addprefix modules/,$(notdir $(wildcard modules/*))) -UPLOAD = $(addsuffix /upload,${PACKAGES}) -TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES})) -TESTS_DIND = $(addsuffix -dind,${TESTS}) -DOCTESTS = $(addsuffix /doctests,$(filter-out modules/README.md,${PACKAGES})) +COMMUNITY_MODULES = $(patsubst tests/community/%/,%,$(wildcard tests/community/*/)) +COMMUNITY_TESTS = $(addprefix community/,$(addsuffix /tests,$(COMMUNITY_MODULES))) +COMMUNITY_DOCTESTS = $(addprefix community/,$(addsuffix /doctests,$(COMMUNITY_MODULES))) install: ## Set up the project for development - poetry install --all-extras - poetry run pre-commit install + uv sync --all-extras + uv run pre-commit install build: ## Build the python package - poetry build && poetry run twine check dist/* + uv build && uv run twine check dist/* -tests: ${TESTS} ## Run tests for each package -${TESTS}: %/tests: - poetry run pytest -v --cov=testcontainers.$* $*/tests +tests: core/tests community-tests ## Run all tests + +core/tests: ## Run tests for the core package + uv run coverage run --parallel -m pytest -v tests/core + +community-tests: $(COMMUNITY_TESTS) ## Run tests for all community modules +$(COMMUNITY_TESTS): community/%/tests: + uv run coverage run --parallel -m pytest -v tests/community/$* + +quick-core-tests: ## Run core tests excluding long_running + uv run coverage run --parallel -m pytest -v -m "not long_running" tests/core coverage: ## Target to combine and report coverage. - poetry run coverage combine - poetry run coverage report - poetry run coverage xml - poetry run coverage html + uv run coverage combine + uv run coverage report + uv run coverage xml + uv run coverage html lint: ## Lint all files in the project, which we also run in pre-commit - poetry run pre-commit run -a + uv run pre-commit run --all-files docs: ## Build the docs for the project - poetry run sphinx-build -nW . docs/_build + uv run --all-extras sphinx-build -nW docs docs/_build # Target to build docs watching for changes as per https://stackoverflow.com/a/21389615 -docs-watch : - poetry run sphinx-autobuild . docs/_build # requires 'pip install sphinx-autobuild' +docs-watch: + uv run sphinx-autobuild docs docs/_build # requires 'pip install sphinx-autobuild' -doctests: ${DOCTESTS} ## Run doctests found across the documentation. - poetry run sphinx-build -b doctest . docs/_build +doctests: core/doctests community-doctests ## Run doctests found across the documentation. -${DOCTESTS}: %/doctests: ## Run doctests found for a module. - poetry run sphinx-build -b doctest -c doctests $* docs/_build +core/doctests: ## Run doctests for the core package + uv run --all-extras sphinx-build -b doctest -c doctests docs/core docs/_build + +community-doctests: $(COMMUNITY_DOCTESTS) ## Run doctests for all community modules +$(COMMUNITY_DOCTESTS): community/%/doctests: + uv run --all-extras sphinx-build -b doctest docs docs/_build docs/community/$*.rst clean: ## Remove generated files. @@ -55,7 +64,9 @@ clean-all: clean ## Remove all generated files and reset the local virtual envir rm -rf .venv # Targets that do not generate file-level artifacts. -.PHONY: clean docs doctests image tests ${TESTS} +.PHONY: clean docs doctests core/doctests community-doctests $(COMMUNITY_DOCTESTS) \ + tests core/tests community-tests $(COMMUNITY_TESTS) \ + quick-core-tests install build coverage lint mypy-core mypy-core-report docs-watch # Implements this pattern for autodocumenting Makefiles: @@ -65,3 +76,9 @@ clean-all: clean ## Remove all generated files and reset the local virtual envir .PHONY: help help: ## Display command usage @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +## -------------------------------------- + +.PHONY: serve-docs +serve-docs: + uv run mkdocs serve -f mkdocs.yml -a 127.0.0.1:8000 diff --git a/README.md b/README.md index 43d5d2aa6..ce9289693 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) +[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) ![PyPI - Version](https://img.shields.io/pypi/v/testcontainers) [![PyPI - License](https://img.shields.io/pypi/l/testcontainers.svg)](https://github.com/testcontainers/testcontainers-python/blob/main/LICENSE) @@ -41,6 +41,8 @@ See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more details. ## Configuration +You can set environment variables to configure the library behaviour: + | Env Variable | Example | Description | | --------------------------------------- | --------------------------- | ---------------------------------------------------------------------------------- | | `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE` | `/var/run/docker.sock` | Path to Docker's socket used by ryuk | @@ -48,3 +50,11 @@ See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more details. | `TESTCONTAINERS_RYUK_DISABLED` | `false` | Disable ryuk | | `RYUK_CONTAINER_IMAGE` | `testcontainers/ryuk:0.8.1` | Custom image for ryuk | | `RYUK_RECONNECTION_TIMEOUT` | `10s` | Reconnection timeout for Ryuk TCP socket before Ryuk reaps all dangling containers | + +Alternatively you can set the configuration during runtime: + +```python +from testcontainers.core import testcontainers_config + +testcontainers_config.ryuk_docker_socket = "/home/user/docker.sock" +``` diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..c1600550e --- /dev/null +++ b/codecov.yml @@ -0,0 +1,26 @@ +# https://docs.codecov.com/docs/codecov-yaml +coverage: + status: + project: + default: + target: auto + # Allow small coverage fluctuations without failing the check. + threshold: 1% + # Don't fail codecov if the CI run itself failed (avoids double-noise). + if_ci_failed: success + patch: + default: + # Report patch coverage, but never block the PR on it. + informational: true + +# Recompute the base when code is removed so refactors don't appear to +# regress overall coverage. +codecov: + notify: + after_n_builds: 1 + require_ci_to_pass: true + +comment: + layout: "reach,diff,flags,files" + behavior: default + require_changes: true # only post a PR comment when coverage actually changed diff --git a/core/README.rst b/core/README.rst deleted file mode 100644 index 8cc9a2780..000000000 --- a/core/README.rst +++ /dev/null @@ -1,32 +0,0 @@ -Testcontainers Core -=================== - -:code:`testcontainers-core` is the core functionality for spinning up Docker containers in test environments. - -.. autoclass:: testcontainers.core.container.DockerContainer - -.. autoclass:: testcontainers.core.image.DockerImage - -.. autoclass:: testcontainers.core.generic.DbContainer - -.. raw:: html - -
- -Examples --------- - -Using `DockerContainer` and `DockerImage` to create a container: - -.. doctest:: - - >>> from testcontainers.core.container import DockerContainer - >>> from testcontainers.core.waiting_utils import wait_for_logs - >>> from testcontainers.core.image import DockerImage - - >>> with DockerImage(path="./core/tests/image_fixtures/sample/", tag="test-sample:latest") as image: - ... with DockerContainer(str(image)) as container: - ... delay = wait_for_logs(container, "Test Sample Image") - -The `DockerImage` class is used to build the image from the specified path and tag. -The `DockerContainer` class is then used to create a container from the image. diff --git a/core/testcontainers/compose/__init__.py b/core/testcontainers/compose/__init__.py deleted file mode 100644 index 9af994f30..000000000 --- a/core/testcontainers/compose/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# flake8: noqa -from testcontainers.compose.compose import ( - ContainerIsNotRunning, - NoSuchPortExposed, - PublishedPort, - ComposeContainer, - DockerCompose, -) diff --git a/core/testcontainers/core/config.py b/core/testcontainers/core/config.py deleted file mode 100644 index 110a441ee..000000000 --- a/core/testcontainers/core/config.py +++ /dev/null @@ -1,131 +0,0 @@ -from dataclasses import dataclass, field -from enum import Enum -from logging import warning -from os import environ -from os.path import exists -from pathlib import Path -from typing import Optional, Union - - -class ConnectionMode(Enum): - bridge_ip = "bridge_ip" - gateway_ip = "gateway_ip" - docker_host = "docker_host" - - @property - def use_mapped_port(self) -> bool: - """ - Return true if we need to use mapped port for this connection - - This is true for everything but bridge mode. - """ - if self == self.bridge_ip: - return False - return True - - -MAX_TRIES = int(environ.get("TC_MAX_TRIES", 120)) -SLEEP_TIME = int(environ.get("TC_POOLING_INTERVAL", 1)) -TIMEOUT = MAX_TRIES * SLEEP_TIME - -RYUK_IMAGE: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.8.1") -RYUK_PRIVILEGED: bool = environ.get("TESTCONTAINERS_RYUK_PRIVILEGED", "false") == "true" -RYUK_DISABLED: bool = environ.get("TESTCONTAINERS_RYUK_DISABLED", "false") == "true" -RYUK_DOCKER_SOCKET: str = environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/run/docker.sock") -RYUK_RECONNECTION_TIMEOUT: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s") -TC_HOST_OVERRIDE: Optional[str] = environ.get("TC_HOST", environ.get("TESTCONTAINERS_HOST_OVERRIDE")) - -TC_FILE = ".testcontainers.properties" -TC_GLOBAL = Path.home() / TC_FILE - - -def get_user_overwritten_connection_mode() -> Optional[ConnectionMode]: - """ - Return the user overwritten connection mode. - """ - connection_mode: str | None = environ.get("TESTCONTAINERS_CONNECTION_MODE") - if connection_mode: - try: - return ConnectionMode(connection_mode) - except ValueError as e: - raise ValueError(f"Error parsing TESTCONTAINERS_CONNECTION_MODE: {e}") from e - return None - - -def read_tc_properties() -> dict[str, str]: - """ - Read the .testcontainers.properties for settings. (see the Java implementation for details) - Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later. - - :return: the merged properties from the sources. - """ - tc_files = [item for item in [TC_GLOBAL] if exists(item)] - if not tc_files: - return {} - settings: dict[str, str] = {} - - for file in tc_files: - with open(file) as contents: - tuples = [line.split("=") for line in contents.readlines() if "=" in line] - settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} - return settings - - -_WARNINGS = {"DOCKER_AUTH_CONFIG": "DOCKER_AUTH_CONFIG is experimental, see testcontainers/testcontainers-python#566"} - - -@dataclass -class TestcontainersConfiguration: - max_tries: int = MAX_TRIES - sleep_time: int = SLEEP_TIME - ryuk_image: str = RYUK_IMAGE - ryuk_privileged: bool = RYUK_PRIVILEGED - ryuk_disabled: bool = RYUK_DISABLED - ryuk_docker_socket: str = RYUK_DOCKER_SOCKET - ryuk_reconnection_timeout: str = RYUK_RECONNECTION_TIMEOUT - tc_properties: dict[str, str] = field(default_factory=read_tc_properties) - _docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG")) - tc_host_override: Optional[str] = TC_HOST_OVERRIDE - connection_mode_override: Optional[ConnectionMode] = None - - """ - https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 - if os env TC_HOST is set, use it - """ - - @property - def docker_auth_config(self) -> Optional[str]: - config = self._docker_auth_config - if config and "DOCKER_AUTH_CONFIG" in _WARNINGS: - warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) - return config - - @docker_auth_config.setter - def docker_auth_config(self, value: str) -> None: - if "DOCKER_AUTH_CONFIG" in _WARNINGS: - warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) - self._docker_auth_config = value - - def tc_properties_get_tc_host(self) -> Union[str, None]: - return self.tc_properties.get("tc.host") - - @property - def timeout(self) -> int: - return self.max_tries * self.sleep_time - - -testcontainers_config = TestcontainersConfiguration() - -__all__ = [ - # the public API of this module - "testcontainers_config", - # and all the legacy things that are deprecated: - "MAX_TRIES", - "SLEEP_TIME", - "TIMEOUT", - "RYUK_IMAGE", - "RYUK_PRIVILEGED", - "RYUK_DISABLED", - "RYUK_DOCKER_SOCKET", - "RYUK_RECONNECTION_TIMEOUT", -] diff --git a/core/testcontainers/core/container.py b/core/testcontainers/core/container.py deleted file mode 100644 index f677182f4..000000000 --- a/core/testcontainers/core/container.py +++ /dev/null @@ -1,266 +0,0 @@ -import contextlib -from os import PathLike -from socket import socket -from typing import TYPE_CHECKING, Optional, Union - -import docker.errors -from docker import version -from docker.types import EndpointConfig -from dotenv import dotenv_values -from typing_extensions import Self, assert_never - -from testcontainers.core.config import ConnectionMode -from testcontainers.core.config import testcontainers_config as c -from testcontainers.core.docker_client import DockerClient -from testcontainers.core.exceptions import ContainerConnectException, ContainerStartException -from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID -from testcontainers.core.network import Network -from testcontainers.core.utils import is_arm, setup_logger -from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs - -if TYPE_CHECKING: - from docker.models.containers import Container - -logger = setup_logger(__name__) - - -class DockerContainer: - """ - Basic container object to spin up Docker instances. - - .. doctest:: - - >>> from testcontainers.core.container import DockerContainer - >>> from testcontainers.core.waiting_utils import wait_for_logs - - >>> with DockerContainer("hello-world") as container: - ... delay = wait_for_logs(container, "Hello from Docker!") - """ - - def __init__( - self, - image: str, - docker_client_kw: Optional[dict] = None, - **kwargs, - ) -> None: - self.env = {} - self.ports = {} - self.volumes = {} - self.image = image - self._docker = DockerClient(**(docker_client_kw or {})) - self._container = None - self._command = None - self._name = None - self._network: Optional[Network] = None - self._network_aliases: Optional[list[str]] = None - self._kwargs = kwargs - - def with_env(self, key: str, value: str) -> Self: - self.env[key] = value - return self - - def with_env_file(self, env_file: Union[str, PathLike]) -> Self: - env_values = dotenv_values(env_file) - for key, value in env_values.items(): - self.with_env(key, value) - return self - - def with_bind_ports(self, container: int, host: Optional[int] = None) -> Self: - self.ports[container] = host - return self - - def with_exposed_ports(self, *ports: int) -> Self: - for port in ports: - self.ports[port] = None - return self - - def with_network(self, network: Network) -> Self: - self._network = network - return self - - def with_network_aliases(self, *aliases) -> Self: - self._network_aliases = aliases - return self - - def with_kwargs(self, **kwargs) -> Self: - self._kwargs = kwargs - return self - - def maybe_emulate_amd64(self) -> Self: - if is_arm(): - return self.with_kwargs(platform="linux/amd64") - return self - - def start(self) -> Self: - if not c.ryuk_disabled and self.image != c.ryuk_image: - logger.debug("Creating Ryuk container") - Reaper.get_instance() - logger.info("Pulling image %s", self.image) - docker_client = self.get_docker_client() - self._configure() - - network_kwargs = ( - { - "network": self._network.name, - "networking_config": { - self._network.name: EndpointConfig(version.__version__, aliases=self._network_aliases) - }, - } - if self._network - else {} - ) - - self._container = docker_client.run( - self.image, - command=self._command, - detach=True, - environment=self.env, - ports=self.ports, - name=self._name, - volumes=self.volumes, - **network_kwargs, - **self._kwargs, - ) - - logger.info("Container started: %s", self._container.short_id) - return self - - def stop(self, force=True, delete_volume=True) -> None: - if self._container: - self._container.remove(force=force, v=delete_volume) - self.get_docker_client().client.close() - - def __enter__(self) -> Self: - return self.start() - - def __exit__(self, exc_type, exc_val, exc_tb) -> None: - self.stop() - - def get_container_host_ip(self) -> str: - connection_mode: ConnectionMode - connection_mode = self.get_docker_client().get_connection_mode() - if connection_mode == ConnectionMode.docker_host: - return self.get_docker_client().host() - elif connection_mode == ConnectionMode.gateway_ip: - return self.get_docker_client().gateway_ip(self._container.id) - elif connection_mode == ConnectionMode.bridge_ip: - return self.get_docker_client().bridge_ip(self._container.id) - else: - # ensure that we covered all possible connection_modes - assert_never(connection_mode) - - @wait_container_is_ready() - def get_exposed_port(self, port: int) -> int: - if self.get_docker_client().get_connection_mode().use_mapped_port: - return self.get_docker_client().port(self._container.id, port) - return port - - def with_command(self, command: str) -> Self: - self._command = command - return self - - def with_name(self, name: str) -> Self: - self._name = name - return self - - def with_volume_mapping(self, host: str, container: str, mode: str = "ro") -> Self: - mapping = {"bind": container, "mode": mode} - self.volumes[host] = mapping - return self - - def get_wrapped_container(self) -> "Container": - return self._container - - def get_docker_client(self) -> DockerClient: - return self._docker - - def get_logs(self) -> tuple[bytes, bytes]: - if not self._container: - raise ContainerStartException("Container should be started before getting logs") - return self._container.logs(stderr=False), self._container.logs(stdout=False) - - def exec(self, command: Union[str, list[str]]) -> tuple[int, bytes]: - if not self._container: - raise ContainerStartException("Container should be started before executing a command") - return self._container.exec_run(command) - - def _configure(self) -> None: - # placeholder if subclasses want to define this and use the default start method - pass - - -class Reaper: - _instance: "Optional[Reaper]" = None - _container: Optional[DockerContainer] = None - _socket: Optional[socket] = None - - @classmethod - def get_instance(cls) -> "Reaper": - if not Reaper._instance: - Reaper._instance = Reaper._create_instance() - - return Reaper._instance - - @classmethod - def delete_instance(cls) -> None: - if Reaper._socket is not None: - Reaper._socket.close() - Reaper._socket = None - - if Reaper._container is not None and Reaper._container._container is not None: - with contextlib.suppress(docker.errors.NotFound): - Reaper._container.stop() - Reaper._container = None - - if Reaper._instance is not None: - Reaper._instance = None - - @classmethod - def _create_instance(cls) -> "Reaper": - logger.debug(f"Creating new Reaper for session: {SESSION_ID}") - - Reaper._container = ( - DockerContainer(c.ryuk_image) - .with_name(f"testcontainers-ryuk-{SESSION_ID}") - .with_exposed_ports(8080) - .with_volume_mapping(c.ryuk_docker_socket, "/var/run/docker.sock", "rw") - .with_kwargs(privileged=c.ryuk_privileged, auto_remove=True) - .with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout) - .start() - ) - wait_for_logs(Reaper._container, r".* Started!", timeout=20, raise_on_exit=True) - - container_host = Reaper._container.get_container_host_ip() - container_port = int(Reaper._container.get_exposed_port(8080)) - - if not container_host or not container_port: - raise ContainerConnectException( - f"Could not obtain network details for {Reaper._container._container.id}. Host: {container_host} Port: {container_port}" - ) - - last_connection_exception: Optional[Exception] = None - for _ in range(50): - try: - Reaper._socket = socket() - Reaper._socket.settimeout(1) - Reaper._socket.connect((container_host, container_port)) - last_connection_exception = None - break - except (ConnectionRefusedError, OSError) as e: - if Reaper._socket is not None: - with contextlib.suppress(Exception): - Reaper._socket.close() - Reaper._socket = None - last_connection_exception = e - - from time import sleep - - sleep(0.5) - if last_connection_exception: - raise last_connection_exception - - Reaper._socket.send(f"label={LABEL_SESSION_ID}={SESSION_ID}\r\n".encode()) - - Reaper._instance = Reaper() - - return Reaper._instance diff --git a/core/testcontainers/core/docker_client.py b/core/testcontainers/core/docker_client.py deleted file mode 100644 index 83127884c..000000000 --- a/core/testcontainers/core/docker_client.py +++ /dev/null @@ -1,264 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import contextlib -import functools as ft -import importlib.metadata -import ipaddress -import os -import socket -import urllib -import urllib.parse -from collections.abc import Iterable -from typing import Callable, Optional, TypeVar, Union - -import docker -from docker.models.containers import Container, ContainerCollection -from docker.models.images import Image, ImageCollection -from typing_extensions import ParamSpec - -from testcontainers.core import utils -from testcontainers.core.auth import DockerAuthInfo, parse_docker_auth_config -from testcontainers.core.config import ConnectionMode -from testcontainers.core.config import testcontainers_config as c -from testcontainers.core.labels import SESSION_ID, create_labels - -LOGGER = utils.setup_logger(__name__) - -_P = ParamSpec("_P") -_T = TypeVar("_T") - - -def _wrapped_container_collection(function: Callable[_P, _T]) -> Callable[_P, _T]: - @ft.wraps(ContainerCollection.run) - def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: - return function(*args, **kwargs) - - return wrapper - - -def _wrapped_image_collection(function: Callable[_P, _T]) -> Callable[_P, _T]: - @ft.wraps(ImageCollection.build) - def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: - return function(*args, **kwargs) - - return wrapper - - -class DockerClient: - """ - Thin wrapper around :class:`docker.DockerClient` for a more functional interface. - """ - - def __init__(self, **kwargs) -> None: - docker_host = get_docker_host() - - if docker_host: - LOGGER.info(f"using host {docker_host}") - os.environ["DOCKER_HOST"] = docker_host - self.client = docker.from_env(**kwargs) - else: - self.client = docker.from_env(**kwargs) - self.client.api.headers["x-tc-sid"] = SESSION_ID - self.client.api.headers["User-Agent"] = "tc-python/" + importlib.metadata.version("testcontainers") - - # Verify if we have a docker auth config and login if we do - if docker_auth_config := get_docker_auth_config(): - LOGGER.debug(f"DOCKER_AUTH_CONFIG found: {docker_auth_config}") - if auth_config := parse_docker_auth_config(docker_auth_config): - self.login(auth_config[0]) # Only using the first auth config) - - @_wrapped_container_collection - def run( - self, - image: str, - command: Optional[Union[str, list[str]]] = None, - environment: Optional[dict] = None, - ports: Optional[dict] = None, - labels: Optional[dict[str, str]] = None, - detach: bool = False, - stdout: bool = True, - stderr: bool = False, - remove: bool = False, - **kwargs, - ) -> Container: - # If the user has specified a network, we'll assume the user knows best - if "network" not in kwargs and not get_docker_host(): - # Otherwise we'll try to find the docker host for dind usage. - host_network = self.find_host_network() - if host_network: - kwargs["network"] = host_network - container = self.client.containers.run( - image, - command=command, - stdout=stdout, - stderr=stderr, - remove=remove, - detach=detach, - environment=environment, - ports=ports, - labels=create_labels(image, labels), - **kwargs, - ) - return container - - @_wrapped_image_collection - def build(self, path: str, tag: str, rm: bool = True, **kwargs) -> tuple[Image, Iterable[dict]]: - """ - Build a Docker image from a directory containing the Dockerfile. - - :return: A tuple containing the image object and the build logs. - """ - image_object, image_logs = self.client.images.build(path=path, tag=tag, rm=rm, **kwargs) - - return image_object, image_logs - - def find_host_network(self) -> Optional[str]: - """ - Try to find the docker host network. - - :return: The network name if found, None if not set. - """ - # If we're docker in docker running on a custom network, we need to inherit the - # network settings, so we can access the resulting container. - - # first to try to find the network the container runs in, if we can determine - container_id = utils.get_running_in_container_id() - if container_id: - with contextlib.suppress(Exception): - return self.network_name(container_id) - - # if this results nothing, try to determine the network based on the - # docker_host - try: - host_ip = socket.gethostbyname(self.host()) - docker_host = ipaddress.IPv4Address(host_ip) - # See if we can find the host on our networks - for network in self.client.networks.list(filters={"type": "custom"}): - if "IPAM" in network.attrs: - for config in network.attrs["IPAM"]["Config"]: - try: - subnet = ipaddress.IPv4Network(config["Subnet"]) - except ipaddress.AddressValueError: - continue - if docker_host in subnet: - return network.name - except (ipaddress.AddressValueError, OSError): - pass - return None - - def port(self, container_id: str, port: int) -> int: - """ - Lookup the public-facing port that is NAT-ed to :code:`port`. - """ - port_mappings = self.client.api.port(container_id, port) - if not port_mappings: - raise ConnectionError(f"Port mapping for container {container_id} and port {port} is " "not available") - return port_mappings[0]["HostPort"] - - def get_container(self, container_id: str) -> Container: - """ - Get the container with a given identifier. - """ - containers = self.client.api.containers(filters={"id": container_id}) - if not containers: - raise RuntimeError(f"Could not get container with id {container_id}") - return containers[0] - - def bridge_ip(self, container_id: str) -> str: - """ - Get the bridge ip address for a container. - """ - container = self.get_container(container_id) - network_name = self.network_name(container_id) - return container["NetworkSettings"]["Networks"][network_name]["IPAddress"] - - def network_name(self, container_id: str) -> str: - """ - Get the name of the network this container runs on - """ - container = self.get_container(container_id) - name = container["HostConfig"]["NetworkMode"] - if name == "default": - return "bridge" - return name - - def gateway_ip(self, container_id: str) -> str: - """ - Get the gateway ip address for a container. - """ - container = self.get_container(container_id) - network_name = self.network_name(container_id) - return container["NetworkSettings"]["Networks"][network_name]["Gateway"] - - def get_connection_mode(self) -> ConnectionMode: - """ - Determine the connection mode. - - See https://github.com/testcontainers/testcontainers-python/issues/475#issuecomment-2407250970 - """ - if c.connection_mode_override: - return c.connection_mode_override - localhosts = {"localhost", "127.0.0.1", "::1"} - if not utils.inside_container() or self.host() not in localhosts: - # if running not inside a container or with a non-local docker client, - # connect ot the docker host per default - return ConnectionMode.docker_host - elif self.find_host_network(): - # a host network could be determined, indicator for DooD, - # so we should connect to the bridge_ip as the container we run in - # and the one we started are connected to the same network - # that might have no access to either docker_host or the gateway - return ConnectionMode.bridge_ip - # default for DinD - return ConnectionMode.gateway_ip - - def host(self) -> str: - """ - Get the hostname or ip address of the docker host. - """ - host = c.tc_host_override - if host: - return host - try: - url = urllib.parse.urlparse(self.client.api.base_url) - except ValueError: - return "localhost" - if "http" in url.scheme or "tcp" in url.scheme and url.hostname: - # see https://github.com/testcontainers/testcontainers-python/issues/415 - if url.hostname == "localnpipe" and utils.is_windows(): - return "localhost" - return url.hostname - if utils.inside_container() and ("unix" in url.scheme or "npipe" in url.scheme): - ip_address = utils.default_gateway_ip() - if ip_address: - return ip_address - return "localhost" - - def login(self, auth_config: DockerAuthInfo) -> None: - """ - Login to a docker registry using the given auth config. - """ - login_info = self.client.login(**auth_config._asdict()) - LOGGER.debug(f"logged in using {login_info}") - - def client_networks_create(self, name: str, param: dict): - labels = create_labels("", param.get("labels")) - return self.client.networks.create(name, **{**param, "labels": labels}) - - -def get_docker_host() -> Optional[str]: - return c.tc_properties_get_tc_host() or os.getenv("DOCKER_HOST") - - -def get_docker_auth_config() -> Optional[str]: - return c.docker_auth_config diff --git a/core/testcontainers/core/version.py b/core/testcontainers/core/version.py deleted file mode 100644 index cac51fc18..000000000 --- a/core/testcontainers/core/version.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Callable - -from packaging.version import Version - - -class ComparableVersion: - def __init__(self, version): - self.version = Version(version) - - def __lt__(self, other: str): - return self._apply_op(other, lambda x, y: x < y) - - def __le__(self, other: str): - return self._apply_op(other, lambda x, y: x <= y) - - def __eq__(self, other: str): - return self._apply_op(other, lambda x, y: x == y) - - def __ne__(self, other: str): - return self._apply_op(other, lambda x, y: x != y) - - def __gt__(self, other: str): - return self._apply_op(other, lambda x, y: x > y) - - def __ge__(self, other: str): - return self._apply_op(other, lambda x, y: x >= y) - - def _apply_op(self, other: str, op: Callable[[Version, Version], bool]): - other = Version(other) - return op(self.version, other) diff --git a/core/testcontainers/core/waiting_utils.py b/core/testcontainers/core/waiting_utils.py deleted file mode 100644 index 8966e5f99..000000000 --- a/core/testcontainers/core/waiting_utils.py +++ /dev/null @@ -1,130 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -import re -import time -import traceback -from typing import TYPE_CHECKING, Any, Callable, Union - -import wrapt - -from testcontainers.core.config import testcontainers_config as config -from testcontainers.core.utils import setup_logger - -if TYPE_CHECKING: - from testcontainers.core.container import DockerContainer - -logger = setup_logger(__name__) - -# Get a tuple of transient exceptions for which we'll retry. Other exceptions will be raised. -TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionError) - - -def wait_container_is_ready(*transient_exceptions) -> Callable: - """ - Wait until container is ready. - - Function that spawn container should be decorated by this method Max wait is configured by - config. Default is 120 sec. Polling interval is 1 sec. - - Args: - *transient_exceptions: Additional transient exceptions that should be retried if raised. Any - non-transient exceptions are fatal, and the exception is re-raised immediately. - """ - transient_exceptions = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) - - @wrapt.decorator - def wrapper(wrapped: Callable, instance: Any, args: list, kwargs: dict) -> Any: - from testcontainers.core.container import DockerContainer - - if isinstance(instance, DockerContainer): - logger.info("Waiting for container %s with image %s to be ready ...", instance._container, instance.image) - else: - logger.info("Waiting for %s to be ready ...", instance) - - exception = None - for attempt_no in range(config.max_tries): - try: - return wrapped(*args, **kwargs) - except transient_exceptions as e: - logger.debug( - f"Connection attempt '{attempt_no + 1}' of '{config.max_tries + 1}' " - f"failed: {traceback.format_exc()}" - ) - time.sleep(config.sleep_time) - exception = e - raise TimeoutError( - f"Wait time ({config.timeout}s) exceeded for {wrapped.__name__}(args: {args}, kwargs: " - f"{kwargs}). Exception: {exception}" - ) - - return wrapper - - -@wait_container_is_ready() -def wait_for(condition: Callable[..., bool]) -> bool: - return condition() - - -_NOT_EXITED_STATUSES = {"running", "created"} - - -def wait_for_logs( - container: "DockerContainer", - predicate: Union[Callable, str], - timeout: float = config.timeout, - interval: float = 1, - predicate_streams_and: bool = False, - raise_on_exit: bool = False, - # -) -> float: - """ - Wait for the container to emit logs satisfying the predicate. - - Args: - container: Container whose logs to wait for. - predicate: Predicate that should be satisfied by the logs. If a string, then it is used as - the pattern for a multiline regular expression search. - timeout: Number of seconds to wait for the predicate to be satisfied. Defaults to wait - indefinitely. - interval: Interval at which to poll the logs. - predicate_streams_and: should the predicate be applied to both - - Returns: - duration: Number of seconds until the predicate was satisfied. - """ - if isinstance(predicate, str): - predicate = re.compile(predicate, re.MULTILINE).search - wrapped = container.get_wrapped_container() - start = time.time() - while True: - duration = time.time() - start - stdout, stderr = container.get_logs() - stdout = stdout.decode() - stderr = stderr.decode() - predicate_result = ( - predicate(stdout) or predicate(stderr) - if predicate_streams_and is False - else predicate(stdout) and predicate(stderr) - # - ) - if predicate_result: - return duration - if duration > timeout: - raise TimeoutError(f"Container did not emit logs satisfying predicate in {timeout:.3f} " "seconds") - if raise_on_exit: - wrapped.reload() - if wrapped.status not in _NOT_EXITED_STATUSES: - raise RuntimeError("Container exited before emitting logs satisfying predicate") - time.sleep(interval) diff --git a/core/tests/test_config.py b/core/tests/test_config.py deleted file mode 100644 index 8be68cc76..000000000 --- a/core/tests/test_config.py +++ /dev/null @@ -1,86 +0,0 @@ -import pytest - -from testcontainers.core.config import ( - TestcontainersConfiguration as TCC, - TC_FILE, - get_user_overwritten_connection_mode, - ConnectionMode, -) - -from pytest import MonkeyPatch, mark, LogCaptureFixture - -import logging -import tempfile - - -def test_read_tc_properties(monkeypatch: MonkeyPatch) -> None: - with tempfile.TemporaryDirectory() as tmpdirname: - file = f"{tmpdirname}/{TC_FILE}" - with open(file, "w") as f: - f.write("tc.host=some_value\n") - - monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file) - - config = TCC() - assert config.tc_properties == {"tc.host": "some_value"} - - -@mark.parametrize("docker_auth_config_env", ["key=value", ""]) -@mark.parametrize("warning_dict", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}]) -@mark.parametrize("warning_dict_post", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}]) -def test_docker_auth_config( - caplog: LogCaptureFixture, - monkeypatch: MonkeyPatch, - docker_auth_config_env: str, - warning_dict: dict[str, str], - warning_dict_post: dict[str, str], -) -> None: - monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict) - monkeypatch.setenv("DOCKER_AUTH_CONFIG", docker_auth_config_env) - caplog.set_level(logging.WARNING) - - config = TCC() - if not docker_auth_config_env: - assert config.docker_auth_config == "" - assert caplog.text == "" - else: - assert config.docker_auth_config == docker_auth_config_env - - if "DOCKER_AUTH_CONFIG" in warning_dict: - assert warning_dict["DOCKER_AUTH_CONFIG"] in caplog.text - - if warning_dict == {}: - monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict_post) - - config.docker_auth_config = "new_value" - assert config.docker_auth_config == "new_value" - - -def test_tc_properties_get_tc_host() -> None: - config = TCC() - config.tc_properties = {"tc.host": "some_value"} - assert config.tc_properties_get_tc_host() == "some_value" - - -def test_timeout() -> None: - config = TCC() - config.max_tries = 2 - config.sleep_time = 3 - assert config.timeout == 6 - - -def test_invalid_connection_mode(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.setenv("TESTCONTAINERS_CONNECTION_MODE", "FOOBAR") - with pytest.raises(ValueError, match="Error parsing TESTCONTAINERS_CONNECTION_MODE.*FOOBAR.*"): - get_user_overwritten_connection_mode() - - -@pytest.mark.parametrize("mode, use_mapped", (("bridge_ip", False), ("gateway_ip", True), ("docker_host", True))) -def test_valid_connection_mode(monkeypatch: pytest.MonkeyPatch, mode: str, use_mapped: bool) -> None: - monkeypatch.setenv("TESTCONTAINERS_CONNECTION_MODE", mode) - assert get_user_overwritten_connection_mode().use_mapped_port is use_mapped - - -def test_no_connection_mode_given(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.delenv("TESTCONTAINERS_CONNECTION_MODE", raising=False) - assert get_user_overwritten_connection_mode() is None diff --git a/core/tests/test_container.py b/core/tests/test_container.py deleted file mode 100644 index e1e7cff7a..000000000 --- a/core/tests/test_container.py +++ /dev/null @@ -1,77 +0,0 @@ -import pytest - -from testcontainers.core.container import DockerContainer -from testcontainers.core.docker_client import DockerClient -from testcontainers.core.config import ConnectionMode - -FAKE_ID = "ABC123" - - -class FakeContainer: - @property - def id(self) -> str: - return FAKE_ID - - -@pytest.fixture -def container(monkeypatch: pytest.MonkeyPatch) -> DockerContainer: - """ - Fake initialized container - """ - client = DockerClient() - container = DockerContainer("foobar") - monkeypatch.setattr(container, "_docker", client) - monkeypatch.setattr(container, "_container", FakeContainer()) - - return container - - -@pytest.mark.parametrize("mode", ["docker_host", "gateway_ip", "bridge_ip"]) -def test_get_container_host_ip(container: DockerContainer, monkeypatch: pytest.MonkeyPatch, mode: str) -> None: - """ - Depending on the connection mode the correct function is executed to the host_ip - """ - connection_mode = ConnectionMode(mode) - - def result_fake(result: str, require_container_id): - def fake_for_mode(*container_id: str): - if require_container_id: - assert len(container_id) == 1 - assert container_id[0] == FAKE_ID - else: - assert len(container_id) == 0 - return result - - return fake_for_mode - - client = container._docker - - monkeypatch.setattr(client, "get_connection_mode", lambda: connection_mode) - monkeypatch.setattr(client, "gateway_ip", result_fake("gateway_ip", True)) - monkeypatch.setattr(client, "bridge_ip", result_fake("bridge_ip", True)) - monkeypatch.setattr(client, "host", result_fake("docker_host", False)) - - assert container.get_container_host_ip() == mode - - -@pytest.mark.parametrize("mode", [ConnectionMode.gateway_ip, ConnectionMode.docker_host]) -def test_get_exposed_port_mapped( - container: DockerContainer, monkeypatch: pytest.MonkeyPatch, mode: ConnectionMode -) -> None: - def fake_mapped(container_id: int, port: int) -> int: - assert container_id == FAKE_ID - assert port == 8080 - return 45678 - - client = container._docker - monkeypatch.setattr(client, "port", fake_mapped) - monkeypatch.setattr(client, "get_connection_mode", lambda: mode) - - assert container.get_exposed_port(8080) == 45678 - - -def test_get_exposed_port_original(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: - client = container._docker - monkeypatch.setattr(client, "get_connection_mode", lambda: ConnectionMode.bridge_ip) - - assert container.get_exposed_port(8080) == 8080 diff --git a/core/tests/test_ryuk.py b/core/tests/test_ryuk.py deleted file mode 100644 index 5d6b208af..000000000 --- a/core/tests/test_ryuk.py +++ /dev/null @@ -1,63 +0,0 @@ -from time import sleep -import pytest -from pytest import MonkeyPatch - -from docker import DockerClient -from docker.errors import NotFound - -from testcontainers.core.config import testcontainers_config -from testcontainers.core.container import Reaper -from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for_logs - - -@pytest.mark.inside_docker_check -def test_wait_for_reaper(monkeypatch: MonkeyPatch): - Reaper.delete_instance() - monkeypatch.setattr(testcontainers_config, "ryuk_reconnection_timeout", "0.1s") - container = DockerContainer("hello-world") - container.start() - - docker_client = container.get_docker_client().client - - container_id = container.get_wrapped_container().short_id - reaper_id = Reaper._container.get_wrapped_container().short_id - - assert docker_client.containers.get(container_id) is not None - assert docker_client.containers.get(reaper_id) is not None - - wait_for_logs(container, "Hello from Docker!") - - Reaper._socket.close() - - sleep(0.6) # Sleep until Ryuk reaps all dangling containers. 0.5 extra seconds for good measure. - - with pytest.raises(NotFound): - docker_client.containers.get(container_id) - with pytest.raises(NotFound): - docker_client.containers.get(reaper_id) - - # Cleanup Ryuk class fields after manual Ryuk shutdown - Reaper.delete_instance() - - -@pytest.mark.inside_docker_check -def test_container_without_ryuk(monkeypatch: MonkeyPatch): - Reaper.delete_instance() - monkeypatch.setattr(testcontainers_config, "ryuk_disabled", True) - with DockerContainer("hello-world") as container: - wait_for_logs(container, "Hello from Docker!") - assert Reaper._instance is None - - -@pytest.mark.inside_docker_check -def test_ryuk_is_reused_in_same_process(): - with DockerContainer("hello-world") as container: - wait_for_logs(container, "Hello from Docker!") - reaper_instance = Reaper._instance - - assert reaper_instance is not None - - with DockerContainer("hello-world") as container: - wait_for_logs(container, "Hello from Docker!") - assert reaper_instance is Reaper._instance diff --git a/core/tests/test_waiting_utils.py b/core/tests/test_waiting_utils.py deleted file mode 100644 index 1e684fc46..000000000 --- a/core/tests/test_waiting_utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import pytest - -from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for_logs - - -def test_wait_for_logs() -> None: - with DockerContainer("hello-world") as container: - wait_for_logs(container, "Hello from Docker!") - - -def test_timeout_is_raised_when_waiting_for_logs() -> None: - with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container: - wait_for_logs(container, "Hello from Docker!", timeout=1e-3) diff --git a/docs/_headers b/docs/_headers new file mode 100644 index 000000000..e59f34a29 --- /dev/null +++ b/docs/_headers @@ -0,0 +1,2 @@ +/search/search_index.json + Access-Control-Allow-Origin: * diff --git a/core/testcontainers/core/__init__.py b/docs/_redirects similarity index 100% rename from core/testcontainers/core/__init__.py rename to docs/_redirects diff --git a/docs/community/arangodb.rst b/docs/community/arangodb.rst new file mode 100644 index 000000000..4d4781ba1 --- /dev/null +++ b/docs/community/arangodb.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.arangodb.ArangoDbContainer +.. title:: testcontainers.community.arangodb.ArangoDbContainer diff --git a/modules/aws/README.rst b/docs/community/aws.rst similarity index 91% rename from modules/aws/README.rst rename to docs/community/aws.rst index a44dc856f..30e66af4a 100644 --- a/modules/aws/README.rst +++ b/docs/community/aws.rst @@ -1,7 +1,7 @@ :code:`testcontainers-aws` is a set of AWS containers modules that can be used to create AWS containers. -.. autoclass:: testcontainers.aws.AWSLambdaContainer -.. title:: testcontainers.aws.AWSLambdaContainer +.. autoclass:: testcontainers.community.aws.AWSLambdaContainer +.. title:: testcontainers.community.aws.AWSLambdaContainer The following environment variables are used by the AWS Lambda container: diff --git a/docs/community/azurite.rst b/docs/community/azurite.rst new file mode 100644 index 000000000..e56af0c75 --- /dev/null +++ b/docs/community/azurite.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.azurite.AzuriteContainer +.. title:: testcontainers.community.azurite.AzuriteContainer diff --git a/docs/community/cassandra.rst b/docs/community/cassandra.rst new file mode 100644 index 000000000..5726f09be --- /dev/null +++ b/docs/community/cassandra.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.cassandra.CassandraContainer +.. title:: testcontainers.community.cassandra.CassandraContainer diff --git a/docs/community/chroma.rst b/docs/community/chroma.rst new file mode 100644 index 000000000..9b028a3b6 --- /dev/null +++ b/docs/community/chroma.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.chroma.ChromaContainer +.. title:: testcontainers.community.chroma.ChromaContainer diff --git a/docs/community/clickhouse.rst b/docs/community/clickhouse.rst new file mode 100644 index 000000000..9840370ab --- /dev/null +++ b/docs/community/clickhouse.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.clickhouse.ClickHouseContainer +.. title:: testcontainers.community.clickhouse.ClickHouseContainer diff --git a/docs/community/cockroachdb.rst b/docs/community/cockroachdb.rst new file mode 100644 index 000000000..bdbcfe3a7 --- /dev/null +++ b/docs/community/cockroachdb.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.cockroachdb.CockroachDBContainer +.. title:: testcontainers.community.cockroachdb.CockroachDBContainer diff --git a/docs/community/cosmosdb.rst b/docs/community/cosmosdb.rst new file mode 100644 index 000000000..ccf6ee631 --- /dev/null +++ b/docs/community/cosmosdb.rst @@ -0,0 +1,5 @@ +.. autoclass:: testcontainers.community.cosmosdb.CosmosDBMongoEndpointContainer +.. title:: testcontainers.community.cosmosdb.CosmosDBMongoEndpointContainer + +.. autoclass:: testcontainers.community.cosmosdb.CosmosDBNoSQLEndpointContainer +.. title:: testcontainers.community.cosmosdb.CosmosDBNoSQLEndpointContainer diff --git a/docs/community/cratedb.rst b/docs/community/cratedb.rst new file mode 100644 index 000000000..a6603ca8e --- /dev/null +++ b/docs/community/cratedb.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.cratedb.CrateDBContainer +.. title:: testcontainers.community.cratedb.CrateDBContainer diff --git a/docs/community/db2.rst b/docs/community/db2.rst new file mode 100644 index 000000000..494875cc6 --- /dev/null +++ b/docs/community/db2.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.db2.Db2Container +.. title:: testcontainers.community.db2.Db2Container diff --git a/docs/community/elasticsearch.rst b/docs/community/elasticsearch.rst new file mode 100644 index 000000000..4d93ab1c4 --- /dev/null +++ b/docs/community/elasticsearch.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.elasticsearch.ElasticSearchContainer +.. title:: testcontainers.community.elasticsearch.ElasticSearchContainer diff --git a/docs/community/generic.rst b/docs/community/generic.rst new file mode 100644 index 000000000..33e8b31f0 --- /dev/null +++ b/docs/community/generic.rst @@ -0,0 +1,89 @@ +:code:`testcontainers-generic` is a set of generic containers modules that can be used to creat containers. + +.. autoclass:: testcontainers.community.generic.ServerContainer +.. title:: testcontainers.community.generic.ServerContainer + +FastAPI container that is using :code:`ServerContainer` + +.. doctest:: + + >>> from testcontainers.community.generic import ServerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path=f"{TEST_DIR}/community/generic/samples/fastapi", tag="fastapi-test:latest") as image: + ... with ServerContainer(port=80, image=image) as fastapi_server: + ... delay = wait_for_logs(fastapi_server, "Uvicorn running on http://0.0.0.0:80") + ... fastapi_server.get_api_url = lambda: fastapi_server._create_connection_url() + "/api/v1/" + ... client = fastapi_server.get_client() + ... response = client.get("/") + ... assert response.status_code == 200 + ... assert response.json() == {"Status": "Working"} + +A more advance use-case, where we are using a FastAPI container that is using Redis container: + +.. doctest:: + + >>> from testcontainers.community.redis import RedisContainer + >>> from testcontainers.community.generic import ServerContainer + + >>> with RedisContainer() as redis: + ... redis_container_port = redis.port + ... redis_container_ip_address = redis.get_docker_client().bridge_ip(redis._container.id) + + ... with DockerImage(path=f"{TEST_DIR}/community/generic/samples/advance_1", tag="advance-1:latest") as image: + ... web_server = ServerContainer(port=80, image=image) + ... web_server.with_env(key="REDIS_HOST", value=redis_container_ip_address) + ... web_server.with_env(key="REDIS_PORT", value=redis_container_port) + + ... with web_server: + ... web_server.get_api_url = lambda: web_server._create_connection_url() + ... client = web_server.get_client() + + ... response = client.get("/") + ... assert response.status_code == 200, "Server request failed" + ... assert response.json() == {"Status": "ok"} + + ... test_data = {"key": "test_key", "value": "test_value"} + ... response = client.post("/set", params=test_data) + ... assert response.status_code == 200, "Failed to set data" + + ... response = client.get(f"/get/{test_data['key']}") + ... assert response.status_code == 200, "Failed to get data" + ... assert response.json() == {"key": test_data["key"], "value": test_data["value"]} + +.. autoclass:: testcontainers.community.generic.SqlContainer +.. title:: testcontainers.community.generic.SqlContainer + +Postgres container that is using :code:`SqlContainer` + +.. doctest:: + + >>> from testcontainers.community.generic import SqlContainer + >>> from testcontainers.community.generic.providers.sql_connection_wait_strategy import SqlAlchemyConnectWaitStrategy + >>> from sqlalchemy import text + >>> import sqlalchemy + + >>> class CustomPostgresContainer(SqlContainer): + ... def __init__(self, image="postgres:15-alpine", + ... port=5432, username="test", password="test", dbname="test"): + ... super().__init__(image=image, wait_strategy=SqlAlchemyConnectWaitStrategy()) + ... self.port_to_expose = port + ... self.username = username + ... self.password = password + ... self.dbname = dbname + ... def get_connection_url(self) -> str: + ... host = self.get_container_host_ip() + ... port = self.get_exposed_port(self.port_to_expose) + ... return f"postgresql://{self.username}:{self.password}@{host}:{port}/{self.dbname}" + ... def _configure(self) -> None: + ... self.with_exposed_ports(self.port_to_expose) + ... self.with_env("POSTGRES_USER", self.username) + ... self.with_env("POSTGRES_PASSWORD", self.password) + ... self.with_env("POSTGRES_DB", self.dbname) + + >>> with CustomPostgresContainer() as postgres: + ... engine = sqlalchemy.create_engine(postgres.get_connection_url()) + ... with engine.connect() as conn: + ... result = conn.execute(text("SELECT 1")) + ... assert result.scalar() == 1 diff --git a/docs/community/google.rst b/docs/community/google.rst new file mode 100644 index 000000000..761c7fe7d --- /dev/null +++ b/docs/community/google.rst @@ -0,0 +1,4 @@ +.. autoclass:: testcontainers.community.google.DatastoreContainer +.. title:: testcontainers.community.google.DatastoreContainer +.. autoclass:: testcontainers.community.google.PubSubContainer +.. title:: testcontainers.community.google.PubSubContainer diff --git a/docs/community/influxdb.rst b/docs/community/influxdb.rst new file mode 100644 index 000000000..ff7ba1e30 --- /dev/null +++ b/docs/community/influxdb.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.influxdb.InfluxDbContainer +.. title:: testcontainers.community.influxdb.InfluxDbContainer diff --git a/docs/community/k3s.rst b/docs/community/k3s.rst new file mode 100644 index 000000000..6e295ee2f --- /dev/null +++ b/docs/community/k3s.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.k3s.K3SContainer +.. title:: testcontainers.community.k3s.K3SContainer diff --git a/docs/community/kafka.rst b/docs/community/kafka.rst new file mode 100644 index 000000000..189df27d2 --- /dev/null +++ b/docs/community/kafka.rst @@ -0,0 +1,3 @@ +.. autoclass:: testcontainers.community.kafka.KafkaContainer +.. title:: testcontainers.community.kafka.KafkaContainer +.. autoclass:: testcontainers.community.kafka.RedpandaContainer diff --git a/docs/community/keycloak.rst b/docs/community/keycloak.rst new file mode 100644 index 000000000..8826c8260 --- /dev/null +++ b/docs/community/keycloak.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.keycloak.KeycloakContainer +.. title:: testcontainers.community.keycloak.KeycloakContainer diff --git a/docs/community/localstack.rst b/docs/community/localstack.rst new file mode 100644 index 000000000..43f11066e --- /dev/null +++ b/docs/community/localstack.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.localstack.LocalStackContainer +.. title:: testcontainers.community.localstack.LocalStackContainer diff --git a/docs/community/mailpit.rst b/docs/community/mailpit.rst new file mode 100644 index 000000000..ea5e29391 --- /dev/null +++ b/docs/community/mailpit.rst @@ -0,0 +1,3 @@ +.. autoclass:: testcontainers.community.mailpit.MailpitUser +.. autoclass:: testcontainers.community.mailpit.MailpitContainer +.. title:: testcontainers.community.mailpit.MailpitContainer diff --git a/docs/community/memcached.rst b/docs/community/memcached.rst new file mode 100644 index 000000000..93eaf0924 --- /dev/null +++ b/docs/community/memcached.rst @@ -0,0 +1 @@ +.. autoclass:: testcontainers.community.memcached.MemcachedContainer diff --git a/docs/community/milvus.rst b/docs/community/milvus.rst new file mode 100644 index 000000000..123501adf --- /dev/null +++ b/docs/community/milvus.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.milvus.MilvusContainer +.. title:: testcontainers.community.milvus.MilvusContainer diff --git a/docs/community/minio.rst b/docs/community/minio.rst new file mode 100644 index 000000000..6ead4df8c --- /dev/null +++ b/docs/community/minio.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.minio.MinioContainer +.. title:: testcontainers.community.minio.MinioContainer diff --git a/docs/community/mongodb.rst b/docs/community/mongodb.rst new file mode 100644 index 000000000..91c84ef80 --- /dev/null +++ b/docs/community/mongodb.rst @@ -0,0 +1,4 @@ +.. autoclass:: testcontainers.community.mongodb.MongoDbContainer +.. autoclass:: testcontainers.community.mongodb.MongoDbReplicaSetContainer +.. autoclass:: testcontainers.community.mongodb.MongoDBAtlasLocalContainer +.. title:: testcontainers.community.mongodb.MongoDbContainer diff --git a/docs/community/mqtt.rst b/docs/community/mqtt.rst new file mode 100644 index 000000000..fe6e4c688 --- /dev/null +++ b/docs/community/mqtt.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.mqtt.MosquittoContainer +.. title:: testcontainers.community.mqtt.MosquittoContainer diff --git a/docs/community/mssql.rst b/docs/community/mssql.rst new file mode 100644 index 000000000..df12b9315 --- /dev/null +++ b/docs/community/mssql.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.mssql.SqlServerContainer +.. title:: testcontainers.community.mssql.SqlServerContainer diff --git a/docs/community/mysql.rst b/docs/community/mysql.rst new file mode 100644 index 000000000..e404f39b7 --- /dev/null +++ b/docs/community/mysql.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.mysql.MySqlContainer +.. title:: testcontainers.community.mysql.MySqlContainer diff --git a/docs/community/nats.rst b/docs/community/nats.rst new file mode 100644 index 000000000..4cc5d7572 --- /dev/null +++ b/docs/community/nats.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.nats.NatsContainer +.. title:: testcontainers.community.nats.NatsContainer diff --git a/docs/community/neo4j.rst b/docs/community/neo4j.rst new file mode 100644 index 000000000..6a4f0ca1a --- /dev/null +++ b/docs/community/neo4j.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.neo4j.Neo4jContainer +.. title:: testcontainers.community.neo4j.Neo4jContainer diff --git a/docs/community/nginx.rst b/docs/community/nginx.rst new file mode 100644 index 000000000..7b0b85b5a --- /dev/null +++ b/docs/community/nginx.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.nginx.NginxContainer +.. title:: testcontainers.community.nginx.NginxContainer diff --git a/docs/community/ollama.rst b/docs/community/ollama.rst new file mode 100644 index 000000000..addc63e7d --- /dev/null +++ b/docs/community/ollama.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.ollama.OllamaContainer +.. title:: testcontainers.community.ollama.OllamaContainer diff --git a/docs/community/openfga.rst b/docs/community/openfga.rst new file mode 100644 index 000000000..dae13a6ee --- /dev/null +++ b/docs/community/openfga.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.openfga.OpenFGAContainer +.. title:: testcontainers.community.openfga.OpenFGAContainer diff --git a/docs/community/opensearch.rst b/docs/community/opensearch.rst new file mode 100644 index 000000000..f6c29d037 --- /dev/null +++ b/docs/community/opensearch.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.opensearch.OpenSearchContainer +.. title:: testcontainers.community.opensearch.OpenSearchContainer diff --git a/docs/community/oracle.rst b/docs/community/oracle.rst new file mode 100644 index 000000000..68f51ef79 --- /dev/null +++ b/docs/community/oracle.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.oracle.OracleDbContainer +.. title:: testcontainers.community.oracle.OracleDbContainer diff --git a/docs/community/postgres.rst b/docs/community/postgres.rst new file mode 100644 index 000000000..12a5450c9 --- /dev/null +++ b/docs/community/postgres.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.postgres.PostgresContainer +.. title:: testcontainers.community.postgres.PostgresContainer diff --git a/docs/community/qdrant.rst b/docs/community/qdrant.rst new file mode 100644 index 000000000..3b4a8a2b3 --- /dev/null +++ b/docs/community/qdrant.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.qdrant.QdrantContainer +.. title:: testcontainers.community.qdrant.QdrantContainer diff --git a/docs/community/rabbitmq.rst b/docs/community/rabbitmq.rst new file mode 100644 index 000000000..44e5ce601 --- /dev/null +++ b/docs/community/rabbitmq.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.rabbitmq.RabbitMqContainer +.. title:: testcontainers.community.rabbitmq.RabbitMqContainer diff --git a/docs/community/redis.rst b/docs/community/redis.rst new file mode 100644 index 000000000..b69a7a27f --- /dev/null +++ b/docs/community/redis.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.redis.RedisContainer +.. title:: testcontainers.community.redis.RedisContainer diff --git a/modules/registry/README.rst b/docs/community/registry.rst similarity index 88% rename from modules/registry/README.rst rename to docs/community/registry.rst index f503f338e..482105ffc 100644 --- a/modules/registry/README.rst +++ b/docs/community/registry.rst @@ -1,4 +1,4 @@ -.. autoclass:: testcontainers.registry.DockerRegistryContainer +.. autoclass:: testcontainers.community.registry.DockerRegistryContainer When building Docker containers with Docker Buildx there is currently no option to test your containers locally without a local registry. Otherwise Buildx pushes your image to Docker Hub, which is not what you want in a test case. More diff --git a/docs/community/scylla.rst b/docs/community/scylla.rst new file mode 100644 index 000000000..bdc4608d0 --- /dev/null +++ b/docs/community/scylla.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.scylla.ScyllaContainer +.. title:: testcontainers.community.scylla.ScyllaContainer diff --git a/docs/community/selenium.rst b/docs/community/selenium.rst new file mode 100644 index 000000000..d67246d96 --- /dev/null +++ b/docs/community/selenium.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.selenium.BrowserWebDriverContainer +.. title:: testcontainers.community.selenium.BrowserWebDriverContainer diff --git a/docs/community/sftp.rst b/docs/community/sftp.rst new file mode 100644 index 000000000..9503fb60c --- /dev/null +++ b/docs/community/sftp.rst @@ -0,0 +1,3 @@ +.. autoclass:: testcontainers.community.sftp.SFTPContainer +.. autoclass:: testcontainers.community.sftp.SFTPUser +.. title:: testcontainers.community.sftp.SFTPContainer diff --git a/docs/community/toxiproxy.rst b/docs/community/toxiproxy.rst new file mode 100644 index 000000000..1399a516f --- /dev/null +++ b/docs/community/toxiproxy.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.toxiproxy.ToxiproxyContainer +.. title:: testcontainers.community.toxiproxy.ToxiproxyContainer diff --git a/docs/community/trino.rst b/docs/community/trino.rst new file mode 100644 index 000000000..fa6ba1003 --- /dev/null +++ b/docs/community/trino.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.trino.TrinoContainer +.. title:: testcontainers.community.trino.TrinoContainer diff --git a/docs/community/valkey.rst b/docs/community/valkey.rst new file mode 100644 index 000000000..24ab8a96b --- /dev/null +++ b/docs/community/valkey.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.valkey.ValkeyContainer +.. title:: testcontainers.community.valkey.ValkeyContainer diff --git a/docs/community/vault.rst b/docs/community/vault.rst new file mode 100644 index 000000000..dde0e90b1 --- /dev/null +++ b/docs/community/vault.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.vault.VaultContainer +.. title:: testcontainers.community.vault.VaultContainer diff --git a/docs/community/weaviate.rst b/docs/community/weaviate.rst new file mode 100644 index 000000000..33e22168f --- /dev/null +++ b/docs/community/weaviate.rst @@ -0,0 +1,2 @@ +.. autoclass:: testcontainers.community.weaviate.WeaviateContainer +.. title:: testcontainers.community.weaviate.WeaviateContainer diff --git a/docs/compose.rst b/docs/compose.rst new file mode 100644 index 000000000..b85303bb8 --- /dev/null +++ b/docs/compose.rst @@ -0,0 +1,4 @@ +Docker Compose +============== + +Docker compose is described in :ref:`Testcontainers Core`. diff --git a/conf.py b/docs/conf.py similarity index 90% rename from conf.py rename to docs/conf.py index b310e939b..2f342cd16 100644 --- a/conf.py +++ b/docs/conf.py @@ -18,6 +18,10 @@ # import sys # sys.path.insert(0, os.path.abspath('.')) +from pathlib import Path + +BASE_DIR = Path(__file__).parent.parent.resolve() +TEST_DIR = BASE_DIR / "tests" # -- General configuration ------------------------------------------------ @@ -33,6 +37,7 @@ "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", + "sphinx.ext.autosectionlabel", ] # Configure autodoc to avoid excessively long fully-qualified names. @@ -160,5 +165,14 @@ intersphinx_mapping = { "python": ("https://docs.python.org/3", None), - "selenium": ("https://seleniumhq.github.io/selenium/docs/api/py/", None), + "selenium": ("https://www.selenium.dev/selenium/docs/api/py/", None), + "typing_extensions": ("https://typing-extensions.readthedocs.io/en/latest/", None), } + +nitpick_ignore = [ + ("py:class", "typing_extensions.Self"), + ("py:class", "docker.models.containers.ExecResult"), + ("py:class", "testcontainers.core.docker_client.ContainerInspectInfo"), +] + +doctest_global_setup = f"TEST_DIR = '{TEST_DIR}'" diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 000000000..ebba8cd41 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,126 @@ +# Contributing to `testcontainers-python` + +Welcome to the `testcontainers-python` community! +This should give you an idea about how we build, test and release `testcontainers-python`! + +Highly recommended to read this document thoroughly to understand what we're working on right now +and what our priorities are before you are trying to contribute something. + +This will greatly increase your chances of getting prompt replies as the maintainers are volunteers themselves. + +## Before you begin + +We recommend following these steps: + +1. Finish reading this document. +2. Read the [recently updated issues](https://github.com/testcontainers/testcontainers-python/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc){:target="\_blank"} +3. Look for existing issues on the subject you are interested in - we do our best to label everything correctly + +## Local development + +### Pre-Requisites + +You need to have the following tools available to you: + +- `make` - You'll need a GNU Make for common developer activities +- `uv` - This is the primary package manager for the project + +### Build and test + +- Run `make install` to get `uv` to install all dependencies and set up `pre-commit` + - **Recommended**: Run `make` or `make help` to see other commands available to you. +- After this, you should have a working virtual environment and proceed with writing code with your favorite IDE +- **TIP**: You can run `make core/tests` or `make community//tests` to run the tests specifically for that to speed up feedback cycles +- You can also run `make lint` to run the `pre-commit` for the entire codebase. + +## Adding new modules + +We have an [issue template](https://github.com/testcontainers/testcontainers-python/blob/main/.github/ISSUE_TEMPLATE/new-container.md){:target="\_blank"} for adding new module containers, please refer to that for more information. +Once you've talked to the maintainers (we do our best to reply!) then you can proceed with contributing the new container. + +!!!WARNING + + Please raise an issue before you try to contribute a new container! It helps maintainersunderstand your use-case and motivation. + This way we can keep pull requests forced on the "how", not the "why"! :pray: + It also gives maintainers a chance to give you last-minute guidance on caveats orexpectations, particularly with + new extra dependencies and how to manage them. + +### Module documentation + +Leave examples for others with your new module such as `docs/modules/_example.py`. You can create as many examples as you want. + +Create a new `docs/modules/.md` describing the basic use of the new container. There is a [starter template provided here](https://raw.githubusercontent.com/testcontainers/testcontainers-python/blob/main/docs/modules/template.md){:target="\_blank"}. + +Also create a `docs/community/.rst` with the `.. autoclass` and `.. title` directives pointing at your container class — this is used by the Sphinx doctest runner. + +!!! important + + Make sure to add your new module to the sidebar nav in the `mkdocs.yml` + +## Raising issues + +We have [Issue Templates](https://raw.githubusercontent.com/testcontainers/testcontainers-python/refs/heads/main/.github/ISSUE_TEMPLATE/new-container.md){:target="\_blank"} to cover most cases, please try to adhere to them, they will guide you through the process. +Try to look through the existing issues before you raise a new one. + +## Releasing versions + +We have automated Semantic Versioning and release via [release-please](https://github.com/testcontainers/testcontainers-python/blob/main/.github/workflows/release-please.yml){:target="\_blank"}. +This takes care of: + +- Detecting the next version, based on the commits that landed on `main` +- When a Release PR has been merged + - Create a GitHub Release with the CHANGELOG included + - Update the [CHANGELOG](https://github.com/testcontainers/testcontainers-python/blob/main/CHANGELOG.md){:target="\_blank"}, similar to the GitHub Release + - Release to PyPI via a [trusted publisher](https://docs.pypi.org/trusted-publishers/using-a-publisher/){:target="\_blank"} + - Automatically script updates in files where it's needed instead of hand-crafting it (i.e. in `pyproject.toml`) + +!!!DANGER + + Community modules are supported on a best-effort basis and for maintenance reasons, any change to them + is only covered under minor and patch changes. + Community modules changes DO NOT contribute to major version changes! + If your community module container was broken by a minor or patch version change, check out the change logs! + +## Documentation contributions + +The _Testcontainers for Go_ documentation is a static site built with [MkDocs](https://www.mkdocs.org/){:target="\_blank"}. +We use the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/){:target="\_blank"} theme, which offers a number of useful extensions to MkDocs. + +We publish our documentation using Netlify. + +### Adding code snippets + +To include code snippets in the documentation, we use the [codeinclude plugin](https://github.com/rnorth/mkdocs-codeinclude-plugin){:target="\_blank"}, which uses the following syntax: + +> <!--codeinclude-->
> [Human readable title for snippet](./relative_path_to_example_code.go) targeting_expression
> [Human readable title for snippet](./relative_path_to_example_code.go) targeting_expression
> <!--/codeinclude-->
+ +Where each title snippet in the same `codeinclude` block would represent a new tab +in the snippet, and each `targeting_expression` would be: + +- `block:someString` or +- `inside_block:someString` + +Please refer to the [codeinclude plugin documentation](https://github.com/rnorth/mkdocs-codeinclude-plugin){:target="\_blank"} for more information. + +### Previewing rendered content + +From the root directory of the repository, you can use the following command to build and serve the documentation locally: + +```shell +make serve-docs +``` + +It will use a Docker container to install the required dependencies and start a local server at `http://localhost:8000`. + +Once finished, you can destroy the container with the following command: + +```shell +make clean-docs +``` + +### PR preview deployments + +Note that documentation for pull requests will automatically be published by Netlify as 'deploy previews'. +These deployment previews can be accessed via the `deploy/netlify` check that appears for each pull request. + +Please check the GitHub comment Netlify posts on the PR for the URL to the deployment preview. diff --git a/docs/core/README.rst b/docs/core/README.rst new file mode 100644 index 000000000..2358838ac --- /dev/null +++ b/docs/core/README.rst @@ -0,0 +1,70 @@ +Testcontainers Core +=================== + +:code:`testcontainers-core` is the core functionality for spinning up Docker containers in test environments. + +.. automodule:: testcontainers.core.container + :members: + :undoc-members: + +.. autoclass:: testcontainers.core.network.Network + :members: + +.. autoclass:: testcontainers.core.image.DockerImage + +.. autoclass:: testcontainers.core.generic.DbContainer + +.. autoclass:: testcontainers.core.wait_strategies.WaitStrategy + +.. autoclass:: testcontainers.core.transferable.Transferable + +.. raw:: html + +
+ +Compose +------- + +It is also possible to use Docker Compose functionality: + +.. automodule:: testcontainers.compose.compose + :members: + +.. raw:: html + +
+ +Examples +-------- + +Using `DockerContainer` and `DockerImage` to create a container: + +.. doctest:: + + >>> from testcontainers.core.container import DockerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + >>> from testcontainers.core.image import DockerImage + + >>> with DockerImage(path=f"{TEST_DIR}/core/image_fixtures/sample/", tag="test-sample:latest") as image: + ... with DockerContainer(str(image)) as container: + ... delay = wait_for_logs(container, "Test Sample Image") + +The `DockerImage` class is used to build the image from the specified path and tag. +The `DockerContainer` class is then used to create a container from the image. + +Copying a file from disk into a container: + +.. doctest:: + + >>> import tempfile + >>> from pathlib import Path + >>> from testcontainers.core.container import DockerContainer + + >>> with tempfile.TemporaryDirectory() as tmp: + ... my_file = Path(tmp) / "my_file.txt" + ... _ = my_file.write_text("file content") + ... with DockerContainer("bash", command="sleep infinity") as container: + ... container.copy_into_container(my_file, "/tmp/my_file.txt") + ... result = container.exec("cat /tmp/my_file.txt") + ... result.output + b'file content' diff --git a/docs/css/extra.css b/docs/css/extra.css new file mode 100644 index 000000000..4c700dac4 --- /dev/null +++ b/docs/css/extra.css @@ -0,0 +1,136 @@ +h1, h2, h3, h4, h5, h6 { + font-family: 'Rubik', sans-serif; +} + +[data-md-color-scheme="testcontainers"] { + --md-primary-fg-color: #00bac2; + --md-accent-fg-color: #361E5B; + --md-typeset-a-color: #0C94AA; + --md-primary-fg-color--dark: #291A3F; + --md-default-fg-color--lightest: #F2F4FE; + --md-footer-fg-color: #361E5B; + --md-footer-fg-color--light: #746C8F; + --md-footer-fg-color--lighter: #C3BEDE; + --md-footer-bg-color: #F7F9FD; + --md-footer-bg-color--dark: #F7F9FD; +} + +.card-grid { + display: grid; + gap: 10px; +} + +.tc-version { + font-size: 1.1em; + text-align: center; + margin: 0; +} + +@media (min-width: 680px) { + .card-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +body .card-grid-item { + display: flex; + align-items: center; + gap: 20px; + border: 1px solid #C3BEDE; + border-radius: 6px; + padding: 16px; + font-weight: 600; + color: #9991B5; + background: #F2F4FE; +} + +body .card-grid-item:hover, +body .card-grid-item:focus { + color: #9991B5; +} + +.card-grid-item[href] { + color: var(--md-primary-fg-color--dark); + background: transparent; +} + +.card-grid-item[href]:hover, +.card-grid-item[href]:focus { + background: #F2F4FE; + color: var(--md-primary-fg-color--dark); +} + +.community-callout-wrapper { + padding: 30px 10px 0 10px; +} + +.community-callout { + color: #F2F4FE; + background: linear-gradient(10.88deg, rgba(102, 56, 242, 0.4) 9.56%, #6638F2 100%), #291A3F; + box-shadow: 0px 20px 45px rgba(#9991B5, 0.75); + border-radius: 10px; + padding: 20px; +} + +.community-callout h2 { + font-size: 1.15em; + margin: 0 0 20px 0; + color: #F2F4FE; + text-align: center; +} + +.community-callout ul { + list-style: none; + padding: 0; + display: flex; + justify-content: space-between; + gap: 10px; + margin-top: 20px; + margin-bottom: 0; +} + +.community-callout a { + transition: opacity 0.2s ease; +} + +.community-callout a:hover { + opacity: 0.5; +} + +.community-callout a img { + height: 1.75em; + width: auto; + aspect-ratio: 1; +} + +@media (min-width: 1220px) { + .community-callout-wrapper { + padding: 40px 0 0; + } + + .community-callout h2 { + font-size: 1.25em; + } + + .community-callout a img { + height: 2em; + } +} + +@media (min-width: 1600px) { + .community-callout h2 { + font-size: 1.15em; + } + + .community-callout a img { + height: 1.75em; + } +} + +.md-typeset__table { + min-width: 100%; + } + + .md-typeset table:not([class]) { + display: table; +} diff --git a/docs/css/tc-header.css b/docs/css/tc-header.css new file mode 100644 index 000000000..de78d636e --- /dev/null +++ b/docs/css/tc-header.css @@ -0,0 +1,389 @@ + +:root { + --color-catskill: #F2F4FE; + --color-catskill-45: rgba(242, 244, 254, 0.45); + --color-mist: #E7EAFB; + --color-fog: #C3C7E6; + --color-smoke: #9991B5; + --color-smoke-75: rgba(153, 145, 181, 0.75); + --color-storm: #746C8F; + --color-topaz: #00BAC2; + --color-pacific: #17A6B2; + --color-teal: #027F9E; + --color-eggplant: #291A3F; + --color-plum: #361E5B; + +} + +#site-header { + color: var(--color-storm); + background: #fff; + font-family: 'Rubik', Arial, Helvetica, sans-serif; + font-size: 12px; + line-height: 1.5; + position: relative; + width: 100%; + z-index: 4; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 20px; +} + +body.tc-header-active #site-header { + z-index: 5; +} + +#site-header .brand { + display: flex; + justify-content: space-between; + gap: 20px; + width: 100%; +} + +#site-header .logo { + display: flex; +} + +#site-header .logo img, +#site-header .logo svg { + height: 30px; + width: auto; + max-width: 100%; +} + +#site-header #mobile-menu-toggle { + background: none; + border: none; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + color: var(--color-eggplant); + padding: 0; + margin: 0; + font-weight: 500; +} + +body.mobile-menu #site-header #mobile-menu-toggle { + color: var(--color-topaz); +} + +#site-header ul { + list-style: none; + padding: 0; + margin: 0; +} + +#site-header nav { + display: none; +} + +#site-header .menu-item { + display: flex; +} + +#site-header .menu-item button, +#site-header .menu-item a { + min-height: 30px; + display: flex; + gap: 6px; + align-items: center; + border: none; + background: none; + cursor: pointer; + padding: 0; + font-weight: 500; + color: var(--color-eggplant); + text-decoration: none; + font-size: 14px; + transition: color 0.2s ease; + white-space: nowrap; +} + +#site-header .menu-item .badge { + color: white; + font-size: 10px; + padding: 2px 6px; + background-color: #0FD5C6; // somehow $topaz is too dark for me. +text-align: center; + text-decoration: none; + display: inline-block; + border-radius: 6px; + &:hover { + + } +} + +#site-header .menu-item button:hover, +#site-header .menu-item a:hover { + color: var(--color-topaz); +} + +#site-header .menu-item button .icon-external, +#site-header .menu-item a .icon-externa { + margin-left: auto; + opacity: .3; + flex-shrink: 0; +} + +#site-header .menu-item button .icon-caret, +#site-header .menu-item a .icon-caret { + opacity: .3; + height: 8px; +} + +#site-header .menu-item button .icon-slack, +#site-header .menu-item a .icon-slack, +#site-header .menu-item button .icon-github, +#site-header .menu-item a .icon-github { + height: 18px; +} + +#site-header .menu-item .menu-dropdown { + flex-direction: column; +} + +body #site-header .menu-item .menu-dropdown { + display: none; +} + +#site-header .menu-item.has-children.active .menu-dropdown { + display: flex; + z-index: 10; +} + +#site-header .menu-dropdown-item + .menu-dropdown-item { + border-top: 1px solid var(--color-mist); +} + +#site-header .menu-dropdown-item a { + display: flex; + gap: 10px; + align-items: center; + padding: 10px 20px; + font-weight: 500; + color: var(--color-eggplant); + text-decoration: none; + transition: + color 0.2s ease, + background 0.2s ease; +} + +#site-header .menu-dropdown-item a .icon-external { + margin-left: auto; + color: var(--color-fog); + flex-shrink: 0; + opacity: 1; +} + +#site-header .menu-dropdown-item a:hover { + background-color: var(--color-catskill-45); +} + +#site-header .menu-dropdown-item a:hover .icon-external { + color: var(--color-topaz); +} + +#site-header .menu-dropdown-item a img { + height: 24px; +} + +.md-header { + background-color: var(--color-catskill); + color: var(--color-eggplant); +} + +.md-header.md-header--shadow { + box-shadow: none; +} + +.md-header__inner.md-grid { + max-width: 100%; + padding: 1.5px 20px; +} + +[dir=ltr] .md-header__title { + margin: 0; +} + +.md-header__topic:first-child { + font-size: 16px; + font-weight: 500; + font-family: 'Rubik', Arial, Helvetica, sans-serif; +} + +.md-header__title.md-header__title--active .md-header__topic, +.md-header__title[data-md-state=active] .md-header__topic { + opacity: 1; + pointer-events: all; + transform: translateX(0); + transition: none; + z-index: 0; +} + +.md-header__topic a { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + transition: color .2s ease; +} + +.md-header__topic a:hover { + color: var(--color-topaz); +} + +div.md-header__source { + width: auto; +} + +div.md-source__repository { + max-width: 100%; +} + +.md-main { + padding: 0 12px; +} + +@media screen and (min-width: 60em) { + form.md-search__form { + background-color: #FBFBFF; + color: var(--color-storm); + } + + form.md-search__form:hover { + background-color: #fff; + } + + .md-search__input + .md-search__icon { + color: var(--color-plum); + } + + .md-search__input::placeholder { + color: var(--color-smoke); + } +} + +@media (min-width: 500px) { + #site-header { + font-size: 16px; + padding: 20px 40px; + } + #site-header .logo img, + #site-header .logo svg { + height: 48px; + } + + #site-header .menu-item button .icon-caret, + #site-header .menu-item a .icon-caret { + height: 10px; + } + + #site-header .menu-item button .icon-slack, + #site-header .menu-item a .icon-slack, + #site-header .menu-item button .icon-github, + #site-header .menu-item a .icon-github { + height: 24px; + } + + .md-header__inner.md-grid { + padding: 5px 40px; + } + + .md-main { + padding: 0 32px; + } +} + +@media (min-width: 1024px) { + #site-header #mobile-menu-toggle { + display: none; + } + + #site-header nav { + display: block; + } + + #site-header .menu { + display: flex; + justify-content: center; + gap: 30px; + } + + #site-header .menu-item { + align-items: center; + position: relative; + } + + #site-header .menu-item button, + #site-header .menu-item a { + min-height: 48px; + gap: 8px; + font-size: 16px; + } + + #site-header .menu-item .menu-dropdown { + position: absolute; + top: 100%; + right: -8px; + border: 1px solid var(--color-mist); + border-radius: 6px; + background: #fff; + box-shadow: 0px 30px 35px var(--color-smoke-75); + min-width: 200px; + } +} + + +@media (max-width: 1023px) { + #site-header { + flex-direction: column; + } + + body.mobile-tc-header-active #site-header { + z-index: 5; + } + + body.mobile-menu #site-header nav { + display: flex; + } + + #site-header nav { + position: absolute; + top: calc(100% - 5px); + width: calc(100% - 80px); + flex-direction: column; + border: 1px solid var(--color-mist); + border-radius: 6px; + background: #fff; + box-shadow: 0px 30px 35px var(--color-smoke-75); + min-width: 200px; + } + + #site-header .menu-item { + flex-direction: column; + } + #site-header .menu-item + .menu-item { + border-top: 1px solid var(--color-mist); + } + + #site-header .menu-item button, + #site-header .menu-item a { + padding: 10px 20px; + } + + #site-header .menu-item.has-children.active .menu-dropdown { + border-top: 1px solid var(--color-mist); + } + + #site-header .menu-dropdown-item a { + padding: 10px 20px 10px 30px; + } +} + +@media (max-width: 499px) { + #site-header nav { + width: calc(100% - 40px); + } +} diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 000000000..311a0acaa Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/features/advanced_features.md b/docs/features/advanced_features.md new file mode 100644 index 000000000..e1f1ee2fc --- /dev/null +++ b/docs/features/advanced_features.md @@ -0,0 +1,424 @@ +# Advanced Features and Best Practices + +This document covers advanced features and best practices for using testcontainers-python in complex scenarios. + +## Docker-in-Docker (DinD) Support + +Testcontainers-python provides robust support for running tests inside Docker containers, enabling true isolation and reproducibility of test environments. This feature is particularly valuable for CI/CD pipelines, integration testing, and scenarios requiring consistent, isolated environments. + +### Use Cases + +- **CI/CD Pipelines**: Run tests in isolated containers within your CI environment +- **Integration Testing**: Test interactions between multiple services in a controlled environment +- **Environment Consistency**: Ensure tests run in the same environment across different machines +- **Resource Isolation**: Prevent test interference and resource conflicts + +### Connection Modes + +Testcontainers-python supports three connection modes for container networking: + +- **`bridge_ip`**: Use this mode when containers need to communicate over a bridge network. This is the default mode and provides isolated network communication between containers. +- **`gateway_ip`**: Use this mode when containers need to access the host network. This is useful when containers need to communicate with services running on the host machine. +- **`docker_host`**: Use this mode for local development. This mode uses the host's Docker socket directly, which is more efficient but provides less isolation. + +### Network Configuration + +Here's how to set up container networking: + +```python +from testcontainers.core.container import DockerContainer +from testcontainers.core.network import Network + +# Create an isolated network +network = Network() + +# Create containers on the network +container1 = DockerContainer("nginx:alpine") +container1.with_network(network) +container1.with_network_aliases(["web"]) + +container2 = DockerContainer("redis:alpine") +container2.with_network(network) +container2.with_network_aliases(["cache"]) +``` + +### Volume Mounting + +Mount host directories into containers for data persistence or configuration: + +```python +container = DockerContainer("nginx:alpine") +container.with_volume_mapping("/host/path", "/container/path", "ro") # Read-only mount +container.with_volume_mapping("/host/data", "/container/data", "rw") # Read-write mount +``` + +### Best Practices + +When working with Docker-in-Docker, it's crucial to follow a comprehensive set of best practices to ensure optimal performance, security, and maintainability. Start by carefully managing your resources: set appropriate memory and CPU limits for your containers, actively monitor their resource usage, and ensure proper cleanup after tests complete. This helps prevent resource exhaustion and maintains system stability. + +Security should be a top priority in your DinD implementation. Always use read-only volume mounts when possible to prevent unauthorized modifications, avoid running containers with privileged access unless absolutely necessary, and implement proper network isolation to prevent unauthorized access between containers. These measures help maintain a secure testing environment. + +For optimal performance, focus on using appropriate base images. Alpine-based images are often a good choice due to their small footprint, but consider your specific needs. Implement proper health checks to ensure containers are truly ready before proceeding with tests, and consider using container caching strategies to speed up test execution. When dealing with complex setups, consider using Docker Compose to manage multiple containers and their interactions. + +## ARM64 Support + +Testcontainers-python provides comprehensive support for ARM64 architecture through automatic emulation, making it seamless to run tests on ARM-based systems like Apple Silicon (M1/M2) Macs and ARM-based cloud instances. + +### Using ARM64 Support + +```python +from testcontainers.core.container import DockerContainer + +# Basic usage with automatic emulation +container = DockerContainer("nginx:alpine") +container.maybe_emulate_amd64() # Automatically handles ARM64 emulation + +# Advanced configuration with resource limits +container = DockerContainer("nginx:alpine") +container.maybe_emulate_amd64() +container.with_memory_limit("512m") +container.with_cpu_limit(0.5) # Use 50% of available CPU +``` + +### Performance Considerations + +1. **Emulation Overhead**: + - Expect 20-30% performance impact when running x86_64 containers on ARM + - Use ARM-native images when available for better performance + - Consider using multi-architecture images (e.g., `nginx:alpine`) + +2. **Resource Management**: + - Monitor memory usage during emulation + - Adjust CPU limits based on your workload + - Use appropriate base images to minimize emulation overhead + +### Best Practices + +When working with ARM64 architecture, a thoughtful approach to image selection and resource management is essential. Prioritize using multi-architecture images when available, as they provide the best compatibility across different platforms. For optimal performance, use minimal base images to reduce emulation overhead, and thoroughly test your setup with different image variants to find the best balance between size and functionality. + +In your development workflow, ensure you test your applications on both ARM and x86_64 environments to catch any architecture-specific issues early. When setting up CI/CD pipelines, make sure they support ARM64 architecture and document any architecture-specific considerations in your project documentation. This helps maintain consistency across different development environments and deployment targets. + +## TCP Forwarding with Socat + +The `SocatContainer` provides powerful TCP forwarding capabilities, enabling complex networking scenarios and service communication patterns. + +### Using Socat Container + +```python +from testcontainers.socat import SocatContainer + +# Basic TCP forwarding +socat = SocatContainer() +socat.with_target(8080, "host.docker.internal", 80) +socat.start() + +# Multiple port forwarding +socat = SocatContainer() +socat.with_target(8080, "host.docker.internal", 80) +socat.with_target(5432, "postgres", 5432) # Forward to another container +socat.start() + +# UDP forwarding +socat = SocatContainer() +socat.with_target(53, "8.8.8.8", 53, protocol="udp") +socat.start() +``` + +### Advanced Configuration + +```python +# Custom Socat options +socat = SocatContainer() +socat.with_option("-d") # Enable debug output +socat.with_option("-v") # Verbose mode +socat.with_target(8080, "host.docker.internal", 80) +socat.start() +``` + +### Best Practices + +When working with Socat, security should be your primary concern. Only forward the ports that are absolutely necessary for your application to function, and implement appropriate access controls to prevent unauthorized access. For sensitive traffic, consider using TLS to encrypt the forwarded connections. Regularly monitor your forwarded connections to detect any suspicious activity or performance issues. + +Performance optimization is crucial for maintaining a responsive system. Monitor connection latency to identify potential bottlenecks, and adjust buffer sizes based on your specific use case. For high-load scenarios, consider implementing connection pooling to manage resources efficiently. Regular maintenance is also important: document your forwarding rules clearly, implement proper cleanup procedures, and monitor connection health to ensure reliable operation. + +## Environment Variables and Configuration + +Testcontainers-python offers flexible configuration options through environment variables, configuration files, and properties. + +### Using Environment Variables + +**Direct Environment Variables**: + +```python +container = DockerContainer("nginx:alpine") +container.with_env("NGINX_HOST", "example.com") +container.with_env("NGINX_PORT", "8080") +container.with_env("DEBUG", "true") +``` + +**Environment Files**: + +```python +# .env file +NGINX_HOST=example.com +NGINX_PORT=8080 +DEBUG=true + +# Python code +container = DockerContainer("nginx:alpine") +container.with_env_file(".env") +``` + +**Configuration Properties**: + +```properties +# .testcontainers.properties +ryuk.container.privileged=true +ryuk.reconnection.timeout=10s +docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy +``` + +### Best Practices + +Configuration management in testcontainers-python requires a careful balance between flexibility and security. Never commit sensitive data to version control; instead, use environment variables for secrets and consider implementing a secrets manager for more complex scenarios. When dealing with configuration files, ensure they are well-documented and include validation to catch errors early. + +In your development workflow, provide example configuration files to help new team members get started quickly. Document all required environment variables and their purposes, and implement configuration testing to catch issues before they reach production. Use configuration templates to maintain consistency across different environments while allowing for environment-specific customization. + +## Container Health Checks + +Testcontainers-python provides robust health checking mechanisms to ensure containers are ready for testing. + +### Custom Health Checks + +```python +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready +import requests + +class WebContainer(DockerContainer): + @wait_container_is_ready() + def _connect(self): + response = requests.get(f"http://{self.get_container_host_ip()}:{self.get_exposed_port(80)}") + return response.status_code == 200 + +class DatabaseContainer(DockerContainer): + @wait_container_is_ready() + def _connect(self): + # Implement database connection check + pass +``` + +### Health Check Strategies + +1. **HTTP Health Checks**: + - Check HTTP endpoints + - Verify response status codes + - Validate response content + +2. **TCP Health Checks**: + - Verify port availability + - Check connection establishment + - Monitor connection stability + +3. **Application-Specific Checks**: + - Verify service readiness + - Check data consistency + - Validate business logic + +### Best Practices + +Health checks are a critical component of reliable containerized applications. When implementing health checks, use appropriate timeouts and implement retry mechanisms to handle temporary issues gracefully. Log health check failures with sufficient detail to aid in debugging, and consider using multiple check strategies to ensure comprehensive coverage of your application's health. + +Monitoring is essential for maintaining system health. Track health check metrics to identify patterns and potential issues, implement proper logging to capture relevant information, and set up alerts for failures to enable quick response to problems. Regular maintenance is also important: review your health checks periodically, update check criteria as your application evolves, and test check reliability to ensure they continue to provide accurate information. + +## Error Handling and Debugging + +### Common Issues and Solutions + +**Container Startup Failures**: + +```python +try: + container = DockerContainer("nginx:alpine") + container.start() +except Exception as e: + print(f"Container startup failed: {e}") + print(f"Container logs: {container.get_logs()}") + raise +``` + +**Network Issues**: + +```python +# Debug network configuration +container_id = container.get_wrapped_container().id +network_info = container.get_docker_client().inspect_network(network_name) +print(f"Network configuration: {network_info}") + +# Check container connectivity +host_ip = container.get_container_host_ip() +print(f"Container host IP: {host_ip}") +``` + +**Resource Cleanup**: + +```python +from contextlib import contextmanager + +@contextmanager +def managed_container(): + container = DockerContainer("nginx:alpine") + try: + container.start() + yield container + finally: + container.stop() + container.remove() +``` + +### Debugging Tools + +**Container Logs**: + +```python +# Get all logs +stdout, stderr = container.get_logs() + +# Get recent logs +stdout, stderr = container.get_logs(since="2024-01-01T00:00:00Z") + +# Follow logs +for line in container.get_logs(stream=True): + print(line) +``` + +**Container Information**: + +```python +# Get container details +container_id = container.get_wrapped_container().id +container_info = container.get_docker_client().inspect_container(container_id) + +# Get resource usage +stats = container.get_docker_client().stats(container_id) +``` + +**Network Information**: + +```python +# Get network details +network_name = container.get_docker_client().network_name(container_id) +network_info = container.get_docker_client().inspect_network(network_name) + +# List connected containers +connected_containers = container.get_docker_client().list_containers( + filters={"network": network_name} +) +``` + +### Best Practices + +Error handling and debugging in containerized environments require a systematic approach. Start by implementing proper validation and using appropriate timeouts to prevent common issues. Set up monitoring to catch problems early, and document known issues and their solutions to help team members resolve similar problems quickly. + +When debugging issues, collect relevant logs and analyze error patterns to identify root causes. Use appropriate tools for different types of problems, and document your solutions to build a knowledge base for future reference. Regular maintenance is crucial: perform regular system checks, keep documentation up to date, monitor error rates, and implement improvements based on your findings. + +## Performance Optimization + +Optimizing the performance of your testcontainers-python setup is crucial for maintaining efficient test execution and resource utilization. This section covers key strategies and best practices for achieving optimal performance. + +### Image Selection and Management + +The choice of base images significantly impacts your container's performance and resource usage. When selecting images, consider the following: + +```python +# Using minimal base images +container = DockerContainer("nginx:alpine") # ~7MB +container = DockerContainer("python:3.9-slim") # ~125MB +container = DockerContainer("python:3.9") # ~900MB + +# Using multi-stage builds for custom images +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient + +client = DockerClient() +client.build_image( + path=".", + tag="my-optimized-app:latest", + dockerfile=""" + FROM python:3.9-slim as builder + WORKDIR /app + COPY requirements.txt . + RUN pip install --no-cache-dir -r requirements.txt + + FROM python:3.9-slim + WORKDIR /app + COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages + COPY . . + """ +) +``` + +### Resource Management + +Proper resource allocation is essential for maintaining system stability and performance. Here's how to manage resources effectively: + +```python +# Setting resource limits +container = DockerContainer("nginx:alpine") +container.with_memory_limit("512m") # Limit memory usage +container.with_cpu_limit(0.5) # Use 50% of available CPU +container.with_shm_size("256m") # Set shared memory size + +# Monitoring resource usage +stats = container.get_docker_client().stats(container.get_wrapped_container().id) +print(f"CPU Usage: {stats['cpu_stats']['cpu_usage']['total_usage']}") +print(f"Memory Usage: {stats['memory_stats']['usage']}") +``` + +### Parallel Execution + +Running tests in parallel can significantly reduce overall execution time. Here's how to implement parallel execution: + +```python +import concurrent.futures +from testcontainers.core.container import DockerContainer + +def run_test(container_config): + with DockerContainer(**container_config) as container: + # Run your test + pass + +# Run multiple containers in parallel +container_configs = [ + {"image": "nginx:alpine", "ports": {"80": 8080}}, + {"image": "redis:alpine", "ports": {"6379": 6379}}, + {"image": "postgres:alpine", "ports": {"5432": 5432}} +] + +with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + futures = [executor.submit(run_test, config) for config in container_configs] + concurrent.futures.wait(futures) +``` + +### Caching Strategies + +Implementing effective caching strategies can significantly improve test execution time: + +```python +# Using Docker layer caching +container = DockerContainer("python:3.9-slim") +container.with_volume_mapping( + "${HOME}/.cache/pip", # Host pip cache + "/root/.cache/pip", # Container pip cache + "rw" +) + +# Using build cache +client = DockerClient() +client.build_image( + path=".", + tag="my-app:latest", + dockerfile="Dockerfile", + buildargs={"BUILDKIT_INLINE_CACHE": "1"} +) +``` diff --git a/docs/features/authentication.md b/docs/features/authentication.md new file mode 100644 index 000000000..239271768 --- /dev/null +++ b/docs/features/authentication.md @@ -0,0 +1,109 @@ +# Docker Authentication + +Testcontainers-Python supports various methods of authenticating with Docker registries. This is essential when working with private registries or when you need to pull images that require authentication. + +## Basic Authentication + +The simplest way to authenticate is using Docker's built-in credential store. Testcontainers-Python will automatically use credentials stored by Docker: + +```python +from testcontainers.community.generic import GenericContainer + +# Docker will automatically use stored credentials +container = GenericContainer("private.registry.com/myimage:latest") +``` + +## Environment Variables + +You can provide registry credentials using environment variables: + +```bash +# Set registry credentials +export DOCKER_USERNAME=myuser +export DOCKER_PASSWORD=mypassword +export DOCKER_REGISTRY=private.registry.com +``` + +## Configuration File + +You can also configure authentication in the `.testcontainers.properties` file: + +```properties +registry.username=myuser +registry.password=mypassword +registry.url=private.registry.com +``` + +## Programmatic Authentication + +For more control, you can provide credentials programmatically: + +```python +from testcontainers.core.config import TestcontainersConfiguration + +# Configure registry credentials +config = TestcontainersConfiguration() +config.registry_username = "myuser" +config.registry_password = "mypassword" +config.registry_url = "private.registry.com" + +# Use the configuration +container = GenericContainer("private.registry.com/myimage:latest") +``` + +## AWS ECR Authentication + +For Amazon Elastic Container Registry (ECR), Testcontainers-Python supports automatic authentication: + +```python +from testcontainers.community.generic import GenericContainer + +# ECR authentication is handled automatically +container = GenericContainer("123456789012.dkr.ecr.region.amazonaws.com/myimage:latest") +``` + +## Google Container Registry (GCR) + +For Google Container Registry, you can use Google Cloud credentials: + +```python +from testcontainers.community.generic import GenericContainer + +# GCR authentication using Google Cloud credentials +container = GenericContainer("gcr.io/myproject/myimage:latest") +``` + +## Azure Container Registry (ACR) + +For Azure Container Registry, you can use Azure credentials: + +```python +from testcontainers.community.generic import GenericContainer + +# ACR authentication using Azure credentials +container = GenericContainer("myregistry.azurecr.io/myimage:latest") +``` + +## Best Practices + +1. Never commit credentials to version control +2. Use environment variables or secure credential stores +3. Rotate credentials regularly +4. Use the least privileged credentials necessary +5. Consider using Docker credential helpers +6. Use registry-specific authentication when available +7. Keep credentials secure and encrypted +8. Use separate credentials for different environments + +## Troubleshooting + +If you encounter authentication issues: + +1. Verify your credentials are correct +2. Check if the registry is accessible +3. Ensure your Docker daemon is running +4. Check Docker's credential store +5. Verify network connectivity +6. Check for any proxy settings +7. Look for any rate limiting +8. Check registry-specific requirements diff --git a/docs/features/building_images.md b/docs/features/building_images.md new file mode 100644 index 000000000..615ef9a89 --- /dev/null +++ b/docs/features/building_images.md @@ -0,0 +1,192 @@ +# Building Images from Dockerfiles + +Testcontainers-Python allows you to build Docker images from Dockerfiles during test execution. This is useful when you need to test custom images or when you want to ensure your Dockerfile builds correctly. + +## Basic Image Building + +The simplest way to build an image is using the `DockerImage` class. It is a context manager that builds the image on entry and removes it on exit: + +```python +from testcontainers.core.container import DockerContainer +from testcontainers.core.image import DockerImage + +# Build an image from a build context containing a Dockerfile +with DockerImage(path="path/to/dockerfile/directory", tag="myapp:test") as image: + # Use the built image + with DockerContainer(str(image)) as container: + # Your test code here + pass +``` + +`path` is the build context directory. By default the Dockerfile is expected to be named `Dockerfile` within that directory. + +## Building with a Custom Dockerfile + +Use `dockerfile_path` to point at a Dockerfile with a different name or location (relative to the build context): + +```python +with DockerImage( + path="path/to/dockerfile/directory", + dockerfile_path="Dockerfile.test", + tag="myapp:test", +) as image: + ... +``` + +## Building with Options + +Any additional keyword arguments are forwarded to the underlying docker-py +[`images.build`](https://docker-py.readthedocs.io/en/stable/images.html#docker.models.images.ImageCollection.build) +call, so you can use all of its options. + +### Build Arguments + +```python +with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + buildargs={ + "VERSION": "1.0.0", + "ENVIRONMENT": "test", + }, +) as image: + ... +``` + +### Target Stage + +```python +with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + target="test", +) as image: + ... +``` + +### Disabling the Build Cache + +Use the `no_cache` argument to bypass the build cache (equivalent to the CLI's `--no-cache`): + +```python +with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + no_cache=True, +) as image: + ... +``` + +You can also reuse cache layers from existing images with `cache_from`: + +```python +with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + cache_from=["myapp:latest"], +) as image: + ... +``` + +### Platform + +```python +with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + platform="linux/amd64", +) as image: + ... +``` + +### Labels + +```python +with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + labels={ + "test": "true", + "environment": "test", + }, +) as image: + ... +``` + +## Inspecting the Build + +`DockerImage` exposes helpers to inspect the result of a build: + +```python +with DockerImage(path="path/to/dockerfile/directory", tag="myapp:test") as image: + print(image.short_id) # short image ID + print(str(image)) # tag if set, otherwise short ID + logs = image.get_logs() # list of build log entries + wrapped = image.get_wrapped_image() # underlying docker-py Image +``` + +## Cleaning Up + +By default the image is removed when the context manager exits. Set `clean_up=False` +to keep the image after the block completes: + +```python +with DockerImage(path="path/to/dockerfile/directory", tag="myapp:test", clean_up=False) as image: + ... +# image is still available here +``` + +## Best Practices + +1. Use appropriate tags +2. Clean up built images +3. Use build arguments for configuration +4. Consider build context size +5. Use appropriate build caching +6. Use appropriate platforms +7. Add meaningful labels + +## Common Use Cases + +### Building Test Images + +```python +def test_custom_image(): + with DockerImage( + path="path/to/dockerfile/directory", + tag="myapp:test", + buildargs={"TEST_MODE": "true"}, + ) as image: + with DockerContainer(str(image)) as container: + # Your test code here + pass +``` + +### Building for Different Environments + +```python +def test_different_environments(): + environments = ["dev", "test", "staging"] + + for env in environments: + with DockerImage( + path="path/to/dockerfile/directory", + tag=f"myapp:{env}", + buildargs={"ENVIRONMENT": env}, + ) as image: + with DockerContainer(str(image)) as container: + # Your test code here + pass +``` + +## Troubleshooting + +If you encounter issues with image building: + +1. Check Dockerfile syntax +2. Verify the build context (the `path` argument) +3. Check for missing files +4. Verify build arguments +5. Check for platform compatibility +6. Verify cache settings +7. Verify Docker daemon state diff --git a/docs/features/configuration.md b/docs/features/configuration.md new file mode 100644 index 000000000..fb9b1fcea --- /dev/null +++ b/docs/features/configuration.md @@ -0,0 +1,163 @@ +# Custom Configuration + +You can override some default properties if your environment requires it. + +## Configuration Locations + +The configuration will be loaded from multiple locations. Properties are considered in the following order: + +1. Environment variables +2. `.testcontainers.properties` in the user's home folder. Example locations: + **Linux:** `/home/myuser/.testcontainers.properties` + **Windows:** `C:/Users/myuser/.testcontainers.properties` + **macOS:** `/Users/myuser/.testcontainers.properties` + +Note that when using environment variables, configuration property names should be set in uppercase with underscore separators, preceded by `TESTCONTAINERS_` - e.g. `ryuk.disabled` becomes `TESTCONTAINERS_RYUK_DISABLED`. + +### Supported Properties + +Testcontainers-Python provides a configuration class to represent the settings: + +```python +from testcontainers.core.config import TestcontainersConfiguration + +# Default configuration +config = TestcontainersConfiguration() + +# Access configuration values +max_tries = config.max_tries +sleep_time = config.sleep_time +ryuk_image = config.ryuk_image +ryuk_privileged = config.ryuk_privileged +ryuk_disabled = config.ryuk_disabled +ryuk_docker_socket = config.ryuk_docker_socket +ryuk_reconnection_timeout = config.ryuk_reconnection_timeout +tc_host_override = config.tc_host_override +``` + +The following properties are supported: + +| Property | Environment Variable | Description | Default | +| --------------------------- | ------------------------------------------- | ---------------------------------------------------- | ------------------------- | +| `tc.host` | `TC_HOST` or `TESTCONTAINERS_HOST_OVERRIDE` | Testcontainers host address | - | +| `docker.host` | `DOCKER_HOST` | Address of the Docker daemon | - | +| `docker.tls.verify` | `DOCKER_TLS_VERIFY` | Enable/disable TLS verification | 0 | +| `docker.cert.path` | `DOCKER_CERT_PATH` | Path to Docker certificates | - | +| `ryuk.disabled` | `TESTCONTAINERS_RYUK_DISABLED` | Disable the Garbage Collector | false | +| `ryuk.container.privileged` | `TESTCONTAINERS_RYUK_PRIVILEGED` | Run Ryuk in privileged mode | false | +| `ryuk.reconnection.timeout` | `RYUK_RECONNECTION_TIMEOUT` | Time to wait before reconnecting | 10s | +| `ryuk.image` | `RYUK_CONTAINER_IMAGE` | Ryuk container image | testcontainers/ryuk:0.8.1 | +| `connection.mode` | `TESTCONTAINERS_CONNECTION_MODE` | Connection mode (bridge_ip, gateway_ip, docker_host) | - | + +Additional configuration options: + +| Environment Variable | Description | Default | +| --------------------- | ------------------------------------------- | ------- | +| `TC_MAX_TRIES` | Maximum number of connection attempts | 120 | +| `TC_POOLING_INTERVAL` | Time between connection attempts | 1 | +| `DOCKER_AUTH_CONFIG` | Docker authentication config (experimental) | - | + +## Docker Host Detection + +Testcontainers-Python will attempt to detect the Docker environment and configure everything to work automatically. + +However, sometimes customization is required. Testcontainers-Python will respect the following order: + +1. Read the **tc.host** property in the `~/.testcontainers.properties` file. E.g. `tc.host=tcp://my.docker.host:1234` + +2. Read the **TC_HOST** or **TESTCONTAINERS_HOST_OVERRIDE** environment variable. E.g. `TC_HOST=tcp://my.docker.host:1234` + +3. Read the **DOCKER_HOST** environment variable. E.g. `DOCKER_HOST=unix:///var/run/docker.sock` + See [Docker environment variables](https://docs.docker.com/engine/reference/commandline/cli/#environment-variables) for more information. + +4. Read the **current Docker context** (as set by `docker context use`) and use its host. E.g. with a context pointing at `ssh://user@remote-host`, no extra configuration is needed. + The built-in `default` context is skipped so local sockets / named pipes go through the standard fallback. + +5. Read the default Docker socket path, without the unix schema. E.g. `/var/run/docker.sock` + +6. Read the **docker.host** property in the `~/.testcontainers.properties` file. E.g. `docker.host=tcp://my.docker.host:1234` + +7. Read the rootless Docker socket path, checking the following alternative locations: + + 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock` + 2. `${HOME}/.docker/run/docker.sock` + 3. `${HOME}/.docker/desktop/docker.sock` + 4. `/run/user/${UID}/docker.sock`, where `${UID}` is the user ID of the current user + +8. The library will raise a `DockerHostError` if none of the above are set, meaning that the Docker host was not detected. + +## Docker Socket Path Detection + +Testcontainers-Python will attempt to detect the Docker socket path and configure everything to work automatically. + +However, sometimes customization is required. Testcontainers-Python will respect the following order: + +1. Read the **TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE** environment variable. Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other containers that need to perform Docker actions. + Example: `/var/run/docker-alt.sock` + +2. If the operating system retrieved by the Docker client is "Docker Desktop", and the host is running on Windows, it will return the `//var/run/docker.sock` UNC path. Otherwise, it returns the default Docker socket path for rootless Docker. + +3. Get the current Docker host from the existing strategies: see Docker host detection. + +4. If the socket contains the unix schema, the schema is removed (e.g. `unix:///var/run/docker.sock` -> `/var/run/docker.sock`) + +5. Otherwise, the default location of the Docker socket is used: `/var/run/docker.sock` + +The library will raise a `DockerHostError` if the Docker host cannot be discovered. + +## Connection Modes + +Testcontainers-Python supports different connection modes that determine how containers are accessed: + +1. `bridge_ip` (default): Uses the bridge network IP address +2. `gateway_ip`: Uses the gateway IP address +3. `docker_host`: Uses the Docker host address + +You can set the connection mode using the `TESTCONTAINERS_CONNECTION_MODE` environment variable or the `connection.mode` property in `.testcontainers.properties`. + +## Example Configuration File + +Here's an example of a `.testcontainers.properties` file: + +```properties +# Docker host configuration +docker.host=tcp://my.docker.host:1234 +docker.tls.verify=1 +docker.cert.path=/path/to/certs + +# Ryuk configuration +ryuk.disabled=false +ryuk.container.privileged=true +ryuk.reconnection.timeout=30s +ryuk.image=testcontainers/ryuk:0.8.1 + +# Testcontainers configuration +tc.host=tcp://my.testcontainers.host:1234 +connection.mode=bridge_ip +``` + +## Using Configuration in Code + +You can access and modify the configuration programmatically: + +```python +from testcontainers.core.config import testcontainers_config + +# Access configuration values +max_tries = testcontainers_config.max_tries +sleep_time = testcontainers_config.sleep_time + +# The configuration is read-only by default +# Changes should be made through environment variables or .testcontainers.properties +``` + +## Best Practices + +1. Use environment variables for CI/CD environments +2. Use `.testcontainers.properties` for local development +3. Set appropriate timeouts for your environment +4. Enable verbose logging when debugging +5. Consider disabling Ryuk if your environment already handles container cleanup +6. Use privileged mode for Ryuk only when necessary +7. Set proper TLS verification and certificate paths for secure environments +8. Choose the appropriate connection mode for your environment diff --git a/docs/features/container_logs.md b/docs/features/container_logs.md new file mode 100644 index 000000000..01cbc5f82 --- /dev/null +++ b/docs/features/container_logs.md @@ -0,0 +1,151 @@ +# Container Logs + +Testcontainers-Python provides several ways to access and follow container logs. This is essential for debugging and monitoring container behavior during tests. + +## Basic Log Access + +The simplest way to access logs is using the `get_logs` method: + +```python +from testcontainers.community.generic import GenericContainer + +with GenericContainer("nginx:alpine") as container: + # Get all logs + stdout, stderr = container.get_logs() + print(f"STDOUT: {stdout}") + print(f"STDERR: {stderr}") +``` + +## Following Logs + +To follow logs in real-time: + +```python +with GenericContainer("nginx:alpine") as container: + # Follow logs + for line in container.follow_logs(): + print(line) # Each line as it appears +``` + +## Log Access with Options + +You can customize log access with various options: + +```python +with GenericContainer("nginx:alpine") as container: + # Get logs with timestamps + stdout, stderr = container.get_logs(timestamps=True) + + # Get logs since a specific time + import datetime + since = datetime.datetime.now() - datetime.timedelta(minutes=5) + stdout, stderr = container.get_logs(since=since) + + # Get logs with tail + stdout, stderr = container.get_logs(tail=100) # Last 100 lines +``` + +## Log Streams + +You can access specific log streams: + +```python +with GenericContainer("nginx:alpine") as container: + # Get only stdout + stdout, _ = container.get_logs() + + # Get only stderr + _, stderr = container.get_logs() + + # Get both streams + stdout, stderr = container.get_logs() +``` + +## Log Following with Callback + +You can use a callback function to process logs: + +```python +def log_callback(line): + print(f"Log line: {line}") + +with GenericContainer("nginx:alpine") as container: + # Follow logs with callback + container.follow_logs(callback=log_callback) +``` + +## Log Access in Tests + +Here's how to use logs in tests: + +```python +import pytest +from testcontainers.community.generic import GenericContainer + +def test_container_logs(): + with GenericContainer("nginx:alpine") as container: + # Wait for specific log message + for line in container.follow_logs(): + if "Configuration complete" in line: + break + + # Verify log content + stdout, stderr = container.get_logs() + assert "Configuration complete" in stdout +``` + +## Best Practices + +1. Use appropriate log levels +2. Handle log streams separately +3. Use timestamps for debugging +4. Consider log rotation +5. Use log following for real-time monitoring +6. Clean up log resources +7. Use appropriate log formats +8. Consider log volume + +## Common Use Cases + +### Application Startup Verification + +```python +with GenericContainer("myapp:latest") as container: + # Wait for application to start + for line in container.follow_logs(): + if "Application started" in line: + break +``` + +### Error Detection + +```python +with GenericContainer("myapp:latest") as container: + # Monitor for errors + for line in container.follow_logs(): + if "ERROR" in line: + print(f"Error detected: {line}") +``` + +### Performance Monitoring + +```python +with GenericContainer("myapp:latest") as container: + # Monitor performance metrics + for line in container.follow_logs(): + if "Performance" in line: + print(f"Performance metric: {line}") +``` + +## Troubleshooting + +If you encounter issues with log access: + +1. Check container state +2. Verify log configuration +3. Check for log rotation +4. Verify log permissions +5. Check for log volume +6. Verify log format +7. Check for log buffering +8. Verify log drivers diff --git a/docs/features/copying_data.md b/docs/features/copying_data.md new file mode 100644 index 000000000..2d5a8f11b --- /dev/null +++ b/docs/features/copying_data.md @@ -0,0 +1,166 @@ +# Copying Data into Containers + +Testcontainers-Python provides several ways to copy data into containers. This is essential for setting up test data, configuration files, or any other files needed for your tests. + +## Basic File Copy + +The simplest way to copy a file is using the `copy_file_to_container` method: + +```python +from testcontainers.community.generic import GenericContainer + +with GenericContainer("alpine:latest") as container: + # Copy a single file + container.copy_file_to_container( + local_path="path/to/local/file.txt", + container_path="/path/in/container/file.txt" + ) +``` + +## Copying Multiple Files + +You can copy multiple files at once: + +```python +with GenericContainer("alpine:latest") as container: + # Copy multiple files + container.copy_files_to_container([ + ("path/to/local/file1.txt", "/path/in/container/file1.txt"), + ("path/to/local/file2.txt", "/path/in/container/file2.txt") + ]) +``` + +## Copying Directories + +You can copy entire directories: + +```python +with GenericContainer("alpine:latest") as container: + # Copy a directory + container.copy_directory_to_container( + local_path="path/to/local/directory", + container_path="/path/in/container/directory" + ) +``` + +## Copying with Permissions + +You can set permissions for copied files: + +```python +with GenericContainer("alpine:latest") as container: + # Copy file with specific permissions + container.copy_file_to_container( + local_path="path/to/local/file.txt", + container_path="/path/in/container/file.txt", + permissions=0o644 # rw-r--r-- + ) +``` + +## Copying with User + +You can specify the owner of copied files: + +```python +with GenericContainer("alpine:latest") as container: + # Copy file with specific owner + container.copy_file_to_container( + local_path="path/to/local/file.txt", + container_path="/path/in/container/file.txt", + user="nobody" + ) +``` + +## Copying from Memory + +You can copy data directly from memory: + +```python +with GenericContainer("alpine:latest") as container: + # Copy data from memory + data = b"Hello, World!" + container.copy_data_to_container( + data=data, + container_path="/path/in/container/file.txt" + ) +``` + +## Best Practices + +1. Use appropriate file permissions +2. Clean up copied files +3. Use absolute paths +4. Handle file encoding +5. Consider file size +6. Use appropriate owners +7. Handle file conflicts +8. Consider security implications + +## Common Use Cases + +### Setting Up Test Data + +```python +def test_with_data(): + with GenericContainer("alpine:latest") as container: + # Copy test data + container.copy_file_to_container( + local_path="tests/data/test_data.json", + container_path="/app/data/test_data.json" + ) + + # Copy configuration + container.copy_file_to_container( + local_path="tests/config/test_config.yaml", + container_path="/app/config/config.yaml" + ) +``` + +### Setting Up Application Files + +```python +def test_application(): + with GenericContainer("myapp:latest") as container: + # Copy application files + container.copy_directory_to_container( + local_path="app/static", + container_path="/app/static" + ) + + # Copy templates + container.copy_directory_to_container( + local_path="app/templates", + container_path="/app/templates" + ) +``` + +### Setting Up Database Files + +```python +def test_database(): + with GenericContainer("postgres:latest") as container: + # Copy database initialization script + container.copy_file_to_container( + local_path="tests/db/init.sql", + container_path="/docker-entrypoint-initdb.d/init.sql" + ) + + # Copy test data + container.copy_file_to_container( + local_path="tests/db/test_data.sql", + container_path="/docker-entrypoint-initdb.d/test_data.sql" + ) +``` + +## Troubleshooting + +If you encounter issues with copying data: + +1. Check file permissions +2. Verify file paths +3. Check file encoding +4. Verify file size +5. Check container state +6. Verify user permissions +7. Check for file conflicts +8. Verify disk space diff --git a/docs/features/creating_container.md b/docs/features/creating_container.md new file mode 100644 index 000000000..9f83772e8 --- /dev/null +++ b/docs/features/creating_container.md @@ -0,0 +1,195 @@ +# How to Create a Container + +Testcontainers-Python is a thin wrapper around Docker designed for use in tests. Anything you can run in Docker, you can spin up with Testcontainers-Python: + +- NoSQL databases or other data stores (e.g. Redis, ElasticSearch, MongoDB) +- Web servers/proxies (e.g. NGINX, Apache) +- Log services (e.g. Logstash, Kibana) +- Other services developed by your team/organization which are already Dockerized + +## Basic Container Creation + +The simplest way to create a container is using the `GenericContainer` class: + +```python +from testcontainers.community.generic import GenericContainer + +def test_basic_container(): + with GenericContainer("nginx:alpine") as nginx: + # Get container connection details + host = nginx.get_container_host_ip() + port = nginx.get_exposed_port(80) + + # Your test code here + # For example, make HTTP requests to the nginx server + import requests + response = requests.get(f"http://{host}:{port}") + assert response.status_code == 200 +``` + +## Advanced Container Configuration + +For more complex scenarios, use the `run` helper function. This high-level interface is similar to `docker run` and automatically handles: + +- Creating temporary networks +- Mounting files or tmpfs +- Waiting for container readiness +- Container cleanup + +Example with various configuration options: + +```python +import io +import pytest +from testcontainers.core.container import run +from testcontainers.core.network import DockerNetwork +from testcontainers.core.waiting_utils import wait_for_logs + +def test_nginx_advanced(): + # Create an isolated network + network = DockerNetwork() + network.create() + pytest.addfinalizer(network.remove) + + # Create a test file to mount + test_file_content = b"Hello from test file!" + host_file = io.BytesIO(test_file_content) + + # Run the container with various options + container = run( + image="nginx:alpine", + network=network.name, + files=[(host_file, "/usr/share/nginx/html/test.txt")], # Mount file + tmpfs={"/tmp": "rw"}, # Mount tmpfs + labels={"testcontainers.label": "true"}, # Add labels + environment={"TEST": "true"}, # Set environment variables + ports={"80/tcp": None}, # Expose port 80 + command=["nginx", "-g", "daemon off;"], # Override default command + wait=wait_for_logs("Configuration complete; ready for start"), # Wait for logs + startup_timeout=30, # Set startup timeout + ) + + # Ensure cleanup + pytest.addfinalizer(container.stop) + pytest.addfinalizer(container.remove) + + # Test the container + host = container.get_container_host_ip() + port = container.get_exposed_port(80) + + # Verify the mounted file + import requests + response = requests.get(f"http://{host}:{port}/test.txt") + assert response.text == "Hello from test file!" +``` + +## Container Lifecycle Management + +Testcontainers-Python offers several ways to manage container lifecycle: + +1. **Context manager (recommended):** +```python +with GenericContainer("nginx:alpine") as container: + # Container is automatically started and stopped + pass +``` + +2. **Manual management:** +```python +container = GenericContainer("nginx:alpine") +container.start() +try: + # Your test code here + pass +finally: + container.stop() + container.remove() +``` + +3. **Pytest fixtures:** +```python +import pytest +from testcontainers.community.generic import GenericContainer + +@pytest.fixture +def nginx_container(): + container = GenericContainer("nginx:alpine") + container.start() + yield container + container.stop() + container.remove() + +def test_with_nginx(nginx_container): + # Your test code here + pass +``` + +## Container Readiness + +For details on waiting for containers to be ready, see [Wait strategies](wait_strategies.md). + +## Container Information + +You can get detailed information about containers using the `get_container_info()` method. This works with both `DockerContainer` and `ComposeContainer`: + +```python +from testcontainers.community.generic import GenericContainer + +def test_container_info(): + with GenericContainer("nginx:alpine") as container: + # Get detailed container information + info = container.get_container_info() + + if info: + # Basic container information + print(f"Container ID: {info.Id}") + print(f"Name: {info.Name}") + print(f"Image: {info.Image}") + + # Container state + if info.State: + print(f"Status: {info.State.Status}") + print(f"Running: {info.State.Running}") + print(f"PID: {info.State.Pid}") + print(f"Exit Code: {info.State.ExitCode}") + + # Container configuration + if info.Config: + print(f"Hostname: {info.Config.Hostname}") + print(f"Environment: {info.Config.Env}") + print(f"Command: {info.Config.Cmd}") + + # Network information + network_settings = info.get_network_settings() + if network_settings and network_settings.Networks: + for network_name, network in network_settings.Networks.items(): + print(f"Network: {network_name}") + print(f" IP Address: {network.IPAddress}") + print(f" Gateway: {network.Gateway}") + print(f" MAC Address: {network.MacAddress}") +``` + +The container information is lazy-loaded and cached, so subsequent calls to `get_container_info()` will return the same data without making additional Docker API calls. + +### Available Information + +The `ContainerInspectInfo` object provides structured access to all Docker Engine API fields: + +- **Basic Info**: Container ID, name, image, creation time, platform +- **State**: Running status, PID, exit code, start/finish times, health status +- **Config**: Environment variables, command, working directory, labels, exposed ports +- **Network**: IP addresses, port bindings, network configurations, aliases +- **Host Config**: Memory limits, CPU settings, device mappings, restart policies +- **Mounts**: Volume and bind mount information with detailed options +- **Health**: Health check status and logs (if configured) +- **Platform**: Architecture and OS information + +## Best Practices + +1. Always use context managers or ensure proper cleanup +2. Set appropriate timeouts for container startup +3. Use isolated networks for tests +4. Mount test files instead of copying them +5. Use tmpfs for temporary data +6. Add meaningful labels to containers +7. Configure proper wait conditions diff --git a/docs/features/docker_compose.md b/docs/features/docker_compose.md new file mode 100644 index 000000000..6b874a348 --- /dev/null +++ b/docs/features/docker_compose.md @@ -0,0 +1,173 @@ +# Docker Compose Support + +Testcontainers-Python provides support for running Docker Compose environments in your tests. This is useful when you need to test against multiple containers that work together. + +## Basic Usage + +The simplest way to use Docker Compose is with the `DockerCompose` class: + +```python +from testcontainers.compose import DockerCompose + +# Create a compose environment +compose = DockerCompose( + context="path/to/compose/directory", + compose_file_name="docker-compose.yml" +) + +# Start the environment +with compose: + # Your test code here + pass +``` + +## Configuration Options + +The `DockerCompose` class supports various configuration options: + +```python +compose = DockerCompose( + context="path/to/compose/directory", + compose_file_name=["docker-compose.yml", "docker-compose.override.yml"], # Multiple compose files + pull=True, # Pull images before starting + build=True, # Build images before starting + wait=True, # Wait for services to be healthy + env_file=".env", # Environment file + services=["service1", "service2"], # Specific services to run + profiles=["profile1", "profile2"], # Compose profiles to use + keep_volumes=False # Whether to keep volumes after stopping +) +``` + +## Accessing Services + +You can access service information and interact with containers: + +```python +with DockerCompose("path/to/compose/directory") as compose: + # Get service host and port + host = compose.get_service_host("web") + port = compose.get_service_port("web", 8080) + + # Get both host and port + host, port = compose.get_service_host_and_port("web", 8080) + + # Execute commands in a container + stdout, stderr, exit_code = compose.exec_in_container( + ["ls", "-la"], + service_name="web" + ) + + # Get container logs + stdout, stderr = compose.get_logs("web") + + # Get detailed container information + container = compose.get_container("web") + info = container.get_container_info() + if info: + print(f"Container ID: {info.Id}") + if info.State: + print(f"Status: {info.State.Status}") + if info.Config: + print(f"Image: {info.Config.Image}") + + # Access network settings + network_settings = info.get_network_settings() + if network_settings and network_settings.Networks: + for name, network in network_settings.Networks.items(): + print(f"Network {name}: IP {network.IPAddress}") +``` + +## Waiting for Services + +You can wait for services to be ready: + +```python +with DockerCompose("path/to/compose/directory") as compose: + # Wait for a specific URL to be accessible + compose.wait_for("http://localhost:8080/health") +``` + +## Example with Multiple Services + +Here's a complete example using multiple services: + +```python +from testcontainers.compose import DockerCompose +import requests + +def test_web_application(): + compose = DockerCompose( + "path/to/compose/directory", + compose_file_name="docker-compose.yml", + pull=True, + build=True + ) + + with compose: + # Get web service details + host = compose.get_service_host("web") + port = compose.get_service_port("web", 8080) + + # Make a request to the web service + response = requests.get(f"http://{host}:{port}/api/health") + assert response.status_code == 200 + + # Execute a command in the database service + stdout, stderr, exit_code = compose.exec_in_container( + ["psql", "-U", "postgres", "-c", "SELECT 1"], + service_name="db" + ) + assert exit_code == 0 +``` + +## Container Information + +You can get detailed information about containers using the `get_container_info()` method: + +```python +with DockerCompose("path/to/compose/directory") as compose: + container = compose.get_container("web") + info = container.get_container_info() + + if info: + # Basic container information + print(f"Container ID: {info.Id}") + print(f"Name: {info.Name}") + print(f"Image: {info.Image}") + + # Container state + if info.State: + print(f"Status: {info.State.Status}") + print(f"Running: {info.State.Running}") + print(f"PID: {info.State.Pid}") + print(f"Exit Code: {info.State.ExitCode}") + + # Container configuration + if info.Config: + print(f"Hostname: {info.Config.Hostname}") + print(f"Environment: {info.Config.Env}") + print(f"Command: {info.Config.Cmd}") + + # Network information + network_settings = info.get_network_settings() + if network_settings and network_settings.Networks: + for network_name, network in network_settings.Networks.items(): + print(f"Network: {network_name}") + print(f" IP Address: {network.IPAddress}") + print(f" Gateway: {network.Gateway}") + print(f" MAC Address: {network.MacAddress}") +``` + +The container information is lazy-loaded and cached, so subsequent calls to `get_container_info()` will return the same data without making additional Docker API calls. + +## Best Practices + +1. Use context managers (`with` statement) to ensure proper cleanup +2. Set appropriate timeouts for service startup +3. Use health checks in your compose files +4. Keep compose files in your test directory +5. Use environment variables for configuration +6. Consider using profiles for different test scenarios +7. Clean up volumes when not needed +8. Use specific service names in your tests diff --git a/docs/features/executing_commands.md b/docs/features/executing_commands.md new file mode 100644 index 000000000..410169c0e --- /dev/null +++ b/docs/features/executing_commands.md @@ -0,0 +1,136 @@ +# Executing Commands in Containers + +Testcontainers-Python provides several ways to execute commands inside containers. This is useful for setup, verification, and debugging during tests. + +## Basic Command Execution + +The simplest way to execute a command is using the `exec` method, passing either an argv list or a string: + +```python +from testcontainers.core.container import DockerContainer + +with DockerContainer("alpine:latest") as container: + # Execute a simple command (argv form) + result = container.exec(["ls", "-la"]) + print(result.exit_code) # 0 + print(result.output) # command output as bytes + print(result.output.decode()) # ...decoded to str + + # A string is also accepted + result = container.exec("ls -la") +``` + +`exec` returns a named `(exit_code, output)` tuple, so you can also unpack it directly: + +```python +exit_code, output = container.exec(["ls", "-la"]) +``` + +> **A string command is *not* run through a shell.** `docker-py` tokenizes it with `shlex.split`, so shell features such as pipes, redirections, and variable expansion are passed through literally — `container.exec("echo $HOME")` prints the text `$HOME`, not your home directory. When you need shell behavior, invoke a shell explicitly: `container.exec(["sh", "-c", "echo $HOME"])`. + +## Command Execution with Options + +To customize how a command runs — the user, environment, or working directory — pass an `ExecConfig`: + +```python +from testcontainers.core.container import DockerContainer, ExecConfig + +with DockerContainer("alpine:latest") as container: + # Execute command as a specific user + result = container.exec(ExecConfig(command=["whoami"], user="nobody")) + + # Execute command with environment variables + # (use a command that reads the environment, e.g. printenv -- a bare + # argv command is not shell-expanded, see the note above) + result = container.exec(ExecConfig(command=["printenv", "TEST_VAR"], environment={"TEST_VAR": "test_value"})) + + # Execute command in a working directory (str or pathlib.Path) + result = container.exec(ExecConfig(command=["pwd"], workdir="/tmp")) +``` + +`ExecConfig` is a frozen dataclass: only `command` is required, and `user`, `environment`, `workdir`, and `privileged` are optional. Because it is immutable, the idiomatic way to derive a variant is `dataclasses.replace`: + +```python +from dataclasses import replace + +base = ExecConfig(command=["pwd"], workdir="/tmp") +in_var = replace(base, workdir="/var") +``` + +## Command Execution with Privileges + +For commands that require elevated privileges, set `privileged=True`: + +```python +from testcontainers.core.container import DockerContainer, ExecConfig + +with DockerContainer("alpine:latest") as container: + # Execute command with privileges + result = container.exec(ExecConfig(command=["mount"], privileged=True)) +``` + +## Best Practices + +1. Handle command failures gracefully — check `exit_code` rather than assuming success +2. Use environment variables for configuration +3. Consider security implications of privileged commands +4. Clean up after command execution +5. Use appropriate user permissions +6. Decode `output` from bytes when you need text +7. Use an explicit `["sh", "-c", ...]` invocation for commands that rely on shell features + +## Common Use Cases + +### Database Setup + +```python +from testcontainers.community.postgres import PostgresContainer + +with PostgresContainer() as postgres: + # Create a database + postgres.exec(["createdb", "testdb"]) + + # Run migrations + postgres.exec(["psql", "-d", "testdb", "-f", "/path/to/migrations.sql"]) +``` + +### File Operations + +```python +from testcontainers.core.container import DockerContainer + +with DockerContainer("alpine:latest") as container: + # Create a directory + container.exec(["mkdir", "-p", "/data"]) + + # Set permissions + container.exec(["chmod", "755", "/data"]) + + # List files + result = container.exec(["ls", "-la", "/data"]) +``` + +### Service Management + +```python +from testcontainers.core.container import DockerContainer + +with DockerContainer("nginx:alpine") as container: + # Check service status + result = container.exec(["nginx", "-t"]) + + # Reload configuration + container.exec(["nginx", "-s", "reload"]) +``` + +## Troubleshooting + +If you encounter issues with command execution: + +1. Check command syntax and arguments +2. Verify user permissions +3. Check container state +4. Verify command availability +5. Verify environment variables +6. Check the working directory +7. Remember that string commands are tokenized, not shell-interpreted diff --git a/docs/features/garbage_collector.md b/docs/features/garbage_collector.md new file mode 100644 index 000000000..a396b1706 --- /dev/null +++ b/docs/features/garbage_collector.md @@ -0,0 +1,51 @@ +# Garbage Collector + +Testcontainers for Python includes a robust garbage collection mechanism to ensure that containers are properly cleaned up, even in unexpected scenarios. + +## How it Works + +The garbage collection is implemented using a special container called "Ryuk" (pronounced "reaper"). This container is automatically started when you create your first test container and is responsible for cleaning up resources when: + +1. The Python process exits normally +2. The Python process is terminated unexpectedly +3. The system crashes or loses power + +## Configuration + +The Ryuk container can be configured through environment variables: + +- `TESTCONTAINERS_RYUK_DISABLED`: Set to `true` to disable the Ryuk container (not recommended) +- `TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED`: Set to `true` to run Ryuk in privileged mode (default: `false`) +- `TESTCONTAINERS_RYUK_RECONNECTION_TIMEOUT`: Timeout for Ryuk reconnection attempts (default: `10s`) + +## Best Practices + +1. **Don't Disable Ryuk**: The Ryuk container is an important part of Testcontainers' cleanup mechanism. Only disable it if you have a specific reason and understand the implications. + +2. **Use Context Managers**: Always use the `with` statement when creating containers. This ensures proper cleanup even if an exception occurs: + +```python +with RedisContainer() as redis: + # Your test code here +``` + +3. **Session Management**: Each test session gets a unique session ID, and Ryuk tracks containers by this ID. This allows for proper cleanup even when running tests in parallel. + +## Troubleshooting + +If you notice containers not being cleaned up: + +1. Check if Ryuk is running: `docker ps | grep testcontainers-ryuk` +2. Verify that the containers have the correct session label: `docker inspect | grep session-id` +3. Check Ryuk logs: `docker logs ` + +## Implementation Details + +The Ryuk container is a lightweight container that: + +1. Connects to the Docker daemon +2. Listens for container events +3. Automatically removes containers when their parent process exits +4. Handles reconnection if the connection to Docker is lost + +This provides a more reliable cleanup mechanism than relying solely on Python's garbage collection or process termination handlers. diff --git a/docs/features/networking.md b/docs/features/networking.md new file mode 100644 index 000000000..c0a8275ab --- /dev/null +++ b/docs/features/networking.md @@ -0,0 +1,204 @@ +# Networking and Container Communication + +Testcontainers-Python provides several ways to configure networking between containers and your test code. This is essential for testing distributed systems and microservices. + +## Connection Modes + +Testcontainers-Python supports three connection modes that determine how containers are accessed: + +1. `bridge_ip` (default): Uses the bridge network IP address. Best for: + + - Docker-in-Docker (DinD) scenarios + - When containers need to communicate over a bridge network + - When you need direct container-to-container communication + +2. `gateway_ip`: Uses the gateway IP address. Best for: + + - Docker-in-Docker (DinD) scenarios + - When containers need to access the host network + - When you need to access services running on the host + +3. `docker_host`: Uses the Docker host address. Best for: + + - Local development + - When running tests outside of containers + - When you need to access containers from the host machine + +You can set the connection mode using the `TESTCONTAINERS_CONNECTION_MODE` environment variable or the `connection.mode` property in `.testcontainers.properties`. + +## Port Exposure + +Testcontainers-Python provides two methods for exposing container ports, with `with_exposed_ports` being the recommended approach: + +### Exposing Ports with Random Host Ports (Recommended) + +```python +from testcontainers.core.container import DockerContainer + +container = DockerContainer("nginx:alpine") +container.with_exposed_ports(80, "443/tcp") # Expose ports, host ports will be assigned randomly +container.start() +mapped_port = container.get_exposed_port(80) # Get the randomly assigned host port +``` + +This is the preferred method because it: + +- Avoids port conflicts in parallel test execution +- Is more secure as it doesn't expose fixed ports +- Matches the behavior of other testcontainers implementations +- Allows for better isolation between test runs + +### Binding to Specific Host Ports (Not Recommended) + +```python +container = DockerContainer("nginx:alpine") +container.with_bind_ports(80, 8080) # Map container port 80 to host port 8080 +container.with_bind_ports("443/tcp", 8443) # Map container port 443 to host port 8443 +``` + +Use `with_bind_ports` only in specific cases where you absolutely need a fixed port number, such as: + +- When testing with tools that require specific port numbers +- When integrating with external systems that can't handle dynamic ports +- When debugging and need consistent port numbers + +Note that using fixed ports can cause conflicts when running tests in parallel and may lead to test failures if the specified ports are already in use. + +## Creating Networks + +You can create isolated networks for your containers: + +```python +from testcontainers.core.network import Network + +# Create a new network +network = Network() +network.create() + +# Use the network with containers +container1 = GenericContainer("nginx:alpine") +container1.with_network(network) +container1.with_network_aliases(["web"]) + +container2 = GenericContainer("redis:alpine") +container2.with_network(network) +container2.with_network_aliases(["cache"]) + +# Start containers +with container1, container2: + # Containers can communicate using their network aliases + # e.g., "web" can connect to "cache:6379" + pass +``` + +## Container Communication + +Containers can communicate with each other in several ways: + +1. Using network aliases: + +```python +# Container 1 can reach Container 2 using its network alias +container1 = GenericContainer("app:latest") +container1.with_network(network) +container1.with_network_aliases(["app"]) + +container2 = GenericContainer("db:latest") +container2.with_network(network) +container2.with_network_aliases(["database"]) + +# Container 1 can connect to Container 2 using "database:5432" +``` + +2. Using container IP addresses: + +```python +with container1, container2: + # Get container IP addresses + container1_ip = container1.get_container_host_ip() + container2_ip = container2.get_container_host_ip() + + # Containers can communicate using IP addresses + # e.g., container1 can connect to container2_ip:5432 +``` + +3. Using host networking: + +```python +container = GenericContainer("nginx:alpine") +container.with_network_mode("host") # Use host networking +``` + +## Example: Multi-Container Application + +Here's a complete example of a multi-container application: + +```python +from testcontainers.core.network import Network +from testcontainers.community.postgres import PostgresContainer +from testcontainers.community.redis import RedisContainer + +def test_multi_container_app(): + # Create a network + network = Network() + network.create() + + # Create containers + postgres = PostgresContainer() + postgres.with_network(network) + postgres.with_network_aliases(["db"]) + + redis = RedisContainer() + redis.with_network(network) + redis.with_network_aliases(["cache"]) + + # Start containers + with postgres, redis: + # Get connection details + db_host = postgres.get_container_host_ip() + db_port = postgres.get_exposed_port(5432) + + redis_host = redis.get_container_host_ip() + redis_port = redis.get_exposed_port(6379) + + # Your test code here + pass +``` + +## Best Practices + +1. **Port Management**: + + - Always use `with_exposed_ports` instead of `with_bind_ports` unless you have a specific requirement for fixed ports + - Use `get_exposed_port` to retrieve the mapped port number when using `with_exposed_ports` + - Avoid hardcoding port numbers in your tests + +2. **Network Configuration**: + + - Use isolated networks for tests to prevent conflicts + - Use meaningful network aliases for better readability and maintainability + - Avoid using host networking unless absolutely necessary + - Use the appropriate connection mode for your environment: + - `bridge_ip` for Docker-in-Docker (DinD) scenarios + - `gateway_ip` for accessing host network services + - `docker_host` for local development + +3. **Container Communication**: + + - Use network aliases for container-to-container communication + - Use environment variables for configuration + - Consider using Docker Compose for complex multi-container setups + +4. **Resource Management**: + + - Always use context managers (`with` statements) to ensure proper cleanup + - Let the Ryuk container handle cleanup in case of unexpected termination + - Clean up networks after tests + - Use environment variables for configuration + +5. **Testing Best Practices**: + - Write tests that are independent and can run in parallel + - Avoid dependencies on specific port numbers + - Use meaningful container and network names for debugging + - Consider using Docker Compose for complex setups + - Use environment variables for configuration diff --git a/docs/features/wait_strategies.md b/docs/features/wait_strategies.md new file mode 100644 index 000000000..65fad8ff1 --- /dev/null +++ b/docs/features/wait_strategies.md @@ -0,0 +1,129 @@ +# Wait Strategies + +Testcontainers-Python provides several strategies to wait for containers to be ready before proceeding with tests. This is crucial for ensuring that your tests don't start before the container is fully initialized and ready to accept connections. + +## Basic Wait Strategy + +The simplest way to wait for a container is using a structured wait strategy: + +```python +from testcontainers.core.wait_strategies import HttpWaitStrategy + +class MyContainer(DockerContainer): + def _connect(self): + HttpWaitStrategy(8080).wait_until_ready(self) +``` + +The strategy will retry until it succeeds or times out. By default, it will retry for 120 seconds with a 1-second interval between attempts. + +## Log-based Waiting + +Wait for specific log messages to appear: + +```python +from testcontainers.core.waiting_utils import wait_for_logs + +# Wait for a specific log message +container = GenericContainer( + "nginx:alpine", + wait=wait_for_logs("Configuration complete; ready for start") +) + +# Wait for a log pattern using regex +container = GenericContainer( + "postgres:latest", + wait=wait_for_logs("database system is ready to accept connections") +) + +# Wait for logs in both stdout and stderr +container = GenericContainer( + "myapp:latest", + wait=wait_for_logs("Ready", predicate_streams_and=True) +) +``` + +## HTTP-based Waiting + +Wait for an HTTP endpoint to be accessible: + +```python +from testcontainers.core.waiting_utils import wait_for_http + +# Wait for an HTTP endpoint +container = GenericContainer( + "nginx:alpine", + wait=wait_for_http("/", port=80) +) + +# Wait for a specific HTTP status code +container = GenericContainer( + "myapp:latest", + wait=wait_for_http("/health", port=8080, status_code=200) +) +``` + +## Custom Wait Conditions + +You can create custom wait conditions by implementing your own wait function: + +```python +def custom_wait(container): + # Your custom logic here + # Return True if the container is ready, False otherwise + return True + +container = GenericContainer( + "myapp:latest", + wait=custom_wait +) +``` + +## Connection-based Waiting + +Many container implementations include built-in connection waiting. For example: + +```python +from testcontainers.community.redis import RedisContainer +from testcontainers.community.postgres import PostgresContainer + +# Redis container waits for connection +redis = RedisContainer() +redis.start() # Will wait until Redis is ready to accept connections + +# PostgreSQL container waits for connection +postgres = PostgresContainer() +postgres.start() # Will wait until PostgreSQL is ready to accept connections +``` + +## Ryuk Container Wait Behavior + +The Ryuk container (used for garbage collection) has its own wait mechanism that combines log-based and connection-based waiting: + +1. **Log-based Wait**: Waits for the message ".\* Started!" with a 20-second timeout +2. **Connection Wait**: After the logs are found, attempts to establish a socket connection to the Ryuk container, retrying up to 50 times with a 0.5-second interval between attempts + +This ensures that the Ryuk container is fully operational before any test containers are started. + +## Configuring Wait Behavior + +You can configure the wait behavior using environment variables: + +- `TC_MAX_TRIES`: Maximum number of connection attempts (default: 120) +- `TC_POOLING_INTERVAL`: Time between connection attempts in seconds (default: 1) + +Example: + +```bash +export TC_MAX_TRIES=60 +export TC_POOLING_INTERVAL=2 +``` + +## Best Practices + +1. Always use appropriate wait strategies for your containers +2. Set reasonable timeouts for your environment +3. Use specific wait conditions rather than generic ones when possible +4. Consider using connection-based waiting for database containers +5. Use log-based waiting for applications that output clear startup messages +6. Use HTTP-based waiting for web services +7. Implement custom wait conditions for complex startup scenarios diff --git a/docs/getting_help.md b/docs/getting_help.md new file mode 100644 index 000000000..51a1227ac --- /dev/null +++ b/docs/getting_help.md @@ -0,0 +1,10 @@ +# Getting help + +We hope that you find Testcontainers intuitive to use and reliable. +However, sometimes things don't go the way we'd expect, and we'd like to try and help out if we can. + +To contact the Testcontainers team and other users you can: + +- Join our [Slack team](https://slack.testcontainers.org) +- [Search our issues tracker](https://github.com/testcontainers/testcontainers-python/issues), or raise a new issue if you find any bugs or have suggested improvements +- [Search Stack Overflow](https://stackoverflow.com/questions/tagged/testcontainers), especially among posts tagged with `testcontainers` diff --git a/docs/icons/github.svg b/docs/icons/github.svg new file mode 100644 index 000000000..8274d383d --- /dev/null +++ b/docs/icons/github.svg @@ -0,0 +1,4 @@ + + + diff --git a/docs/icons/slack.svg b/docs/icons/slack.svg new file mode 100644 index 000000000..1b371770b --- /dev/null +++ b/docs/icons/slack.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/docs/icons/stackoverflow.svg b/docs/icons/stackoverflow.svg new file mode 100644 index 000000000..0cf51ec46 --- /dev/null +++ b/docs/icons/stackoverflow.svg @@ -0,0 +1,5 @@ + + + + diff --git a/docs/icons/twitter.svg b/docs/icons/twitter.svg new file mode 100644 index 000000000..a6a902ce7 --- /dev/null +++ b/docs/icons/twitter.svg @@ -0,0 +1,4 @@ + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..3defde8c3 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,45 @@ +# Testcontainers for Python + +

Not using Python? Here are other supported languages!

+
+ Java + Go + .NET + Node.js + Python + Rust + Haskell + Ruby +
+ +## About Testcontainers For Python + +_Testcontainers for Python_ is a Python library that makes it simple to create and clean up container-based dependencies for automated integration or smoke tests. The easy-to-use API enables developers to programmatically define containers that should be run as part of a test and clean up those resources when the test is done. + +To start using _Testcontainers for Python_, see the [quickstart guide](quickstart.md). + +!!!note + + If you need a high-level explanation of _Testcontainers_, see the [main website](https://testcontainers.com/getting-started/). + +## Code Comments + +Inline documentation and docs where the code lives are crucial for us. Testcontainers For Python follows the [PEP 257](https://peps.python.org/pep-0257/){:target="\_blank"} comment conventions. The codebase previously supported Sphinx, so you may encounter comments not yet updated for the new documentation style. + +## Who Is Using Testcontainers Python? + +- [Timescale](https://www.timescale.com/) - Uses testcontainers-python in their pgai project for testing PostgreSQL integrations, AI model interactions, and AWS service integrations. +- [Redis](https://redis.io/) - Depends on testcontainers-python for their Redis vector library implementation. +- [Apache](https://skywalking.apache.org/) - Uses testcontainers-python in their Skywalking project for an application performance monitoring tool in distributed systems. + +## License + +See [LICENSE](https://raw.githubusercontent.com/testcontainers/testcontainers-python/refs/heads/main/LICENSE.txt){:target="\_blank"}. + +## Attributions + +## Copyright + +Copyright (c) 2015-2021 Sergey Pirogov and other authors. + +See [AUTHORS](https://github.com/testcontainers/testcontainers-python/graphs/contributors){:target="\_blank"} for contributors. diff --git a/index.rst b/docs/index.rst similarity index 95% rename from index.rst rename to docs/index.rst index 307f934c0..613e903c9 100644 --- a/index.rst +++ b/docs/index.rst @@ -14,16 +14,18 @@ testcontainers-python facilitates the use of Docker containers for functional an .. toctree:: :maxdepth: 1 + :glob: core/README - modules/index + compose + community/* Getting Started --------------- .. doctest:: - >>> from testcontainers.postgres import PostgresContainer + >>> from testcontainers.community.postgres import PostgresContainer >>> import sqlalchemy >>> with PostgresContainer("postgres:16") as postgres: @@ -38,7 +40,7 @@ The snippet above will spin up the current latest version of a postgres database .. doctest:: - >>> from testcontainers.postgres import PostgresContainer + >>> from testcontainers.community.postgres import PostgresContainer >>> import psycopg >>> with PostgresContainer("postgres:16", driver=None) as postgres: @@ -77,7 +79,7 @@ Crafting containers that are based on custom images is supported by the `core` m This allows you to create containers from images that are not part of the modules provided by testcontainers-python. -For common use cases, you can also use the generic containers provided by the `testcontainers-generic` module. Please check the `generic documentation `_ for more information. +For common use cases, you can also use the generic containers provided by the `testcontainers-generic` module. Please check the `generic documentation `_ for more information. (example: `ServerContainer` for running a FastAPI server) @@ -140,8 +142,8 @@ Development and Contributing ---------------------------- -We recommend you use a `Poetry `_ for development. -After having installed `poetry`, you can run the following snippet to set up your local dev environment. +We recommend you use a `uv `_ for development. +After having installed `uv`, you can run the following snippet to set up your local dev environment. .. code-block:: bash diff --git a/docs/js/tc-header.js b/docs/js/tc-header.js new file mode 100644 index 000000000..7d51ebf6b --- /dev/null +++ b/docs/js/tc-header.js @@ -0,0 +1,45 @@ +const mobileToggle = document.getElementById("mobile-menu-toggle"); +const mobileSubToggle = document.getElementById("mobile-submenu-toggle"); +function toggleMobileMenu() { + document.body.classList.toggle('mobile-menu'); + document.body.classList.toggle("mobile-tc-header-active"); +} +function toggleMobileSubmenu() { + document.body.classList.toggle('mobile-submenu'); +} +if (mobileToggle) + mobileToggle.addEventListener("click", toggleMobileMenu); +if (mobileSubToggle) + mobileSubToggle.addEventListener("click", toggleMobileSubmenu); + +const allParentMenuItems = document.querySelectorAll("#site-header .menu-item.has-children"); +function clearActiveMenuItem() { + document.body.classList.remove("tc-header-active"); + allParentMenuItems.forEach((item) => { + item.classList.remove("active"); + }); +} +function setActiveMenuItem(e) { + clearActiveMenuItem(); + e.currentTarget.closest(".menu-item").classList.add("active"); + document.body.classList.add("tc-header-active"); +} +allParentMenuItems.forEach((item) => { + const trigger = item.querySelector(":scope > a, :scope > button"); + + trigger.addEventListener("click", (e) => { + if (e.currentTarget.closest(".menu-item").classList.contains("active")) { + clearActiveMenuItem(); + } else { + setActiveMenuItem(e); + } + }); + + trigger.addEventListener("mouseenter", (e) => { + setActiveMenuItem(e); + }); + + item.addEventListener("mouseleave", (e) => { + clearActiveMenuItem(); + }); +}); diff --git a/docs/language-logos/dotnet.svg b/docs/language-logos/dotnet.svg new file mode 100644 index 000000000..496753d54 --- /dev/null +++ b/docs/language-logos/dotnet.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/language-logos/go.svg b/docs/language-logos/go.svg new file mode 100644 index 000000000..58ba79abd --- /dev/null +++ b/docs/language-logos/go.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/docs/language-logos/haskell.svg b/docs/language-logos/haskell.svg new file mode 100644 index 000000000..eb6de3776 --- /dev/null +++ b/docs/language-logos/haskell.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/language-logos/java.svg b/docs/language-logos/java.svg new file mode 100644 index 000000000..d9080555a --- /dev/null +++ b/docs/language-logos/java.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/docs/language-logos/nodejs.svg b/docs/language-logos/nodejs.svg new file mode 100644 index 000000000..34af396b0 --- /dev/null +++ b/docs/language-logos/nodejs.svg @@ -0,0 +1,5 @@ + + + diff --git a/docs/language-logos/python.svg b/docs/language-logos/python.svg new file mode 100644 index 000000000..c7ba2353b --- /dev/null +++ b/docs/language-logos/python.svg @@ -0,0 +1,8 @@ + + + + diff --git a/docs/language-logos/ruby.svg b/docs/language-logos/ruby.svg new file mode 100644 index 000000000..05537cedf --- /dev/null +++ b/docs/language-logos/ruby.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/language-logos/rust.svg b/docs/language-logos/rust.svg new file mode 100644 index 000000000..1691f56bb --- /dev/null +++ b/docs/language-logos/rust.svg @@ -0,0 +1,57 @@ + + + diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 000000000..88961b3e3 Binary files /dev/null and b/docs/logo.png differ diff --git a/docs/logo.svg b/docs/logo.svg new file mode 100644 index 000000000..bac0c391a --- /dev/null +++ b/docs/logo.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/modules/arangodb.md b/docs/modules/arangodb.md new file mode 100644 index 000000000..7cfe5d9ad --- /dev/null +++ b/docs/modules/arangodb.md @@ -0,0 +1,41 @@ +# ArangoDB + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for ArangoDB. + +## Adding this module to your project dependencies + +Please run the following command to add the ArangoDB module to your python dependencies: + +```bash +pip install testcontainers[arangodb] python-arango +``` + +## Usage example + + + +[Creating an ArangoDB container](arangodb_example.py) + + + +## Features + +- Multi-model database support (key-value, document, graph) +- AQL (ArangoDB Query Language) for complex queries +- Built-in aggregation functions +- Collection management +- Document CRUD operations +- Bulk document import + +## Configuration + +The ArangoDB container can be configured with the following parameters: + +- `username`: Database username (default: "root") +- `password`: Database password (default: "test") +- `port`: Port to expose (default: 8529) +- `version`: ArangoDB version to use (default: "latest") diff --git a/docs/modules/arangodb_example.py b/docs/modules/arangodb_example.py new file mode 100644 index 000000000..94f17725a --- /dev/null +++ b/docs/modules/arangodb_example.py @@ -0,0 +1,91 @@ +import json + +from arango import ArangoClient + +from testcontainers.community.arangodb import ArangoDbContainer + + +def basic_example(): + with ArangoDbContainer() as arango: + # Get connection parameters + host = arango.get_container_host_ip() + port = arango.get_exposed_port(arango.port) + username = arango.username + password = arango.password + + # Create ArangoDB client + client = ArangoClient(hosts=f"http://{host}:{port}") + db = client.db("_system", username=username, password=password) + print("Connected to ArangoDB") + + # Create a test database + db_name = "test_db" + if not db.has_database(db_name): + db.create_database(db_name) + print(f"Created database: {db_name}") + + # Switch to test database + test_db = client.db(db_name, username=username, password=password) + + # Create a test collection + collection_name = "test_collection" + if not test_db.has_collection(collection_name): + test_db.create_collection(collection_name) + print(f"Created collection: {collection_name}") + + collection = test_db.collection(collection_name) + + # Insert test documents + test_docs = [ + {"_key": "1", "name": "test1", "value": 100, "category": "A"}, + {"_key": "2", "name": "test2", "value": 200, "category": "B"}, + {"_key": "3", "name": "test3", "value": 300, "category": "A"}, + ] + + collection.import_bulk(test_docs) + print("Inserted test documents") + + # Query documents + cursor = test_db.aql.execute(""" + FOR doc IN test_collection + FILTER doc.category == "A" + RETURN doc + """) + + print("\nQuery results:") + for doc in cursor: + print(json.dumps(doc, indent=2)) + + # Execute a more complex query + cursor = test_db.aql.execute(""" + FOR doc IN test_collection + COLLECT category = doc.category + AGGREGATE + count = COUNT(1), + avg_value = AVG(doc.value), + min_value = MIN(doc.value), + max_value = MAX(doc.value) + RETURN { + category: category, + count: count, + avg_value: avg_value, + min_value: min_value, + max_value: max_value + } + """) + + print("\nAggregation results:") + for result in cursor: + print(json.dumps(result, indent=2)) + + # Get collection info + collection_info = collection.properties() + print("\nCollection properties:") + print(f"Name: {collection_info['name']}") + print(f"Type: {collection_info['type']}") + print(f"Status: {collection_info['status']}") + print(f"Count: {collection.count()}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/aws.md b/docs/modules/aws.md new file mode 100644 index 000000000..6983c6975 --- /dev/null +++ b/docs/modules/aws.md @@ -0,0 +1,23 @@ +# AWS + +Since testcontainers-python :material-tag: v4.8.0 + +## Introduction + +The Testcontainers module for AWS. + +## Adding this module to your project dependencies + +Please run the following command to add the AWS module to your python dependencies: + +```bash +pip install testcontainers[aws] httpx +``` + +## Usage example + + + +[Creating an AWS container](aws_example.py) + + diff --git a/docs/modules/aws_example.py b/docs/modules/aws_example.py new file mode 100644 index 000000000..0fca74f53 --- /dev/null +++ b/docs/modules/aws_example.py @@ -0,0 +1,117 @@ +import json +from datetime import datetime + +import boto3 + +from testcontainers.community.aws import AWSLambdaContainer + + +def basic_example(): + with AWSLambdaContainer() as aws: + # Get connection parameters + host = aws.get_container_host_ip() + port = aws.get_exposed_port(aws.port) + access_key = aws.access_key + secret_key = aws.secret_key + region = aws.region + + # Initialize AWS clients + s3 = boto3.client( + "s3", + endpoint_url=f"http://{host}:{port}", + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + region_name=region, + ) + + dynamodb = boto3.resource( + "dynamodb", + endpoint_url=f"http://{host}:{port}", + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + region_name=region, + ) + + sqs = boto3.client( + "sqs", + endpoint_url=f"http://{host}:{port}", + aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + region_name=region, + ) + + print("Connected to AWS services") + + # Test S3 + bucket_name = f"test-bucket-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}" + s3.create_bucket(Bucket=bucket_name) + print(f"\nCreated S3 bucket: {bucket_name}") + + # Upload a file + s3.put_object(Bucket=bucket_name, Key="test.txt", Body="Hello, S3!") + print("Uploaded test file") + + # List objects + objects = s3.list_objects(Bucket=bucket_name) + print("\nObjects in bucket:") + for obj in objects.get("Contents", []): + print(f"- {obj['Key']}") + + # Test DynamoDB + table_name = "test_table" + table = dynamodb.create_table( + TableName=table_name, + KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}], + AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + print(f"\nCreated DynamoDB table: {table_name}") + + # Wait for table to be created + table.meta.client.get_waiter("table_exists").wait(TableName=table_name) + + # Insert items + table.put_item(Item={"id": "1", "name": "Test Item", "value": 42, "timestamp": datetime.utcnow().isoformat()}) + print("Inserted test item") + + # Query items + response = table.scan() + print("\nDynamoDB items:") + for item in response["Items"]: + print(json.dumps(item, indent=2)) + + # Test SQS + queue_name = "test-queue" + queue = sqs.create_queue(QueueName=queue_name) + queue_url = queue["QueueUrl"] + print(f"\nCreated SQS queue: {queue_name}") + + # Send message + response = sqs.send_message(QueueUrl=queue_url, MessageBody="Hello, SQS!") + print(f"Sent message: {response['MessageId']}") + + # Receive message + messages = sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=1) + print("\nReceived messages:") + for message in messages.get("Messages", []): + print(json.dumps(message, indent=2)) + + # Clean up + # Delete S3 bucket and its contents + objects = s3.list_objects(Bucket=bucket_name) + for obj in objects.get("Contents", []): + s3.delete_object(Bucket=bucket_name, Key=obj["Key"]) + s3.delete_bucket(Bucket=bucket_name) + print("\nDeleted S3 bucket") + + # Delete DynamoDB table + table.delete() + print("Deleted DynamoDB table") + + # Delete SQS queue + sqs.delete_queue(QueueUrl=queue_url) + print("Deleted SQS queue") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/azurite.md b/docs/modules/azurite.md new file mode 100644 index 000000000..d6b8fcec8 --- /dev/null +++ b/docs/modules/azurite.md @@ -0,0 +1,23 @@ +# Azurite + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Azurite. + +## Adding this module to your project dependencies + +Please run the following command to add the Azurite module to your python dependencies: + +```bash +pip install testcontainers[azurite] azure-storage-blob +``` + +## Usage example + + + +[Creating an Azurite container](azurite_example.py) + + diff --git a/docs/modules/azurite_example.py b/docs/modules/azurite_example.py new file mode 100644 index 000000000..ef59e8e98 --- /dev/null +++ b/docs/modules/azurite_example.py @@ -0,0 +1,73 @@ +import json + +from azure.storage.blob import BlobServiceClient +from azure.storage.queue import QueueServiceClient + +from testcontainers.community.azurite import AzuriteContainer + + +def basic_example(): + with AzuriteContainer() as azurite: + # Get connection string + connection_string = azurite.get_connection_string() + + # Create BlobServiceClient + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + + # Create QueueServiceClient + queue_service_client = QueueServiceClient.from_connection_string(connection_string) + + # Create a test container + container_name = "test-container" + container_client = blob_service_client.create_container(container_name) + print(f"Created container: {container_name}") + + # Upload test blobs + test_data = [ + {"name": "test1", "value": 100, "category": "A"}, + {"name": "test2", "value": 200, "category": "B"}, + {"name": "test3", "value": 300, "category": "A"}, + ] + + for i, data in enumerate(test_data, 1): + blob_name = f"test{i}.json" + blob_client = container_client.get_blob_client(blob_name) + blob_client.upload_blob(json.dumps(data), overwrite=True) + print(f"Uploaded blob: {blob_name}") + + # List blobs + print("\nBlobs in container:") + for blob in container_client.list_blobs(): + print(f"Name: {blob.name}, Size: {blob.size} bytes") + + # Download and read a blob + blob_client = container_client.get_blob_client("test1.json") + blob_data = blob_client.download_blob() + content = json.loads(blob_data.readall()) + print("\nBlob content:") + print(json.dumps(content, indent=2)) + + # Create a test queue + queue_name = "test-queue" + queue_client = queue_service_client.create_queue(queue_name) + print(f"\nCreated queue: {queue_name}") + + # Send test messages + test_messages = ["Hello Azurite!", "This is a test message", "Queue is working!"] + + for msg in test_messages: + queue_client.send_message(msg) + print(f"Sent message: {msg}") + + # Receive messages + print("\nReceived messages:") + for _ in range(len(test_messages)): + message = queue_client.receive_message() + if message: + print(f"Message: {message.content}") + queue_client.delete_message(message.id, message.pop_receipt) + print("Deleted message") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/cassandra.md b/docs/modules/cassandra.md new file mode 100644 index 000000000..f903bcb4c --- /dev/null +++ b/docs/modules/cassandra.md @@ -0,0 +1,23 @@ +# Cassandra + +Since testcontainers-python :material-tag: v4.8.0 + +## Introduction + +The Testcontainers module for Cassandra. + +## Adding this module to your project dependencies + +Please run the following command to add the Cassandra module to your python dependencies: + +```bash +pip install testcontainers[cassandra] cassandra-driver +``` + +## Usage example + + + +[Creating a Cassandra container](cassandra_example.py) + + diff --git a/docs/modules/cassandra_example.py b/docs/modules/cassandra_example.py new file mode 100644 index 000000000..53bfa2db8 --- /dev/null +++ b/docs/modules/cassandra_example.py @@ -0,0 +1,153 @@ +import json +from datetime import datetime + +from cassandra.auth import PlainTextAuthProvider +from cassandra.cluster import Cluster + +from testcontainers.community.cassandra import CassandraContainer + + +def basic_example(): + with CassandraContainer() as cassandra: + # Get connection parameters + host = cassandra.get_container_host_ip() + port = cassandra.get_exposed_port(cassandra.port) + username = cassandra.username + password = cassandra.password + + # Create Cassandra client + auth_provider = PlainTextAuthProvider(username=username, password=password) + cluster = Cluster([host], port=port, auth_provider=auth_provider) + session = cluster.connect() + print("Connected to Cassandra") + + # Create keyspace + session.execute(""" + CREATE KEYSPACE IF NOT EXISTS test_keyspace + WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} + """) + print("Created keyspace") + + # Use keyspace + session.set_keyspace("test_keyspace") + + # Create table + session.execute(""" + CREATE TABLE IF NOT EXISTS test_table ( + id UUID PRIMARY KEY, + name text, + value int, + category text, + created_at timestamp + ) + """) + print("Created table") + + # Insert test data + test_data = [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "test1", + "value": 100, + "category": "A", + "created_at": datetime.utcnow(), + }, + { + "id": "550e8400-e29b-41d4-a716-446655440001", + "name": "test2", + "value": 200, + "category": "B", + "created_at": datetime.utcnow(), + }, + { + "id": "550e8400-e29b-41d4-a716-446655440002", + "name": "test3", + "value": 300, + "category": "A", + "created_at": datetime.utcnow(), + }, + ] + + insert_stmt = session.prepare(""" + INSERT INTO test_table (id, name, value, category, created_at) + VALUES (uuid(), ?, ?, ?, ?) + """) + + for data in test_data: + session.execute(insert_stmt, (data["name"], data["value"], data["category"], data["created_at"])) + print("Inserted test data") + + # Query data + print("\nQuery results:") + rows = session.execute("SELECT * FROM test_table WHERE category = 'A' ALLOW FILTERING") + for row in rows: + print( + json.dumps( + { + "id": str(row.id), + "name": row.name, + "value": row.value, + "category": row.category, + "created_at": row.created_at.isoformat(), + }, + indent=2, + ) + ) + + # Create materialized view + session.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS test_view AS + SELECT category, name, value, created_at + FROM test_table + WHERE category IS NOT NULL AND name IS NOT NULL + PRIMARY KEY (category, name) + """) + print("\nCreated materialized view") + + # Query materialized view + print("\nMaterialized view results:") + rows = session.execute("SELECT * FROM test_view WHERE category = 'A'") + for row in rows: + print( + json.dumps( + { + "category": row.category, + "name": row.name, + "value": row.value, + "created_at": row.created_at.isoformat(), + }, + indent=2, + ) + ) + + # Create secondary index + session.execute("CREATE INDEX IF NOT EXISTS ON test_table (value)") + print("\nCreated secondary index") + + # Query using secondary index + print("\nQuery using secondary index:") + rows = session.execute("SELECT * FROM test_table WHERE value > 150 ALLOW FILTERING") + for row in rows: + print( + json.dumps( + { + "id": str(row.id), + "name": row.name, + "value": row.value, + "category": row.category, + "created_at": row.created_at.isoformat(), + }, + indent=2, + ) + ) + + # Get table metadata + table_meta = session.cluster.metadata.keyspaces["test_keyspace"].tables["test_table"] + print("\nTable metadata:") + print(f"Columns: {[col.name for col in table_meta.columns.values()]}") + print(f"Partition key: {[col.name for col in table_meta.partition_key]}") + print(f"Clustering key: {[col.name for col in table_meta.clustering_key]}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/chroma.md b/docs/modules/chroma.md new file mode 100644 index 000000000..9df70af03 --- /dev/null +++ b/docs/modules/chroma.md @@ -0,0 +1,43 @@ +# Chroma + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Chroma. + +## Adding this module to your project dependencies + +Please run the following command to add the Chroma module to your python dependencies: + +```bash +pip install testcontainers[chroma] chromadb requests +``` + +## Usage example + + + +[Creating a Chroma container](chroma_example.py) + + + +## Features + +- Vector similarity search +- Document storage and retrieval +- Metadata filtering +- Collection management +- Embedding storage +- Distance metrics +- Batch operations +- REST API support + +## Configuration + +The Chroma container can be configured with the following parameters: + +- `port`: Port to expose (default: 8000) +- `version`: Chroma version to use (default: "latest") +- `persist_directory`: Directory to persist data (default: None) +- `allow_reset`: Whether to allow collection reset (default: True) diff --git a/docs/modules/chroma_example.py b/docs/modules/chroma_example.py new file mode 100644 index 000000000..52f4a5453 --- /dev/null +++ b/docs/modules/chroma_example.py @@ -0,0 +1,65 @@ +import chromadb +from chromadb.config import Settings + +from testcontainers.community.chroma import ChromaContainer + + +def basic_example(): + with ChromaContainer() as chroma: + # Get connection URL + connection_url = chroma.get_connection_url() + + # Create Chroma client + client = chromadb.HttpClient(host=connection_url, settings=Settings(allow_reset=True)) + + # Create a collection + collection_name = "test_collection" + collection = client.create_collection(name=collection_name) + print(f"Created collection: {collection_name}") + + # Add documents and embeddings + documents = [ + "This is a test document about AI", + "Machine learning is a subset of AI", + "Deep learning uses neural networks", + ] + + embeddings = [ + [0.1, 0.2, 0.3], # Simple example embeddings + [0.2, 0.3, 0.4], + [0.3, 0.4, 0.5], + ] + + ids = ["doc1", "doc2", "doc3"] + metadatas = [ + {"source": "test1", "category": "AI"}, + {"source": "test2", "category": "ML"}, + {"source": "test3", "category": "DL"}, + ] + + collection.add(documents=documents, embeddings=embeddings, ids=ids, metadatas=metadatas) + print("Added documents to collection") + + # Query the collection + results = collection.query(query_embeddings=[[0.1, 0.2, 0.3]], n_results=2) + + print("\nQuery results:") + print(f"Documents: {results['documents'][0]}") + print(f"Distances: {results['distances'][0]}") + print(f"Metadatas: {results['metadatas'][0]}") + + # Get collection info + collection_info = client.get_collection(collection_name) + print("\nCollection info:") + print(f"Name: {collection_info.name}") + print(f"Count: {collection_info.count()}") + + # List all collections + collections = client.list_collections() + print("\nAvailable collections:") + for coll in collections: + print(f"- {coll.name}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/clickhouse.md b/docs/modules/clickhouse.md new file mode 100644 index 000000000..f7eb7d31d --- /dev/null +++ b/docs/modules/clickhouse.md @@ -0,0 +1,44 @@ +# ClickHouse + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for ClickHouse. + +## Adding this module to your project dependencies + +Please run the following command to add the ClickHouse module to your python dependencies: + +```bash +pip install testcontainers[clickhouse] clickhouse-driver +``` + +## Usage example + + + +[Creating a ClickHouse container](clickhouse_example.py) + + + +## Features + +- Column-oriented storage +- High-performance analytics +- Real-time data processing +- SQL support +- Data compression +- Parallel processing +- Distributed queries +- Integration with pandas for data analysis + +## Configuration + +The ClickHouse container can be configured with the following parameters: + +- `port`: Port to expose (default: 9000) +- `version`: ClickHouse version to use (default: "latest") +- `user`: Database username (default: "default") +- `password`: Database password (default: "") +- `database`: Database name (default: "default") diff --git a/docs/modules/clickhouse_example.py b/docs/modules/clickhouse_example.py new file mode 100644 index 000000000..dad564d20 --- /dev/null +++ b/docs/modules/clickhouse_example.py @@ -0,0 +1,76 @@ +from datetime import datetime, timedelta + +import pandas as pd +from clickhouse_driver import Client + +from testcontainers.community.clickhouse import ClickHouseContainer + + +def basic_example(): + with ClickHouseContainer() as clickhouse: + # Get connection parameters + host = clickhouse.get_container_host_ip() + port = clickhouse.get_exposed_port(clickhouse.port) + + # Create ClickHouse client + client = Client(host=host, port=port) + + # Create a test table + client.execute(""" + CREATE TABLE IF NOT EXISTS test_table ( + id UInt32, + name String, + value Float64, + timestamp DateTime + ) ENGINE = MergeTree() + ORDER BY (id, timestamp) + """) + print("Created test table") + + # Generate test data + now = datetime.now() + data = [ + (1, "test1", 100.0, now), + (2, "test2", 200.0, now + timedelta(hours=1)), + (3, "test3", 300.0, now + timedelta(hours=2)), + ] + + # Insert data + client.execute("INSERT INTO test_table (id, name, value, timestamp) VALUES", data) + print("Inserted test data") + + # Query data + result = client.execute(""" + SELECT * + FROM test_table + ORDER BY id + """) + + print("\nQuery results:") + for row in result: + print(f"ID: {row[0]}, Name: {row[1]}, Value: {row[2]}, Timestamp: {row[3]}") + + # Execute a more complex query + result = client.execute(""" + SELECT + name, + avg(value) as avg_value, + min(value) as min_value, + max(value) as max_value + FROM test_table + GROUP BY name + ORDER BY avg_value DESC + """) + + print("\nAggregation results:") + for row in result: + print(f"Name: {row[0]}, Avg: {row[1]:.2f}, Min: {row[2]:.2f}, Max: {row[3]:.2f}") + + # Convert to pandas DataFrame + df = pd.DataFrame(result, columns=["name", "avg_value", "min_value", "max_value"]) + print("\nDataFrame:") + print(df) + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/cockroachdb.md b/docs/modules/cockroachdb.md new file mode 100644 index 000000000..2d353290c --- /dev/null +++ b/docs/modules/cockroachdb.md @@ -0,0 +1,44 @@ +# CockroachDB + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for CockroachDB. + +## Adding this module to your project dependencies + +Please run the following command to add the CockroachDB module to your python dependencies: + +```bash +pip install testcontainers[cockroachdb] sqlalchemy psycopg2 +``` + +## Usage example + + + +[Creating a CockroachDB container](cockroachdb_example.py) + + + +## Features + +- Distributed SQL database +- ACID transactions +- Strong consistency +- Horizontal scaling +- Built-in replication +- Automatic sharding +- SQL compatibility +- Integration with pandas for data analysis + +## Configuration + +The CockroachDB container can be configured with the following parameters: + +- `username`: Database username (default: "root") +- `password`: Database password (default: "") +- `database`: Database name (default: "postgres") +- `port`: Port to expose (default: 26257) +- `version`: CockroachDB version to use (default: "latest") diff --git a/docs/modules/cockroachdb_example.py b/docs/modules/cockroachdb_example.py new file mode 100644 index 000000000..77aaf4eb8 --- /dev/null +++ b/docs/modules/cockroachdb_example.py @@ -0,0 +1,90 @@ +import pandas as pd +import sqlalchemy +from sqlalchemy import text + +from testcontainers.community.cockroachdb import CockroachContainer + + +def basic_example(): + with CockroachContainer() as cockroach: + # Get connection URL + connection_url = cockroach.get_connection_url() + + # Create SQLAlchemy engine + engine = sqlalchemy.create_engine(connection_url) + + # Create a test table + with engine.begin() as conn: + conn.execute( + text(""" + CREATE TABLE IF NOT EXISTS test_table ( + id SERIAL PRIMARY KEY, + name VARCHAR(50), + value DECIMAL(10,2), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + ) + print("Created test table") + + # Insert test data + test_data = [(1, "test1", 100.0), (2, "test2", 200.0), (3, "test3", 300.0)] + + conn.execute( + text(""" + INSERT INTO test_table (id, name, value) + VALUES (:id, :name, :value) + """), + [{"id": item_id, "name": name, "value": value} for item_id, name, value in test_data], + ) + print("Inserted test data") + + # Query data + with engine.connect() as conn: + result = conn.execute( + text(""" + SELECT * + FROM test_table + ORDER BY id + """) + ) + + print("\nQuery results:") + for row in result: + print(f"ID: {row.id}, Name: {row.name}, Value: {row.value}, Created: {row.created_at}") + + # Execute a more complex query + with engine.connect() as conn: + result = conn.execute( + text(""" + SELECT + name, + AVG(value) as avg_value, + COUNT(*) as count, + MIN(created_at) as first_created, + MAX(created_at) as last_created + FROM test_table + GROUP BY name + ORDER BY avg_value DESC + """) + ) + + print("\nAggregation results:") + for row in result: + print( + f"Name: {row.name}, " + f"Avg: {row.avg_value:.2f}, " + f"Count: {row.count}, " + f"First: {row.first_created}, " + f"Last: {row.last_created}" + ) + + # Convert to pandas DataFrame + with engine.connect() as conn: + df = pd.read_sql("SELECT * FROM test_table ORDER BY id", conn) + print("\nDataFrame:") + print(df) + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/cosmosdb.md b/docs/modules/cosmosdb.md new file mode 100644 index 000000000..9c9aa494c --- /dev/null +++ b/docs/modules/cosmosdb.md @@ -0,0 +1,43 @@ +# CosmosDB + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for Azure Cosmos DB. + +## Adding this module to your project dependencies + +Please run the following command to add the CosmosDB module to your python dependencies: + +```bash +pip install testcontainers[cosmosdb] pymongo azure-cosmos +``` + +## Usage example + + + +[Creating a CosmosDB container](cosmosdb_example.py) + + + +## Features + +- Multi-model database support (document, key-value, wide-column, graph) +- SQL-like query language +- Automatic indexing +- Partitioning support +- Global distribution +- Built-in aggregation functions +- Container management +- Document CRUD operations + +## Configuration + +The CosmosDB container can be configured with the following parameters: + +- `port`: Port to expose (default: 8081) +- `version`: CosmosDB Emulator version to use (default: "latest") +- `ssl_verify`: Whether to verify SSL certificates (default: False) +- `emulator_key`: Emulator key for authentication (default: "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==") diff --git a/docs/modules/cosmosdb_example.py b/docs/modules/cosmosdb_example.py new file mode 100644 index 000000000..7c9a3fed2 --- /dev/null +++ b/docs/modules/cosmosdb_example.py @@ -0,0 +1,75 @@ +import json + +from azure.cosmos import CosmosClient, PartitionKey + +from testcontainers.community.cosmosdb import CosmosDbContainer + + +def basic_example(): + with CosmosDbContainer() as cosmos: + # Get connection parameters + endpoint = cosmos.get_connection_url() + key = cosmos.get_primary_key() + + # Create CosmosDB client + client = CosmosClient(endpoint, key) + + # Create a database + database_name = "test_database" + database = client.create_database_if_not_exists(id=database_name) + print(f"Created database: {database_name}") + + # Create a container + container_name = "test_container" + container = database.create_container_if_not_exists( + id=container_name, partition_key=PartitionKey(path="/category") + ) + print(f"Created container: {container_name}") + + # Insert test items + test_items = [ + {"id": "1", "category": "test1", "name": "Item 1", "value": 100}, + {"id": "2", "category": "test2", "name": "Item 2", "value": 200}, + {"id": "3", "category": "test1", "name": "Item 3", "value": 300}, + ] + + for item in test_items: + container.create_item(body=item) + print("Inserted test items") + + # Query items + query = "SELECT * FROM c WHERE c.category = 'test1'" + items = list(container.query_items(query=query, enable_cross_partition_query=True)) + + print("\nQuery results:") + for item in items: + print(json.dumps(item, indent=2)) + + # Execute a more complex query + query = """ + SELECT + c.category, + COUNT(1) as count, + AVG(c.value) as avg_value, + MIN(c.value) as min_value, + MAX(c.value) as max_value + FROM c + GROUP BY c.category + """ + + results = list(container.query_items(query=query, enable_cross_partition_query=True)) + + print("\nAggregation results:") + for result in results: + print(json.dumps(result, indent=2)) + + # Get container info + container_properties = container.read() + print("\nContainer properties:") + print(f"ID: {container_properties['id']}") + print(f"Partition Key: {container_properties['partitionKey']}") + print(f"Indexing Policy: {json.dumps(container_properties['indexingPolicy'], indent=2)}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/cratedb.md b/docs/modules/cratedb.md new file mode 100644 index 000000000..5d660ee5c --- /dev/null +++ b/docs/modules/cratedb.md @@ -0,0 +1,34 @@ +# CrateDB + +## Introduction + +The Testcontainers module for [CrateDB](https://cratedb.com), a distributed SQL +database for real-time analytics. CrateDB is PostgreSQL wire-compatible and +provides a SQLAlchemy dialect (`crate://`) that talks to its HTTP interface. + +## Adding this module to your project dependencies + +Please run the following command to add the CrateDB module to your python dependencies: + +```bash +pip install testcontainers[cratedb] sqlalchemy sqlalchemy-cratedb +``` + +## Usage example + + + +[Creating a CrateDB container](cratedb_example.py) + + + +## Configuration + +The CrateDB container can be configured with the following parameters: + +- `image`: Docker image to use (default: `"crate/crate:latest"`) +- `port`: container port used to build the connection URL (default: `4200`, the HTTP interface) +- `username`: Database username (default: `"crate"`, or the `CRATEDB_USER` env var) +- `password`: Database password (default: `"crate"`, or the `CRATEDB_PASSWORD` env var) +- `dialect`: SQLAlchemy dialect used in the connection URL (default: `"crate"`) +- `cmd_opts`: extra `-C=` options passed to CrateDB (merged over the single-node defaults) diff --git a/docs/modules/cratedb_example.py b/docs/modules/cratedb_example.py new file mode 100644 index 000000000..7d12d2b61 --- /dev/null +++ b/docs/modules/cratedb_example.py @@ -0,0 +1,43 @@ +import sqlalchemy +from sqlalchemy import text + +from testcontainers.community.cratedb import CrateDBContainer + + +def basic_example(): + with CrateDBContainer("crate:latest") as cratedb: + # CrateDB speaks the SQLAlchemy `crate://` dialect over its HTTP interface. + engine = sqlalchemy.create_engine(cratedb.get_connection_url()) + + with engine.begin() as conn: + conn.execute( + text(""" + CREATE TABLE IF NOT EXISTS summits ( + name TEXT PRIMARY KEY, + height INT + ) + """) + ) + print("Created table") + + conn.execute( + text("INSERT INTO summits (name, height) VALUES (:name, :height)"), + [ + {"name": "Mont Blanc", "height": 4808}, + {"name": "Monte Rosa", "height": 4634}, + {"name": "Dom", "height": 4545}, + ], + ) + # CrateDB is eventually consistent for reads; refresh to read-your-writes. + conn.execute(text("REFRESH TABLE summits")) + print("Inserted data") + + with engine.connect() as conn: + result = conn.execute(text("SELECT name, height FROM summits ORDER BY height DESC")) + print("\nQuery results:") + for row in result: + print(f"Name: {row.name}, Height: {row.height}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/db2.md b/docs/modules/db2.md new file mode 100644 index 000000000..c744fc9e4 --- /dev/null +++ b/docs/modules/db2.md @@ -0,0 +1,43 @@ +# DB2 + +Since testcontainers-python :material-tag: v4.8.0 + +## Introduction + +The Testcontainers module for IBM Db2. + +## Adding this module to your project dependencies + +Please run the following command to add the DB2 module to your python dependencies: + +```bash +pip install testcontainers[db2] sqlalchemy ibm-db +``` + +## Usage example + + + +[Creating a DB2 container](db2_example.py) + + + +## Features + +- Full SQL support +- Transaction management +- Stored procedures +- User-defined functions +- Advanced analytics +- JSON support +- Integration with pandas for data analysis + +## Configuration + +The DB2 container can be configured with the following parameters: + +- `username`: Database username (default: "db2inst1") +- `password`: Database password (default: "password") +- `database`: Database name (default: "testdb") +- `port`: Port to expose (default: 50000) +- `version`: DB2 version to use (default: "latest") diff --git a/docs/modules/db2_example.py b/docs/modules/db2_example.py new file mode 100644 index 000000000..6aa1d6153 --- /dev/null +++ b/docs/modules/db2_example.py @@ -0,0 +1,89 @@ +import ibm_db +import ibm_db_dbi +import pandas as pd + +from testcontainers.community.db2 import Db2Container + + +def basic_example(): + with Db2Container() as db2: + # Get connection parameters + host = db2.get_container_host_ip() + port = db2.get_exposed_port(db2.port) + database = db2.database + username = db2.username + password = db2.password + + # Create connection string + conn_str = f"DATABASE={database};HOSTNAME={host};PORT={port};PROTOCOL=TCPIP;UID={username};PWD={password}" + + # Connect to DB2 + conn = ibm_db.connect(conn_str, "", "") + print("Connected to DB2") + + # Create a test table + create_table_sql = """ + CREATE TABLE test_table ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR(50), + value DECIMAL(10,2), + created_at TIMESTAMP DEFAULT CURRENT TIMESTAMP + ) + """ + + try: + ibm_db.exec_immediate(conn, create_table_sql) + print("Created test table") + except Exception as e: + print(f"Table might already exist: {e}") + + # Insert test data + test_data = [(1, "test1", 100.0), (2, "test2", 200.0), (3, "test3", 300.0)] + + insert_sql = "INSERT INTO test_table (id, name, value) VALUES (?, ?, ?)" + stmt = ibm_db.prepare(conn, insert_sql) + + for row in test_data: + ibm_db.execute(stmt, row) + print("Inserted test data") + + # Query data using ibm_db_dbi + conn_dbi = ibm_db_dbi.Connection(conn) + cursor = conn_dbi.cursor() + + cursor.execute("SELECT * FROM test_table ORDER BY id") + rows = cursor.fetchall() + + print("\nQuery results:") + for row in rows: + print(f"ID: {row[0]}, Name: {row[1]}, Value: {row[2]}, Created: {row[3]}") + + # Execute a more complex query + cursor.execute(""" + SELECT + name, + AVG(value) as avg_value, + COUNT(*) as count, + MIN(created_at) as first_created, + MAX(created_at) as last_created + FROM test_table + GROUP BY name + ORDER BY avg_value DESC + """) + + print("\nAggregation results:") + for row in cursor.fetchall(): + print(f"Name: {row[0]}, Avg: {row[1]:.2f}, Count: {row[2]}, First: {row[3]}, Last: {row[4]}") + + # Convert to pandas DataFrame + df = pd.read_sql("SELECT * FROM test_table ORDER BY id", conn_dbi) + print("\nDataFrame:") + print(df) + + # Clean up + cursor.close() + ibm_db.close(conn) + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/elasticsearch.md b/docs/modules/elasticsearch.md new file mode 100644 index 000000000..1ba554520 --- /dev/null +++ b/docs/modules/elasticsearch.md @@ -0,0 +1,23 @@ +# Elasticsearch + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Elasticsearch. + +## Adding this module to your project dependencies + +Please run the following command to add the Elasticsearch module to your python dependencies: + +```bash +pip install testcontainers[elasticsearch] +``` + +## Usage example + + + +[Creating an Elasticsearch container](elasticsearch_example.py) + + diff --git a/docs/modules/elasticsearch_example.py b/docs/modules/elasticsearch_example.py new file mode 100644 index 000000000..036376f95 --- /dev/null +++ b/docs/modules/elasticsearch_example.py @@ -0,0 +1,105 @@ +import json +from datetime import datetime + +from elasticsearch import Elasticsearch + +from testcontainers.community.elasticsearch import ElasticsearchContainer + + +def basic_example(): + with ElasticsearchContainer() as elasticsearch: + # Get connection parameters + host = elasticsearch.get_container_host_ip() + port = elasticsearch.get_exposed_port(elasticsearch.port) + + # Create Elasticsearch client + es = Elasticsearch(f"http://{host}:{port}") + print("Connected to Elasticsearch") + + # Create index + index_name = "test_index" + index_settings = { + "settings": {"number_of_shards": 1, "number_of_replicas": 0}, + "mappings": { + "properties": { + "name": {"type": "text"}, + "value": {"type": "integer"}, + "category": {"type": "keyword"}, + "created_at": {"type": "date"}, + } + }, + } + + if not es.indices.exists(index=index_name): + es.indices.create(index=index_name, body=index_settings) + print(f"Created index: {index_name}") + + # Insert test documents + test_docs = [ + {"name": "test1", "value": 100, "category": "A", "created_at": datetime.utcnow()}, + {"name": "test2", "value": 200, "category": "B", "created_at": datetime.utcnow()}, + {"name": "test3", "value": 300, "category": "A", "created_at": datetime.utcnow()}, + ] + + for i, doc in enumerate(test_docs, 1): + es.index(index=index_name, id=i, document=doc) + print("Inserted test documents") + + # Refresh index + es.indices.refresh(index=index_name) + + # Search documents + search_query = {"query": {"bool": {"must": [{"term": {"category": "A"}}]}}} + + print("\nSearch results:") + response = es.search(index=index_name, body=search_query) + for hit in response["hits"]["hits"]: + print(json.dumps(hit["_source"], default=str, indent=2)) + + # Execute aggregation + agg_query = { + "size": 0, + "aggs": { + "categories": { + "terms": {"field": "category"}, + "aggs": { + "avg_value": {"avg": {"field": "value"}}, + "min_value": {"min": {"field": "value"}}, + "max_value": {"max": {"field": "value"}}, + }, + } + }, + } + + print("\nAggregation results:") + response = es.search(index=index_name, body=agg_query) + for bucket in response["aggregations"]["categories"]["buckets"]: + print(f"\nCategory: {bucket['key']}") + print(f"Count: {bucket['doc_count']}") + print(f"Avg value: {bucket['avg_value']['value']:.2f}") + print(f"Min value: {bucket['min_value']['value']}") + print(f"Max value: {bucket['max_value']['value']}") + + # Update document + update_body = {"doc": {"value": 150, "updated_at": datetime.utcnow()}} + es.update(index=index_name, id=1, body=update_body) + print("\nUpdated document") + + # Get document + doc = es.get(index=index_name, id=1) + print("\nUpdated document:") + print(json.dumps(doc["_source"], default=str, indent=2)) + + # Delete document + es.delete(index=index_name, id=2) + print("\nDeleted document") + + # Get index stats + stats = es.indices.stats(index=index_name) + print("\nIndex stats:") + print(f"Documents: {stats['indices'][index_name]['total']['docs']['count']}") + print(f"Size: {stats['indices'][index_name]['total']['store']['size_in_bytes']} bytes") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/generic.md b/docs/modules/generic.md new file mode 100644 index 000000000..ab2af61c0 --- /dev/null +++ b/docs/modules/generic.md @@ -0,0 +1,23 @@ +# Generic + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for running generic containers with various configurations and features. + +## Adding this module to your project dependencies + +Please run the following command to add the Generic module to your python dependencies: + +``` +pip install testcontainers[generic] +``` + +## Usage example + + + +[Creating a Generic container](generic_example.py) + + diff --git a/docs/modules/generic_example.py b/docs/modules/generic_example.py new file mode 100644 index 000000000..caa3eafab --- /dev/null +++ b/docs/modules/generic_example.py @@ -0,0 +1,115 @@ +import requests + +from testcontainers.community.generic import GenericContainer + + +def basic_example(): + # Example 1: Nginx container + with GenericContainer("nginx:latest") as nginx: + # Get connection parameters + host = nginx.get_container_host_ip() + port = nginx.get_exposed_port(80) + + # Test Nginx + response = requests.get(f"http://{host}:{port}") + print("\nNginx response:") + print(f"Status code: {response.status_code}") + print(f"Content type: {response.headers.get('content-type')}") + + # Example 2: Redis container with custom configuration + with GenericContainer("redis:latest") as redis: + # Get connection parameters + host = redis.get_container_host_ip() + port = redis.get_exposed_port(6379) + + # Test Redis + import redis + + r = redis.Redis(host=host, port=port) + r.set("test_key", "Hello, Redis!") + value = r.get("test_key") + print("\nRedis test:") + print(f"Retrieved value: {value.decode()}") + + # Example 3: PostgreSQL container with environment variables + with GenericContainer( + "postgres:latest", + environment={"POSTGRES_USER": "testuser", "POSTGRES_PASSWORD": "testpass", "POSTGRES_DB": "testdb"}, + ) as postgres: + # Get connection parameters + host = postgres.get_container_host_ip() + port = postgres.get_exposed_port(5432) + + # Test PostgreSQL + import psycopg2 + + conn = psycopg2.connect(host=host, port=port, user="testuser", password="testpass", database="testdb") + cur = conn.cursor() + cur.execute("SELECT version();") + version = cur.fetchone() + print("\nPostgreSQL test:") + print(f"Version: {version[0]}") + cur.close() + conn.close() + + # Example 4: Custom container with volume mounting + with GenericContainer("python:3.9-slim", volumes={"/tmp/test": {"bind": "/app", "mode": "rw"}}) as python: + # Get container ID + container_id = python.get_container_id() + print(f"\nPython container ID: {container_id}") + + # Execute command in container + exit_code, output = python.exec_run("python -c 'print(\"Hello from container!\")'") + print(f"Command output: {output.decode()}") + + # Example 5: Container with health check + with GenericContainer( + "mongo:latest", + healthcheck={ + "test": ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"], + "interval": 1000000000, # 1 second + "timeout": 3000000000, # 3 seconds + "retries": 3, + }, + ) as mongo: + # Get connection parameters + host = mongo.get_container_host_ip() + port = mongo.get_exposed_port(27017) + + # Test MongoDB + from pymongo import MongoClient + + client = MongoClient(f"mongodb://{host}:{port}") + db = client.test_db + collection = db.test_collection + collection.insert_one({"test": "Hello, MongoDB!"}) + result = collection.find_one() + print("\nMongoDB test:") + print(f"Retrieved document: {result}") + + # Example 6: Container with network + with GenericContainer("nginx:latest", network="test_network") as nginx_network: + # Get network info + network_info = nginx_network.get_network_info() + print("\nNetwork test:") + print(f"Network name: {network_info['Name']}") + print(f"Network ID: {network_info['Id']}") + + # Example 7: Container with resource limits + with GenericContainer("nginx:latest", mem_limit="512m", cpu_period=100000, cpu_quota=50000) as nginx_limits: + # Get container stats + stats = nginx_limits.get_stats() + print("\nResource limits test:") + print(f"Memory limit: {stats['memory_stats']['limit']}") + print(f"CPU usage: {stats['cpu_stats']['cpu_usage']['total_usage']}") + + # Example 8: Container with custom command + with GenericContainer("python:3.9-slim", command=["python", "-c", "print('Custom command test')"]) as python_cmd: + # Get logs + logs = python_cmd.get_logs() + print("\nCustom command test:") + print(f"Container logs: {logs.decode()}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/google.md b/docs/modules/google.md new file mode 100644 index 000000000..488034a2a --- /dev/null +++ b/docs/modules/google.md @@ -0,0 +1,23 @@ +# Google + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Google Cloud services. + +## Adding this module to your project dependencies + +Please run the following command to add the Google module to your python dependencies: + +```bash +pip install testcontainers[google] google-cloud-datastore google-cloud-pubsub +``` + +## Usage example + + + +[Creating a Google container](google_example.py) + + diff --git a/docs/modules/google_example.py b/docs/modules/google_example.py new file mode 100644 index 000000000..04d985644 --- /dev/null +++ b/docs/modules/google_example.py @@ -0,0 +1,127 @@ +import json +from datetime import datetime + +from google.cloud import bigquery, datastore, pubsub, storage + +from testcontainers.community.google import GoogleContainer + + +def basic_example(): + with GoogleContainer() as google: + # Get connection parameters + project_id = google.project_id + + # Initialize clients + storage_client = storage.Client(project=project_id) + pubsub_client = pubsub.PublisherClient() + bigquery_client = bigquery.Client(project=project_id) + datastore_client = datastore.Client(project=project_id) + + print("Connected to Google Cloud services") + + # Test Cloud Storage + bucket_name = f"test-bucket-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}" + bucket = storage_client.create_bucket(bucket_name) + print(f"\nCreated bucket: {bucket_name}") + + # Upload a file + blob = bucket.blob("test.txt") + blob.upload_from_string("Hello, Google Cloud Storage!") + print("Uploaded test file") + + # List files + blobs = list(bucket.list_blobs()) + print("\nFiles in bucket:") + for blob in blobs: + print(f"- {blob.name}") + + # Test Pub/Sub + topic_name = f"projects/{project_id}/topics/test-topic" + pubsub_client.create_topic(name=topic_name) + print(f"\nCreated topic: {topic_name}") + + # Create subscription + subscription_name = f"projects/{project_id}/subscriptions/test-subscription" + pubsub_client.create_subscription(name=subscription_name, topic=topic_name) + print(f"Created subscription: {subscription_name}") + + # Publish message + message = "Hello, Pub/Sub!" + future = pubsub_client.publish(topic_name, message.encode()) + message_id = future.result() + print(f"Published message: {message_id}") + + # Test BigQuery + dataset_id = "test_dataset" + bigquery_client.create_dataset(dataset_id) + print(f"\nCreated dataset: {dataset_id}") + + # Create table + table_id = f"{project_id}.{dataset_id}.test_table" + schema = [ + bigquery.SchemaField("name", "STRING"), + bigquery.SchemaField("age", "INTEGER"), + bigquery.SchemaField("city", "STRING"), + ] + table = bigquery_client.create_table(bigquery.Table(table_id, schema=schema)) + print(f"Created table: {table_id}") + + # Insert data + rows_to_insert = [ + {"name": "John", "age": 30, "city": "New York"}, + {"name": "Jane", "age": 25, "city": "Los Angeles"}, + {"name": "Bob", "age": 35, "city": "Chicago"}, + ] + errors = bigquery_client.insert_rows_json(table, rows_to_insert) + if not errors: + print("Inserted test data") + else: + print(f"Encountered errors: {errors}") + + # Query data + query = f"SELECT * FROM `{table_id}` WHERE age > 30" + query_job = bigquery_client.query(query) + results = query_job.result() + print("\nQuery results:") + for row in results: + print(json.dumps(dict(row), indent=2)) + + # Test Datastore + kind = "test_entity" + key = datastore_client.key(kind) + entity = datastore.Entity(key=key) + entity.update({"name": "Test Entity", "value": 42, "timestamp": datetime.utcnow()}) + datastore_client.put(entity) + print(f"\nCreated {kind} entity") + + # Query entities + query = datastore_client.query(kind=kind) + results = list(query.fetch()) + print("\nDatastore entities:") + for entity in results: + print(json.dumps(dict(entity), indent=2)) + + # Clean up + # Delete bucket and its contents + bucket.delete(force=True) + print("\nDeleted bucket") + + # Delete topic and subscription + pubsub_client.delete_subscription(subscription_name) + pubsub_client.delete_topic(topic_name) + print("Deleted Pub/Sub topic and subscription") + + # Delete BigQuery dataset and table + bigquery_client.delete_table(table_id) + bigquery_client.delete_dataset(dataset_id, delete_contents=True) + print("Deleted BigQuery dataset and table") + + # Delete Datastore entities + query = datastore_client.query(kind=kind) + keys = [entity.key for entity in query.fetch()] + datastore_client.delete_multi(keys) + print("Deleted Datastore entities") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/influxdb.md b/docs/modules/influxdb.md new file mode 100644 index 000000000..adba0da92 --- /dev/null +++ b/docs/modules/influxdb.md @@ -0,0 +1,27 @@ +# InfluxDB + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for InfluxDB. + +## Adding this module to your project dependencies + +Please run the following command to add the InfluxDB module to your python dependencies: + +```bash +# For InfluxDB 1.x +pip install testcontainers[influxdb] influxdb + +# For InfluxDB 2.x +pip install testcontainers[influxdb] influxdb-client +``` + +## Usage example + + + +[Creating an InfluxDB container](influxdb_example.py) + + diff --git a/docs/modules/influxdb_example.py b/docs/modules/influxdb_example.py new file mode 100644 index 000000000..97391a25f --- /dev/null +++ b/docs/modules/influxdb_example.py @@ -0,0 +1,170 @@ +import json +from datetime import datetime, timedelta + +from influxdb_client import InfluxDBClient, Point +from influxdb_client.client.write_api import SYNCHRONOUS + +from testcontainers.community.influxdb import InfluxDBContainer + + +def basic_example(): + with InfluxDBContainer() as influxdb: + # Get connection parameters + host = influxdb.get_container_host_ip() + port = influxdb.get_exposed_port(influxdb.port) + token = influxdb.token + org = influxdb.org + bucket = influxdb.bucket + + # Create InfluxDB client + client = InfluxDBClient(url=f"http://{host}:{port}", token=token, org=org) + print("Connected to InfluxDB") + + # Create write API + write_api = client.write_api(write_options=SYNCHRONOUS) + + # Create test data points + points = [] + for i in range(3): + point = ( + Point("test_measurement") + .tag("location", f"location_{i}") + .tag("device", f"device_{i}") + .field("temperature", 20 + i) + .field("humidity", 50 + i) + .time(datetime.utcnow() + timedelta(minutes=i)) + ) + points.append(point) + + # Write points + write_api.write(bucket=bucket, record=points) + print("Wrote test data points") + + # Create query API + query_api = client.query_api() + + # Query data + query = f'from(bucket: "{bucket}") |> range(start: -1h) |> filter(fn: (r) => r["_measurement"] == "test_measurement")' + + result = query_api.query(query) + print("\nQuery results:") + for table in result: + for record in table.records: + record_data = { + "measurement": record.get_measurement(), + "time": record.get_time().isoformat(), + "location": record.values.get("location"), + "device": record.values.get("device"), + "field": record.get_field(), + "value": record.get_value(), + } + print(json.dumps(record_data, indent=2)) + + # Create aggregation query + agg_query = f'from(bucket: "{bucket}") |> range(start: -1h) |> filter(fn: (r) => r["_measurement"] == "test_measurement") |> group(columns: ["location"]) |> mean()' + + agg_result = query_api.query(agg_query) + print("\nAggregation results:") + for table in agg_result: + for record in table.records: + record_data = { + "location": record.values.get("location"), + "field": record.get_field(), + "mean": record.get_value(), + } + print(json.dumps(record_data, indent=2)) + + # Create window query + window_query = f'from(bucket: "{bucket}") |> range(start: -1h) |> filter(fn: (r) => r["_measurement"] == "test_measurement") |> window(every: 5m) |> mean()' + + window_result = query_api.query(window_query) + print("\nWindow results:") + for table in window_result: + for record in table.records: + record_data = { + "window_start": record.get_start().isoformat(), + "window_stop": record.get_stop().isoformat(), + "field": record.get_field(), + "mean": record.get_value(), + } + print(json.dumps(record_data, indent=2)) + + # Create task + task_flux = ( + "option task = {\n" + ' name: "test_task",\n' + " every: 1h\n" + "}\n\n" + f'from(bucket: "{bucket}")\n' + " |> range(start: -1h)\n" + ' |> filter(fn: (r) => r["_measurement"] == "test_measurement")\n' + " |> mean()\n" + f' |> to(bucket: "{bucket}", measurement: "test_measurement_agg")' + ) + + tasks_api = client.tasks_api() + task = tasks_api.create_task(name="test_task", flux=task_flux, org=org) + print("\nCreated task") + + # Get task info + task_info = tasks_api.find_task_by_id(task.id) + print("\nTask info:") + task_data = { + "id": task_info.id, + "name": task_info.name, + "status": task_info.status, + "every": task_info.every, + } + print(json.dumps(task_data, indent=2)) + + # Create dashboard + dashboards_api = client.dashboards_api() + dashboard = dashboards_api.create_dashboard(name="test_dashboard", org=org) + print("\nCreated dashboard") + + # Add cell to dashboard + dashboards_api.create_dashboard_cell( + dashboard_id=dashboard.id, name="test_cell", x=0, y=0, w=6, h=4, query=query + ) + print("Added cell to dashboard") + + # Get dashboard info + dashboard_info = dashboards_api.find_dashboard_by_id(dashboard.id) + print("\nDashboard info:") + dashboard_data = { + "id": dashboard_info.id, + "name": dashboard_info.name, + "cells": len(dashboard_info.cells), + } + print(json.dumps(dashboard_data, indent=2)) + + # Create bucket + buckets_api = client.buckets_api() + new_bucket = buckets_api.create_bucket(bucket_name="test_bucket_2", org=org) + print("\nCreated new bucket") + + # Get bucket info + bucket_info = buckets_api.find_bucket_by_id(new_bucket.id) + print("\nBucket info:") + bucket_data = { + "id": bucket_info.id, + "name": bucket_info.name, + "org_id": bucket_info.org_id, + } + print(json.dumps(bucket_data, indent=2)) + + # Clean up + tasks_api.delete_task(task.id) + print("\nDeleted task") + + dashboards_api.delete_dashboard(dashboard.id) + print("Deleted dashboard") + + buckets_api.delete_bucket(new_bucket.id) + print("Deleted bucket") + + client.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/k3s.md b/docs/modules/k3s.md new file mode 100644 index 000000000..638c7a5f7 --- /dev/null +++ b/docs/modules/k3s.md @@ -0,0 +1,23 @@ +# K3s + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for K3s. + +## Adding this module to your project dependencies + +Please run the following command to add the K3s module to your python dependencies: + +```bash +pip install testcontainers[k3s] kubernetes pyyaml +``` + +## Usage example + + + +[Creating a K3s container](k3s_example.py) + + diff --git a/docs/modules/k3s_example.py b/docs/modules/k3s_example.py new file mode 100644 index 000000000..bf566d2af --- /dev/null +++ b/docs/modules/k3s_example.py @@ -0,0 +1,179 @@ +import json +import time + +import yaml +from kubernetes import client, config +from kubernetes.client.rest import ApiException + +from testcontainers.community.k3s import K3sContainer + + +def basic_example(): + with K3sContainer() as k3s: + # Get kubeconfig + kubeconfig = k3s.get_kubeconfig() + + # Load kubeconfig + config.load_kube_config_from_dict(yaml.safe_load(kubeconfig)) + print("Loaded kubeconfig") + + # Create API clients + v1 = client.CoreV1Api() + apps_v1 = client.AppsV1Api() + + # Create namespace + namespace = "test-namespace" + try: + v1.create_namespace(client.V1Namespace(metadata=client.V1ObjectMeta(name=namespace))) + print(f"Created namespace: {namespace}") + except ApiException as e: + if e.status == 409: # Already exists + print(f"Namespace {namespace} already exists") + else: + raise + + # Create ConfigMap + configmap = client.V1ConfigMap( + metadata=client.V1ObjectMeta(name="test-config", namespace=namespace), data={"config.yaml": "key: value"} + ) + v1.create_namespaced_config_map(namespace=namespace, body=configmap) + print("Created ConfigMap") + + # Create Secret + secret = client.V1Secret( + metadata=client.V1ObjectMeta(name="test-secret", namespace=namespace), + type="Opaque", + data={"username": "dGVzdA==", "password": "cGFzc3dvcmQ="}, # base64 encoded + ) + v1.create_namespaced_secret(namespace=namespace, body=secret) + print("Created Secret") + + # Create Deployment + deployment = client.V1Deployment( + metadata=client.V1ObjectMeta(name="test-deployment", namespace=namespace), + spec=client.V1DeploymentSpec( + replicas=2, + selector=client.V1LabelSelector(match_labels={"app": "test-app"}), + template=client.V1PodTemplateSpec( + metadata=client.V1ObjectMeta(labels={"app": "test-app"}), + spec=client.V1PodSpec( + containers=[ + client.V1Container( + name="nginx", image="nginx:latest", ports=[client.V1ContainerPort(container_port=80)] + ) + ] + ), + ), + ), + ) + apps_v1.create_namespaced_deployment(namespace=namespace, body=deployment) + print("Created Deployment") + + # Create Service + service = client.V1Service( + metadata=client.V1ObjectMeta(name="test-service", namespace=namespace), + spec=client.V1ServiceSpec( + selector={"app": "test-app"}, ports=[client.V1ServicePort(port=80, target_port=80)], type="ClusterIP" + ), + ) + v1.create_namespaced_service(namespace=namespace, body=service) + print("Created Service") + + # Wait for pods to be ready + print("\nWaiting for pods to be ready...") + time.sleep(10) # Give some time for pods to start + + # List pods + pods = v1.list_namespaced_pod(namespace=namespace) + print("\nPods:") + for pod in pods.items: + print(json.dumps({"name": pod.metadata.name, "phase": pod.status.phase, "ip": pod.status.pod_ip}, indent=2)) + + # Get deployment status + deployment_status = apps_v1.read_namespaced_deployment_status(name="test-deployment", namespace=namespace) + print("\nDeployment status:") + print( + json.dumps( + { + "name": deployment_status.metadata.name, + "replicas": deployment_status.spec.replicas, + "available_replicas": deployment_status.status.available_replicas, + "ready_replicas": deployment_status.status.ready_replicas, + }, + indent=2, + ) + ) + + # Get service details + service_details = v1.read_namespaced_service(name="test-service", namespace=namespace) + print("\nService details:") + print( + json.dumps( + { + "name": service_details.metadata.name, + "type": service_details.spec.type, + "cluster_ip": service_details.spec.cluster_ip, + "ports": [{"port": p.port, "target_port": p.target_port} for p in service_details.spec.ports], + }, + indent=2, + ) + ) + + # Create Ingress + ingress = client.V1Ingress( + metadata=client.V1ObjectMeta( + name="test-ingress", + namespace=namespace, + annotations={"nginx.ingress.kubernetes.io/rewrite-target": "/"}, + ), + spec=client.V1IngressSpec( + rules=[ + client.V1IngressRule( + host="test.local", + http=client.V1HTTPIngressRuleValue( + paths=[ + client.V1HTTPIngressPath( + path="/", + path_type="Prefix", + backend=client.V1IngressBackend( + service=client.V1IngressServiceBackend( + name="test-service", port=client.V1ServiceBackendPort(number=80) + ) + ), + ) + ] + ), + ) + ] + ), + ) + networking_v1 = client.NetworkingV1Api() + networking_v1.create_namespaced_ingress(namespace=namespace, body=ingress) + print("\nCreated Ingress") + + # Get ingress details + ingress_details = networking_v1.read_namespaced_ingress(name="test-ingress", namespace=namespace) + print("\nIngress details:") + print( + json.dumps( + { + "name": ingress_details.metadata.name, + "host": ingress_details.spec.rules[0].host, + "path": ingress_details.spec.rules[0].http.paths[0].path, + }, + indent=2, + ) + ) + + # Clean up + print("\nCleaning up resources...") + networking_v1.delete_namespaced_ingress(name="test-ingress", namespace=namespace) + v1.delete_namespaced_service(name="test-service", namespace=namespace) + apps_v1.delete_namespaced_deployment(name="test-deployment", namespace=namespace) + v1.delete_namespaced_secret(name="test-secret", namespace=namespace) + v1.delete_namespaced_config_map(name="test-config", namespace=namespace) + v1.delete_namespace(name=namespace) + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/kafka.md b/docs/modules/kafka.md new file mode 100644 index 000000000..202678447 --- /dev/null +++ b/docs/modules/kafka.md @@ -0,0 +1,23 @@ +# Kafka + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Kafka. + +## Adding this module to your project dependencies + +Please run the following command to add the Kafka module to your python dependencies: + +```bash +pip install testcontainers[kafka] +``` + +## Usage example + + + +[Creating a Kafka container](kafka_example.py) + + diff --git a/docs/modules/kafka_example.py b/docs/modules/kafka_example.py new file mode 100644 index 000000000..1c0df4128 --- /dev/null +++ b/docs/modules/kafka_example.py @@ -0,0 +1,80 @@ +import json +import time +from datetime import datetime +from threading import Thread + +from kafka import KafkaConsumer, KafkaProducer + +from testcontainers.community.kafka import KafkaContainer + + +def basic_example(): + with KafkaContainer() as kafka: + # Get connection parameters + bootstrap_servers = kafka.get_bootstrap_server() + + # Create Kafka producer + producer = KafkaProducer( + bootstrap_servers=bootstrap_servers, value_serializer=lambda v: json.dumps(v).encode("utf-8") + ) + print("Created Kafka producer") + + # Create Kafka consumer + consumer = KafkaConsumer( + bootstrap_servers=bootstrap_servers, + value_deserializer=lambda v: json.loads(v.decode("utf-8")), + auto_offset_reset="earliest", + group_id="test_group", + ) + print("Created Kafka consumer") + + # Define topics + topics = ["test_topic1", "test_topic2"] + + # Subscribe to topics + consumer.subscribe(topics) + print(f"Subscribed to topics: {topics}") + + # Start consuming in a separate thread + def consume_messages(): + for message in consumer: + print(f"\nReceived message from {message.topic}:") + print(json.dumps(message.value, indent=2)) + + consumer_thread = Thread(target=consume_messages) + consumer_thread.daemon = True + consumer_thread.start() + + # Produce test messages + test_messages = [ + { + "topic": "test_topic1", + "message": {"id": 1, "content": "Message for topic 1", "timestamp": datetime.utcnow().isoformat()}, + }, + { + "topic": "test_topic2", + "message": {"id": 2, "content": "Message for topic 2", "timestamp": datetime.utcnow().isoformat()}, + }, + ] + + for msg in test_messages: + producer.send(msg["topic"], msg["message"]) + print(f"Sent message to {msg['topic']}") + + # Wait for messages to be processed + time.sleep(2) + + # Get topic information + print("\nTopic information:") + for topic in topics: + partitions = consumer.partitions_for_topic(topic) + print(f"{topic}:") + print(f" Partitions: {partitions}") + + # Clean up + producer.close() + consumer.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/keycloak.md b/docs/modules/keycloak.md new file mode 100644 index 000000000..5be9ea0d7 --- /dev/null +++ b/docs/modules/keycloak.md @@ -0,0 +1,23 @@ +# Keycloak + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Keycloak. + +## Adding this module to your project dependencies + +Please run the following command to add the Keycloak module to your python dependencies: + +```bash +pip install testcontainers[keycloak] python-keycloak requests +``` + +## Usage example + + + +[Creating a Keycloak container](keycloak_example.py) + + diff --git a/docs/modules/keycloak_example.py b/docs/modules/keycloak_example.py new file mode 100644 index 000000000..712dac2e5 --- /dev/null +++ b/docs/modules/keycloak_example.py @@ -0,0 +1,171 @@ +import json + +from keycloak import KeycloakAdmin, KeycloakOpenID + +from testcontainers.community.keycloak import KeycloakContainer + + +def basic_example(): + with KeycloakContainer() as keycloak: + # Get connection parameters + host = keycloak.get_container_host_ip() + port = keycloak.get_exposed_port(keycloak.port) + admin_username = keycloak.admin_username + admin_password = keycloak.admin_password + + # Create admin client + admin = KeycloakAdmin( + server_url=f"http://{host}:{port}/", + username=admin_username, + password=admin_password, + realm_name="master", + verify=False, + ) + print("Connected to Keycloak as admin") + + # Create realm + realm_name = "test-realm" + admin.create_realm(payload={"realm": realm_name, "enabled": True}) + print(f"\nCreated realm: {realm_name}") + + # Switch to new realm + admin.realm_name = realm_name + + # Create client + client_id = "test-client" + admin.create_client( + payload={ + "clientId": client_id, + "publicClient": True, + "redirectUris": ["http://localhost:8080/*"], + "webOrigins": ["http://localhost:8080"], + } + ) + print(f"Created client: {client_id}") + + # Get client details + client = admin.get_client(client_id=client_id) + print("\nClient details:") + print( + json.dumps( + { + "client_id": client["clientId"], + "public_client": client["publicClient"], + "redirect_uris": client["redirectUris"], + }, + indent=2, + ) + ) + + # Create user + username = "testuser" + admin.create_user( + payload={ + "username": username, + "email": "test@example.com", + "enabled": True, + "credentials": [{"type": "password", "value": "password", "temporary": False}], + } + ) + print(f"\nCreated user: {username}") + + # Get user details + user = admin.get_user(user_id=username) + print("\nUser details:") + print(json.dumps({"username": user["username"], "email": user["email"], "enabled": user["enabled"]}, indent=2)) + + # Create role + role_name = "test-role" + admin.create_realm_role(payload={"name": role_name, "description": "Test role"}) + print(f"\nCreated role: {role_name}") + + # Assign role to user + role = admin.get_realm_role(role_name=role_name) + admin.assign_realm_roles(user_id=user["id"], roles=[role]) + print(f"Assigned role {role_name} to user {username}") + + # Create group + group_name = "test-group" + admin.create_group(payload={"name": group_name}) + print(f"\nCreated group: {group_name}") + + # Add user to group + group = admin.get_group_by_path(path=f"/{group_name}") + admin.group_user_add(user_id=user["id"], group_id=group["id"]) + print(f"Added user {username} to group {group_name}") + + # Create OpenID client + openid = KeycloakOpenID( + server_url=f"http://{host}:{port}/", client_id=client_id, realm_name=realm_name, verify=False + ) + + # Get token + token = openid.token(username=username, password="password") + print("\nToken details:") + print( + json.dumps( + { + "access_token": token["access_token"][:20] + "...", + "refresh_token": token["refresh_token"][:20] + "...", + "expires_in": token["expires_in"], + }, + indent=2, + ) + ) + + # Get user info + userinfo = openid.userinfo(token["access_token"]) + print("\nUser info:") + print(json.dumps(userinfo, indent=2)) + + # Get realm roles + roles = admin.get_realm_roles() + print("\nRealm roles:") + for role in roles: + print(f"- {role['name']}") + + # Get user roles + user_roles = admin.get_realm_roles_of_user(user_id=user["id"]) + print("\nUser roles:") + for role in user_roles: + print(f"- {role['name']}") + + # Get groups + groups = admin.get_groups() + print("\nGroups:") + for group in groups: + print(f"- {group['name']}") + + # Get group members + group_members = admin.get_group_members(group_id=group["id"]) + print("\nGroup members:") + for member in group_members: + print(f"- {member['username']}") + + # Update user + admin.update_user(user_id=user["id"], payload={"firstName": "Test", "lastName": "User"}) + print("\nUpdated user") + + # Update client + admin.update_client(client_id=client["id"], payload={"description": "Updated test client"}) + print("Updated client") + + # Clean up + admin.delete_user(user_id=user["id"]) + print(f"\nDeleted user: {username}") + + admin.delete_client(client_id=client["id"]) + print(f"Deleted client: {client_id}") + + admin.delete_realm_role(role_name=role_name) + print(f"Deleted role: {role_name}") + + admin.delete_group(group_id=group["id"]) + print(f"Deleted group: {group_name}") + + admin.delete_realm(realm_name=realm_name) + print(f"Deleted realm: {realm_name}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/localstack.md b/docs/modules/localstack.md new file mode 100644 index 000000000..72ded42fc --- /dev/null +++ b/docs/modules/localstack.md @@ -0,0 +1,23 @@ +# LocalStack + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for LocalStack. + +## Adding this module to your project dependencies + +Please run the following command to add the LocalStack module to your python dependencies: + +```bash +pip install testcontainers[localstack] boto3 +``` + +## Usage example + + + +[Creating a LocalStack container](localstack_example.py) + + diff --git a/docs/modules/localstack_example.py b/docs/modules/localstack_example.py new file mode 100644 index 000000000..19767caad --- /dev/null +++ b/docs/modules/localstack_example.py @@ -0,0 +1,72 @@ +import json + +import boto3 + +from testcontainers.community.localstack import LocalStackContainer + + +def basic_example(): + with LocalStackContainer() as localstack: + # Get endpoint URL + endpoint_url = localstack.get_endpoint_url() + + # Create S3 client + s3 = boto3.client( + "s3", + endpoint_url=endpoint_url, + aws_access_key_id="test", + aws_secret_access_key="test", + region_name="us-east-1", + ) + + # Create SQS client + sqs = boto3.client( + "sqs", + endpoint_url=endpoint_url, + aws_access_key_id="test", + aws_secret_access_key="test", + region_name="us-east-1", + ) + + # Create S3 bucket + bucket_name = "test-bucket" + s3.create_bucket(Bucket=bucket_name) + print(f"Created S3 bucket: {bucket_name}") + + # Upload file to S3 + test_data = {"message": "Hello from LocalStack!", "timestamp": "2024-01-01"} + s3.put_object(Bucket=bucket_name, Key="test.json", Body=json.dumps(test_data)) + print("Uploaded test.json to S3") + + # Create SQS queue + queue_name = "test-queue" + queue = sqs.create_queue(QueueName=queue_name) + queue_url = queue["QueueUrl"] + print(f"Created SQS queue: {queue_name}") + + # Send message to SQS + message = {"message": "Test message", "number": 42} + sqs.send_message(QueueUrl=queue_url, MessageBody=json.dumps(message)) + print("Sent message to SQS") + + # Receive message from SQS + response = sqs.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=1) + + if "Messages" in response: + received_message = json.loads(response["Messages"][0]["Body"]) + print("\nReceived message from SQS:") + print(json.dumps(received_message, indent=2)) + + # Delete message + sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=response["Messages"][0]["ReceiptHandle"]) + print("Deleted message from queue") + + # List S3 objects + objects = s3.list_objects(Bucket=bucket_name) + print("\nS3 bucket contents:") + for obj in objects.get("Contents", []): + print(f"Key: {obj['Key']}, Size: {obj['Size']} bytes") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/mailpit.md b/docs/modules/mailpit.md new file mode 100644 index 000000000..78ceb2991 --- /dev/null +++ b/docs/modules/mailpit.md @@ -0,0 +1,23 @@ +# Mailpit + +Since testcontainers-python :material-tag: v4.7.1 + +## Introduction + +The Testcontainers module for Mailpit. + +## Adding this module to your project dependencies + +Please run the following command to add the Mailpit module to your python dependencies: + +```bash +pip install testcontainers[mailpit] cryptography +``` + +## Usage example + + + +[Creating a Mailpit container](mailpit_example.py) + + diff --git a/docs/modules/mailpit_example.py b/docs/modules/mailpit_example.py new file mode 100644 index 000000000..c44934c26 --- /dev/null +++ b/docs/modules/mailpit_example.py @@ -0,0 +1,62 @@ +import smtplib +import time +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +import requests + +from testcontainers.community.mailpit import MailpitContainer + + +def basic_example(): + with MailpitContainer() as mailpit: + # Get SMTP and API endpoints + smtp_host = mailpit.get_container_host_ip() + smtp_port = mailpit.get_exposed_smtp_port() + api_url = mailpit.get_base_api_url() + + # Create email message + msg = MIMEMultipart() + msg["From"] = "sender@example.com" + msg["To"] = "recipient@example.com" + msg["Subject"] = "Test Email" + + body = "This is a test email sent to Mailpit." + msg.attach(MIMEText(body, "plain")) + + # Send email using SMTP + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.send_message(msg) + print("Email sent successfully") + + # Wait for email to be processed + time.sleep(1) + + # Check received emails using API + response = requests.get(f"{api_url}/api/v1/messages") + messages = response.json() + + print("\nReceived emails:") + for message in messages["messages"]: + print(f"From: {message['From']['Address']}") + print(f"To: {message['To'][0]['Address']}") + print(f"Subject: {message['Subject']}") + print(f"Body: {message['Text']}") + print("---") + + # Get specific email details + if messages["messages"]: + first_message = messages["messages"][0] + message_id = first_message["ID"] + + response = requests.get(f"{api_url}/api/v1/messages/{message_id}") + message_details = response.json() + + print("\nDetailed message info:") + print(f"Size: {message_details['Size']} bytes") + print(f"Created: {message_details['Created']}") + print(f"Attachments: {len(message_details['Attachments'])}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/memcached.md b/docs/modules/memcached.md new file mode 100644 index 000000000..026ae9876 --- /dev/null +++ b/docs/modules/memcached.md @@ -0,0 +1,23 @@ +# Memcached + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for Memcached. + +## Adding this module to your project dependencies + +Please run the following command to add the Memcached module to your python dependencies: + +```bash +pip install testcontainers[memcached] pymemcache +``` + +## Usage example + + + +[Creating a Memcached container](memcached_example.py) + + diff --git a/docs/modules/memcached_example.py b/docs/modules/memcached_example.py new file mode 100644 index 000000000..e84daccc6 --- /dev/null +++ b/docs/modules/memcached_example.py @@ -0,0 +1,135 @@ +import json +import pickle + +import memcache + +from testcontainers.community.memcached import MemcachedContainer + + +def basic_example(): + with MemcachedContainer() as memcached: + # Get connection parameters + host = memcached.get_container_host_ip() + port = memcached.get_exposed_port(memcached.port) + + # Create Memcached client + client = memcache.Client([f"{host}:{port}"]) + print("Connected to Memcached") + + # Store simple values + client.set("string_key", "Hello from Memcached") + client.set("int_key", 42) + client.set("float_key", 3.14) + print("Stored simple values") + + # Store complex data + complex_data = {"name": "test", "values": [1, 2, 3], "nested": {"key": "value"}} + client.set("complex_key", json.dumps(complex_data)) + print("Stored complex data") + + # Store with expiration + client.set("expiring_key", "This will expire", time=5) + print("Stored value with expiration") + + # Store with pickle + class TestObject: + def __init__(self, name, value): + self.name = name + self.value = value + + test_obj = TestObject("test", 123) + client.set("object_key", pickle.dumps(test_obj)) + print("Stored pickled object") + + # Retrieve values + print("\nRetrieved values:") + print(f"string_key: {client.get('string_key')}") + print(f"int_key: {client.get('int_key')}") + print(f"float_key: {client.get('float_key')}") + + # Retrieve complex data + complex_value = json.loads(client.get("complex_key")) + print("\nComplex data:") + print(json.dumps(complex_value, indent=2)) + + # Retrieve pickled object + obj_data = pickle.loads(client.get("object_key")) + print("\nPickled object:") + print(f"name: {obj_data.name}") + print(f"value: {obj_data.value}") + + # Check expiration + print("\nChecking expiring key:") + print(f"expiring_key: {client.get('expiring_key')}") + print("Waiting for key to expire...") + import time + + time.sleep(6) + print(f"expiring_key after expiration: {client.get('expiring_key')}") + + # Store multiple values + multi_data = {"key1": "value1", "key2": "value2", "key3": "value3"} + client.set_multi(multi_data) + print("\nStored multiple values") + + # Retrieve multiple values + multi_keys = ["key1", "key2", "key3"] + multi_values = client.get_multi(multi_keys) + print("\nMultiple values:") + print(json.dumps(multi_values, indent=2)) + + # Increment and decrement + client.set("counter", 0) + client.incr("counter") + client.incr("counter", 2) + print("\nCounter after increment:") + print(f"counter: {client.get('counter')}") + + client.decr("counter") + print("Counter after decrement:") + print(f"counter: {client.get('counter')}") + + # Store with flags + client.set("flagged_key", "value", flags=1) + print("\nStored value with flags") + + # Get stats + stats = client.get_stats() + print("\nMemcached stats:") + for server, server_stats in stats: + print(f"\nServer: {server}") + print(json.dumps(dict(server_stats), indent=2)) + + # Delete values + client.delete("string_key") + client.delete_multi(["key1", "key2", "key3"]) + print("\nDeleted values") + + # Check deleted values + print("\nChecking deleted values:") + print(f"string_key: {client.get('string_key')}") + print(f"key1: {client.get('key1')}") + + # Store with CAS + client.set("cas_key", "initial") + cas_value = client.gets("cas_key") + print("\nCAS value:") + print(f"value: {cas_value}") + + # Update with CAS + success = client.cas("cas_key", "updated", cas_value[1]) + print(f"CAS update success: {success}") + print(f"Updated value: {client.get('cas_key')}") + + # Try to update with invalid CAS + success = client.cas("cas_key", "failed", 0) + print(f"Invalid CAS update success: {success}") + print(f"Value after failed update: {client.get('cas_key')}") + + # Clean up + client.flush_all() + print("\nFlushed all values") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/milvus.md b/docs/modules/milvus.md new file mode 100644 index 000000000..9cfbddbc3 --- /dev/null +++ b/docs/modules/milvus.md @@ -0,0 +1,23 @@ +# Milvus + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for Milvus. + +## Adding this module to your project dependencies + +Please run the following command to add the Milvus module to your python dependencies: + +```bash +pip install testcontainers[milvus] requests +``` + +## Usage example + + + +[Creating a Milvus container](milvus_example.py) + + diff --git a/docs/modules/milvus_example.py b/docs/modules/milvus_example.py new file mode 100644 index 000000000..3d877bd63 --- /dev/null +++ b/docs/modules/milvus_example.py @@ -0,0 +1,138 @@ +import json +from datetime import datetime + +import numpy as np +from pymilvus import Collection, CollectionSchema, DataType, FieldSchema, connections, utility + +from testcontainers.community.milvus import MilvusContainer + + +def basic_example(): + with MilvusContainer() as milvus: + # Get connection parameters + host = milvus.get_container_host_ip() + port = milvus.get_exposed_port(milvus.port) + + # Connect to Milvus + connections.connect(alias="default", host=host, port=port) + print("Connected to Milvus") + + # Create collection + collection_name = "test_collection" + dim = 128 + + fields = [ + FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), + FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=dim), + FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=500), + FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=100), + FieldSchema(name="tags", dtype=DataType.JSON), + FieldSchema(name="timestamp", dtype=DataType.VARCHAR, max_length=50), + ] + + schema = CollectionSchema(fields=fields, description="Test collection") + collection = Collection(name=collection_name, schema=schema) + print(f"Created collection: {collection_name}") + + # Create index + index_params = {"metric_type": "COSINE", "index_type": "IVF_FLAT", "params": {"nlist": 1024}} + collection.create_index(field_name="vector", index_params=index_params) + print("Created index on vector field") + + # Generate test data + num_entities = 5 + vectors = np.random.rand(num_entities, dim).tolist() + + texts = [ + "AI and machine learning are transforming industries", + "New study reveals benefits of meditation", + "Global warming reaches critical levels", + "Stock market shows strong growth", + "New restaurant opens in downtown", + ] + + categories = ["Technology", "Health", "Environment", "Finance", "Food"] + + tags = [ + ["AI", "ML", "innovation"], + ["wellness", "mental health"], + ["climate", "sustainability"], + ["investing", "markets"], + ["dining", "local"], + ] + + timestamps = [datetime.utcnow().isoformat() for _ in range(num_entities)] + + # Insert data + entities = [vectors, texts, categories, tags, timestamps] + + collection.insert(entities) + print("Inserted test data") + + # Flush collection + collection.flush() + print("Flushed collection") + + # Load collection + collection.load() + print("Loaded collection") + + # Search vectors + search_params = {"metric_type": "COSINE", "params": {"nprobe": 10}} + + results = collection.search( + data=[vectors[0]], + anns_field="vector", + param=search_params, + limit=3, + output_fields=["text", "category", "tags"], + ) + + print("\nSearch results:") + for hits in results: + for hit in hits: + print(json.dumps({"id": hit.id, "distance": hit.distance, "entity": hit.entity}, indent=2)) + + # Query with filter + filter_expr = 'category == "Technology"' + query_results = collection.query(expr=filter_expr, output_fields=["text", "category", "tags"]) + + print("\nQuery results with filter:") + print(json.dumps(query_results, indent=2)) + + # Get collection stats + stats = collection.get_statistics() + print("\nCollection statistics:") + print(json.dumps(stats, indent=2)) + + # Create partition + partition_name = "test_partition" + collection.create_partition(partition_name) + print(f"\nCreated partition: {partition_name}") + + # List partitions + partitions = collection.partitions + print("\nPartitions:") + for partition in partitions: + print( + json.dumps( + {"name": partition.name, "is_empty": partition.is_empty, "num_entities": partition.num_entities}, + indent=2, + ) + ) + + # Delete partition + collection.drop_partition(partition_name) + print(f"Deleted partition: {partition_name}") + + # Clean up + utility.drop_collection(collection_name) + print("\nDropped collection") + + # Disconnect + connections.disconnect("default") + print("Disconnected from Milvus") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/minio.md b/docs/modules/minio.md new file mode 100644 index 000000000..414763a62 --- /dev/null +++ b/docs/modules/minio.md @@ -0,0 +1,23 @@ +# MinIO + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for MinIO. + +## Adding this module to your project dependencies + +Please run the following command to add the MinIO module to your python dependencies: + +```bash +pip install testcontainers[minio] minio requests +``` + +## Usage example + + + +[Creating a MinIO container](minio_example.py) + + diff --git a/docs/modules/minio_example.py b/docs/modules/minio_example.py new file mode 100644 index 000000000..106b037ba --- /dev/null +++ b/docs/modules/minio_example.py @@ -0,0 +1,120 @@ +import io +import json +from datetime import timedelta + +from minio import Minio + +from testcontainers.community.minio import MinioContainer + + +def basic_example(): + with MinioContainer() as minio: + # Get connection parameters + host = minio.get_container_host_ip() + port = minio.get_exposed_port(minio.port) + access_key = minio.access_key + secret_key = minio.secret_key + + # Create MinIO client + client = Minio(f"{host}:{port}", access_key=access_key, secret_key=secret_key, secure=False) + print("Connected to MinIO") + + # Create bucket + bucket_name = "test-bucket" + client.make_bucket(bucket_name) + print(f"Created bucket: {bucket_name}") + + # List buckets + buckets = client.list_buckets() + print("\nBuckets:") + for bucket in buckets: + print(f"- {bucket.name} (created: {bucket.creation_date})") + + # Upload test files + test_files = {"test1.txt": "Hello from test1", "test2.txt": "Hello from test2", "test3.txt": "Hello from test3"} + + for filename, content in test_files.items(): + data = io.BytesIO(content.encode()) + client.put_object(bucket_name, filename, data, len(content.encode()), content_type="text/plain") + print(f"Uploaded {filename}") + + # List objects + objects = client.list_objects(bucket_name) + print("\nObjects in bucket:") + for obj in objects: + print(f"- {obj.object_name} (size: {obj.size} bytes)") + + # Get object + print("\nObject contents:") + for filename in test_files: + response = client.get_object(bucket_name, filename) + content = response.read().decode() + print(f"{filename}: {content}") + + # Create directory structure + client.put_object( + bucket_name, "folder1/test4.txt", io.BytesIO(b"Hello from test4"), 15, content_type="text/plain" + ) + print("\nCreated directory structure") + + # List objects with prefix + objects = client.list_objects(bucket_name, prefix="folder1/") + print("\nObjects in folder1:") + for obj in objects: + print(f"- {obj.object_name}") + + # Copy object + client.copy_object(bucket_name, "test1.txt", f"{bucket_name}/folder1/test1_copy.txt") + print("\nCopied object") + + # Get object metadata + stat = client.stat_object(bucket_name, "test1.txt") + print("\nObject metadata:") + print( + json.dumps( + { + "name": stat.object_name, + "size": stat.size, + "content_type": stat.content_type, + "last_modified": stat.last_modified.isoformat(), + }, + indent=2, + ) + ) + + # Generate presigned URL + url = client.presigned_get_object(bucket_name, "test1.txt", expires=timedelta(hours=1)) + print(f"\nPresigned URL: {url}") + + # Set bucket policy + policy = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"AWS": "*"}, + "Action": ["s3:GetObject"], + "Resource": [f"arn:aws:s3:::{bucket_name}/*"], + } + ], + } + client.set_bucket_policy(bucket_name, json.dumps(policy)) + print("\nSet bucket policy") + + # Get bucket policy + policy = client.get_bucket_policy(bucket_name) + print("\nBucket policy:") + print(json.dumps(json.loads(policy), indent=2)) + + # Remove objects + for filename in test_files: + client.remove_object(bucket_name, filename) + print(f"Removed {filename}") + + # Remove bucket + client.remove_bucket(bucket_name) + print(f"\nRemoved bucket: {bucket_name}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/mongodb.md b/docs/modules/mongodb.md new file mode 100644 index 000000000..1b19ba2ec --- /dev/null +++ b/docs/modules/mongodb.md @@ -0,0 +1,23 @@ +# MongoDB + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for MongoDB. + +## Adding this module to your project dependencies + +Please run the following command to add the MongoDB module to your python dependencies: + +```bash +pip install testcontainers[mongodb] pymongo +``` + +## Usage example + + + +[Creating a MongoDB container](mongodb_example.py) + + diff --git a/docs/modules/mongodb_example.py b/docs/modules/mongodb_example.py new file mode 100644 index 000000000..ba6216807 --- /dev/null +++ b/docs/modules/mongodb_example.py @@ -0,0 +1,125 @@ +import json +from datetime import datetime + +from pymongo import MongoClient + +from testcontainers.community.mongodb import MongoDbContainer, MongoDbReplicaSetContainer + + +def basic_example(): + with MongoDbContainer() as mongodb: + # Get connection URL + connection_url = mongodb.get_connection_url() + + # Create MongoDB client + client = MongoClient(connection_url) + print("Connected to MongoDB") + + # Get database and collection + db = client.test_db + collection = db.test_collection + + # Insert test documents + test_docs = [ + {"name": "test1", "value": 100, "category": "A", "created_at": datetime.utcnow()}, + {"name": "test2", "value": 200, "category": "B", "created_at": datetime.utcnow()}, + {"name": "test3", "value": 300, "category": "A", "created_at": datetime.utcnow()}, + ] + + result = collection.insert_many(test_docs) + print(f"Inserted {len(result.inserted_ids)} documents") + + # Query documents + print("\nQuery results:") + for doc in collection.find({"category": "A"}): + print(json.dumps(doc, default=str, indent=2)) + + # Execute aggregation pipeline + pipeline = [ + { + "$group": { + "_id": "$category", + "avg_value": {"$avg": "$value"}, + "count": {"$sum": 1}, + "min_value": {"$min": "$value"}, + "max_value": {"$max": "$value"}, + } + }, + {"$sort": {"avg_value": -1}}, + ] + + print("\nAggregation results:") + for result in collection.aggregate(pipeline): + print(json.dumps(result, default=str, indent=2)) + + # Create indexes + collection.create_index("name") + collection.create_index([("category", 1), ("value", -1)]) + print("\nCreated indexes") + + # List indexes + print("\nIndexes:") + for index in collection.list_indexes(): + print(json.dumps(index, default=str, indent=2)) + + # Update documents + result = collection.update_many({"category": "A"}, {"$set": {"updated": True}}) + print(f"\nUpdated {result.modified_count} documents") + + # Find updated documents + print("\nUpdated documents:") + for doc in collection.find({"updated": True}): + print(json.dumps(doc, default=str, indent=2)) + + # Delete documents + result = collection.delete_many({"category": "B"}) + print(f"\nDeleted {result.deleted_count} documents") + + # Get collection stats + stats = db.command("collstats", "test_collection") + print("\nCollection stats:") + print(json.dumps(stats, default=str, indent=2)) + + +def replica_set_example(): + with MongoDbReplicaSetContainer() as mongodb: + client = mongodb.get_connection_client() + db = client.test_db + + print("\nConnected to MongoDB replica set") + print(json.dumps(client.admin.command("hello"), default=str, indent=2)) + + # Run a multi-document transaction + accounts = db.accounts + with client.start_session() as session, session.start_transaction(): + accounts.insert_one({"name": "checking", "balance": 100}, session=session) + accounts.update_one( + {"name": "checking"}, + {"$inc": {"balance": -25}}, + session=session, + ) + + print("\nTransaction result:") + print(json.dumps(accounts.find_one({"name": "checking"}), default=str, indent=2)) + + # Observe an insert through a change stream + events = db.events + with events.watch([{"$match": {"operationType": "insert"}}]) as changes: + events.insert_one({"kind": "created", "value": 42}) + change = next(changes) + + print("\nChange stream event:") + print(json.dumps(change, default=str, indent=2)) + + +def unauthenticated_replica_set_example(): + with MongoDbReplicaSetContainer(auth_enabled=False) as mongodb: + client = mongodb.get_connection_client() + print("\nConnected to MongoDB replica set without authentication") + print(json.dumps(client.admin.command("hello"), default=str, indent=2)) + + +if __name__ == "__main__": + basic_example() + replica_set_example() + unauthenticated_replica_set_example() diff --git a/docs/modules/mqtt.md b/docs/modules/mqtt.md new file mode 100644 index 000000000..2ed7c9d92 --- /dev/null +++ b/docs/modules/mqtt.md @@ -0,0 +1,23 @@ +# MQTT + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for MQTT. + +## Adding this module to your project dependencies + +Please run the following command to add the MQTT module to your python dependencies: + +```bash +pip install testcontainers[mqtt] paho-mqtt +``` + +## Usage example + + + +[Creating an MQTT container](mqtt_example.py) + + diff --git a/docs/modules/mqtt_example.py b/docs/modules/mqtt_example.py new file mode 100644 index 000000000..8ab3e564a --- /dev/null +++ b/docs/modules/mqtt_example.py @@ -0,0 +1,51 @@ +import time + +import paho.mqtt.client as mqtt + +from testcontainers.community.mqtt import MqttContainer + + +def basic_example(): + with MqttContainer() as mqtt_container: + # Get connection parameters + host = mqtt_container.get_container_host_ip() + port = mqtt_container.get_exposed_port(mqtt_container.port) + + # Create MQTT client + client = mqtt.Client() + + # Define callback functions + def on_connect(client, userdata, flags, rc): + print(f"Connected with result code {rc}") + # Subscribe to topics + client.subscribe("test/topic") + + def on_message(client, userdata, msg): + print(f"Received message on topic {msg.topic}: {msg.payload.decode()}") + + # Set callbacks + client.on_connect = on_connect + client.on_message = on_message + + # Connect to broker + client.connect(host, port) + client.loop_start() + + # Publish test messages + test_messages = ["Hello MQTT!", "This is a test message", "MQTT is working!"] + + for msg in test_messages: + client.publish("test/topic", msg) + print(f"Published message: {msg}") + time.sleep(1) # Wait a bit between messages + + # Wait for messages to be processed + time.sleep(2) + + # Clean up + client.loop_stop() + client.disconnect() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/mssql.md b/docs/modules/mssql.md new file mode 100644 index 000000000..7b806774c --- /dev/null +++ b/docs/modules/mssql.md @@ -0,0 +1,23 @@ +# MSSQL + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Microsoft SQL Server. + +## Adding this module to your project dependencies + +Please run the following command to add the MSSQL module to your python dependencies: + +```bash +pip install testcontainers[mssql] pymssql +``` + +## Usage example + + + +[Creating an MSSQL container](mssql_example.py) + + diff --git a/docs/modules/mssql_example.py b/docs/modules/mssql_example.py new file mode 100644 index 000000000..e518c5a86 --- /dev/null +++ b/docs/modules/mssql_example.py @@ -0,0 +1,161 @@ +import pymssql + +from testcontainers.community.mssql import MsSqlContainer + + +def basic_example(): + with MsSqlContainer() as mssql: + # Get connection parameters + host = mssql.get_container_host_ip() + port = mssql.get_exposed_port(mssql.port) + username = mssql.username + password = mssql.password + database = mssql.database + + # Connect to MSSQL + connection = pymssql.connect(server=host, port=port, user=username, password=password, database=database) + print("Connected to MSSQL") + + # Create cursor + cursor = connection.cursor() + + # Create test table + cursor.execute(""" + CREATE TABLE test_table ( + id INT IDENTITY(1,1) PRIMARY KEY, + name NVARCHAR(50), + value INT, + category NVARCHAR(10), + created_at DATETIME2 DEFAULT GETDATE() + ) + """) + print("Created test table") + + # Insert test data + test_data = [("test1", 100, "A"), ("test2", 200, "B"), ("test3", 300, "A")] + + cursor.executemany( + """ + INSERT INTO test_table (name, value, category) + VALUES (%s, %s, %s) + """, + test_data, + ) + print("Inserted test data") + + # Commit changes + connection.commit() + + # Query data + print("\nQuery results:") + cursor.execute("SELECT * FROM test_table WHERE category = 'A'") + for row in cursor: + print({"id": row[0], "name": row[1], "value": row[2], "category": row[3], "created_at": row[4].isoformat()}) + + # Create view + cursor.execute(""" + CREATE OR ALTER VIEW test_view AS + SELECT category, COUNT(*) as count, AVG(value) as avg_value + FROM test_table + GROUP BY category + """) + print("\nCreated view") + + # Query view + print("\nView results:") + cursor.execute("SELECT * FROM test_view") + for row in cursor: + print({"category": row[0], "count": row[1], "avg_value": float(row[2])}) + + # Create index + cursor.execute("CREATE INDEX test_idx ON test_table (value)") + print("\nCreated index") + + # Query using index + print("\nQuery using index:") + cursor.execute("SELECT * FROM test_table WHERE value > 150") + for row in cursor: + print({"id": row[0], "name": row[1], "value": row[2], "category": row[3], "created_at": row[4].isoformat()}) + + # Get table metadata + cursor.execute(""" + SELECT + c.name as column_name, + t.name as data_type, + c.max_length, + c.is_nullable + FROM sys.columns c + JOIN sys.types t ON c.user_type_id = t.user_type_id + WHERE OBJECT_ID = OBJECT_ID('test_table') + ORDER BY c.column_id + """) + print("\nTable metadata:") + for row in cursor: + print({"column": row[0], "type": row[1], "length": row[2], "nullable": row[3]}) + + # Create stored procedure + cursor.execute(""" + CREATE OR ALTER PROCEDURE test_proc + @category NVARCHAR(10), + @count INT OUTPUT + AS + BEGIN + SELECT @count = COUNT(*) + FROM test_table + WHERE category = @category + END + """) + print("\nCreated stored procedure") + + # Execute stored procedure + cursor.execute(""" + DECLARE @count INT + EXEC test_proc @category = 'A', @count = @count OUTPUT + SELECT @count as count + """) + count = cursor.fetchone()[0] + print(f"Count for category A: {count}") + + # Create function + cursor.execute(""" + CREATE OR ALTER FUNCTION test_func(@category NVARCHAR(10)) + RETURNS TABLE + AS + RETURN + ( + SELECT name, value + FROM test_table + WHERE category = @category + ) + """) + print("\nCreated function") + + # Use function + print("\nFunction results:") + cursor.execute("SELECT * FROM test_func('A')") + for row in cursor: + print({"name": row[0], "value": row[1]}) + + # Create trigger + cursor.execute(""" + CREATE OR ALTER TRIGGER test_trigger + ON test_table + AFTER INSERT + AS + BEGIN + PRINT 'New row inserted' + END + """) + print("\nCreated trigger") + + # Test trigger + cursor.execute("INSERT INTO test_table (name, value, category) VALUES ('test4', 400, 'B')") + connection.commit() + + # Clean up + cursor.close() + connection.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/mysql.md b/docs/modules/mysql.md new file mode 100644 index 000000000..993f8c850 --- /dev/null +++ b/docs/modules/mysql.md @@ -0,0 +1,23 @@ +# MySQL + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for MySQL. + +## Adding this module to your project dependencies + +Please run the following command to add the MySQL module to your python dependencies: + +```bash +pip install testcontainers[mysql] sqlalchemy pymysql +``` + +## Usage example + + + +[Creating a MySQL container](mysql_example.py) + + diff --git a/docs/modules/mysql_example.py b/docs/modules/mysql_example.py new file mode 100644 index 000000000..2ce977701 --- /dev/null +++ b/docs/modules/mysql_example.py @@ -0,0 +1,16 @@ +import sqlalchemy + +from testcontainers.community.mysql import MySqlContainer + + +def basic_example(): + config = MySqlContainer("mysql:8.3.0", dialect="pymysql") + + with config as mysql: + connection_url = mysql.get_connection_url() + + engine = sqlalchemy.create_engine(connection_url) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("select version()")) + for row in result: + print(f"MySQL version: {row[0]}") diff --git a/docs/modules/nats.md b/docs/modules/nats.md new file mode 100644 index 000000000..927639213 --- /dev/null +++ b/docs/modules/nats.md @@ -0,0 +1,23 @@ +# NATS + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for NATS. + +## Adding this module to your project dependencies + +Please run the following command to add the NATS module to your python dependencies: + +```bash +pip install testcontainers[nats] nats-py +``` + +## Usage example + + + +[Creating a NATS container](nats_example.py) + + diff --git a/docs/modules/nats_example.py b/docs/modules/nats_example.py new file mode 100644 index 000000000..660f74784 --- /dev/null +++ b/docs/modules/nats_example.py @@ -0,0 +1,152 @@ +import asyncio +import json + +from nats.aio.client import Client as NATS +from nats.aio.msg import Msg + +from testcontainers.community.nats import NatsContainer + + +async def message_handler(msg: Msg): + subject = msg.subject + data = msg.data.decode() + print(f"Received message on {subject}: {data}") + + +async def basic_example(): + with NatsContainer(jetstream=True) as nats_container: + # Get connection parameters + host = nats_container.get_container_host_ip() + port = nats_container.get_exposed_port(nats_container.client_port) + + # Create NATS client + nc = NATS() + await nc.connect(f"nats://{host}:{port}") + print("Connected to NATS") + + # Create JetStream context + js = nc.jetstream() + + # Create stream + stream = await js.add_stream(name="test-stream", subjects=["test.>"]) + print(f"\nCreated stream: {stream.config.name}") + + # Create consumer + consumer = await js.add_consumer(stream="test-stream", durable_name="test-consumer") + print(f"Created consumer: {consumer.name}") + + # Subscribe to subjects + subjects = ["test.1", "test.2", "test.3"] + for subject in subjects: + await nc.subscribe(subject, cb=message_handler) + print(f"Subscribed to {subject}") + + # Publish messages + messages = {"test.1": "Hello from test.1", "test.2": "Hello from test.2", "test.3": "Hello from test.3"} + + for subject, message in messages.items(): + await nc.publish(subject, message.encode()) + print(f"Published to {subject}") + + # Publish with headers + headers = {"header1": "value1", "header2": "value2"} + await nc.publish("test.headers", b"Message with headers", headers=headers) + print("\nPublished message with headers") + + # Publish with reply + reply_subject = "test.reply" + await nc.subscribe(reply_subject, cb=message_handler) + print(f"Subscribed to {reply_subject}") + + response = await nc.request("test.request", b"Request message", timeout=1) + print(f"Received reply: {response.data.decode()}") + + # Publish to JetStream + for subject, message in messages.items(): + ack = await js.publish(subject, message.encode()) + print(f"Published to JetStream {subject}: {ack.stream}") + + # Get stream info + stream_info = await js.stream_info("test-stream") + print("\nStream info:") + print( + json.dumps( + { + "name": stream_info.config.name, + "subjects": stream_info.config.subjects, + "messages": stream_info.state.messages, + "bytes": stream_info.state.bytes, + }, + indent=2, + ) + ) + + # Get consumer info + consumer_info = await js.consumer_info("test-stream", "test-consumer") + print("\nConsumer info:") + print( + json.dumps( + { + "name": consumer_info.name, + "stream_name": consumer_info.stream_name, + "delivered": consumer_info.delivered.stream_seq, + "ack_floor": consumer_info.ack_floor.stream_seq, + }, + indent=2, + ) + ) + + # Create key-value store + kv = await js.create_key_value(bucket="test-kv", history=5, ttl=3600) + print("\nCreated key-value store") + + # Put values + await kv.put("key1", b"value1") + await kv.put("key2", b"value2") + print("Put values in key-value store") + + # Get values + entry = await kv.get("key1") + print(f"Got value: {entry.value.decode()}") + + # List keys + keys = await kv.keys() + print("\nKeys in store:") + for key in keys: + print(f"- {key}") + + # Delete key + await kv.delete("key1") + print("Deleted key1") + + # Create object store + os = await js.create_object_store(bucket="test-os", ttl=3600) + print("\nCreated object store") + + # Put object + await os.put("test.txt", b"Hello from object store") + print("Put object in store") + + # Get object + obj = await os.get("test.txt") + print(f"Got object: {obj.data.decode()}") + + # List objects + objects = await os.list() + print("\nObjects in store:") + for obj in objects: + print(f"- {obj.name}") + + # Delete object + await os.delete("test.txt") + print("Deleted object") + + # Clean up + await js.delete_stream("test-stream") + print("\nDeleted stream") + + await nc.close() + + +if __name__ == "__main__": + asyncio.run(basic_example()) diff --git a/docs/modules/neo4j.md b/docs/modules/neo4j.md new file mode 100644 index 000000000..971ade75f --- /dev/null +++ b/docs/modules/neo4j.md @@ -0,0 +1,23 @@ +# Neo4j + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Neo4j. + +## Adding this module to your project dependencies + +Please run the following command to add the Neo4j module to your python dependencies: + +```bash +pip install testcontainers[neo4j] neo4j +``` + +## Usage example + + + +[Creating a Neo4j container](neo4j_example.py) + + diff --git a/docs/modules/neo4j_example.py b/docs/modules/neo4j_example.py new file mode 100644 index 000000000..7bd446764 --- /dev/null +++ b/docs/modules/neo4j_example.py @@ -0,0 +1,198 @@ +import json + +from neo4j import GraphDatabase + +from testcontainers.community.neo4j import Neo4jContainer + + +def basic_example(): + with Neo4jContainer() as neo4j: + # Get connection parameters + host = neo4j.get_container_host_ip() + port = neo4j.get_exposed_port(neo4j.port) + username = neo4j.username + password = neo4j.password + + # Create Neo4j driver + driver = GraphDatabase.driver(f"bolt://{host}:{port}", auth=(username, password)) + print("Connected to Neo4j") + + # Create session + with driver.session() as session: + # Create nodes + create_nodes_query = """ + CREATE (p1:Person {name: 'Alice', age: 30}) + CREATE (p2:Person {name: 'Bob', age: 35}) + CREATE (p3:Person {name: 'Charlie', age: 25}) + CREATE (c1:Company {name: 'Tech Corp', founded: 2000}) + CREATE (c2:Company {name: 'Data Inc', founded: 2010}) + """ + session.run(create_nodes_query) + print("Created nodes") + + # Create relationships + create_rels_query = """ + MATCH (p1:Person {name: 'Alice'}), (c1:Company {name: 'Tech Corp'}) + CREATE (p1)-[:WORKS_AT {since: 2015}]->(c1) + + MATCH (p2:Person {name: 'Bob'}), (c1:Company {name: 'Tech Corp'}) + CREATE (p2)-[:WORKS_AT {since: 2018}]->(c1) + + MATCH (p3:Person {name: 'Charlie'}), (c2:Company {name: 'Data Inc'}) + CREATE (p3)-[:WORKS_AT {since: 2020}]->(c2) + + MATCH (p1:Person {name: 'Alice'}), (p2:Person {name: 'Bob'}) + CREATE (p1)-[:KNOWS {since: 2016}]->(p2) + """ + session.run(create_rels_query) + print("Created relationships") + + # Query nodes + query_nodes = """ + MATCH (n) + RETURN n + """ + result = session.run(query_nodes) + print("\nAll nodes:") + for record in result: + node = record["n"] + print(json.dumps({"labels": list(node.labels), "properties": dict(node)}, indent=2)) + + # Query relationships + query_rels = """ + MATCH (n)-[r]->(m) + RETURN n, r, m + """ + result = session.run(query_rels) + print("\nAll relationships:") + for record in result: + print( + json.dumps( + { + "from": {"labels": list(record["n"].labels), "properties": dict(record["n"])}, + "relationship": {"type": record["r"].type, "properties": dict(record["r"])}, + "to": {"labels": list(record["m"].labels), "properties": dict(record["m"])}, + }, + indent=2, + ) + ) + + # Create index + create_index = """ + CREATE INDEX person_name IF NOT EXISTS + FOR (p:Person) + ON (p.name) + """ + session.run(create_index) + print("\nCreated index on Person.name") + + # Query using index + query_indexed = """ + MATCH (p:Person) + WHERE p.name = 'Alice' + RETURN p + """ + result = session.run(query_indexed) + print("\nQuery using index:") + for record in result: + node = record["p"] + print(json.dumps({"labels": list(node.labels), "properties": dict(node)}, indent=2)) + + # Create constraint + create_constraint = """ + CREATE CONSTRAINT company_name IF NOT EXISTS + FOR (c:Company) + REQUIRE c.name IS UNIQUE + """ + session.run(create_constraint) + print("\nCreated constraint on Company.name") + + # Create full-text index + create_ft_index = """ + CALL db.index.fulltext.createNodeIndex( + "personSearch", + ["Person"], + ["name"] + ) + """ + session.run(create_ft_index) + print("Created full-text index") + + # Query using full-text index + query_ft = """ + CALL db.index.fulltext.queryNodes( + "personSearch", + "Alice" + ) + YIELD node + RETURN node + """ + result = session.run(query_ft) + print("\nFull-text search results:") + for record in result: + node = record["node"] + print(json.dumps({"labels": list(node.labels), "properties": dict(node)}, indent=2)) + + # Create stored procedure + create_proc = """ + CALL apoc.custom.asProcedure( + 'getCompanyEmployees', + 'MATCH (p:Person)-[:WORKS_AT]->(c:Company {name: $companyName}) + RETURN p', + 'READ', + [['p', 'NODE']], + [['companyName', 'STRING']] + ) + """ + session.run(create_proc) + print("\nCreated stored procedure") + + # Call stored procedure + call_proc = """ + CALL custom.getCompanyEmployees('Tech Corp') + YIELD p + RETURN p + """ + result = session.run(call_proc) + print("\nStored procedure results:") + for record in result: + node = record["p"] + print(json.dumps({"labels": list(node.labels), "properties": dict(node)}, indent=2)) + + # Create trigger + create_trigger = """ + CALL apoc.trigger.add( + 'setTimestamp', + 'UNWIND apoc.trigger.nodesByLabel($assignedLabels, "Person") AS n + SET n.updated_at = datetime()', + {phase: 'after'} + ) + """ + session.run(create_trigger) + print("\nCreated trigger") + + # Test trigger + test_trigger = """ + MATCH (p:Person {name: 'Alice'}) + SET p.age = 31 + RETURN p + """ + result = session.run(test_trigger) + print("\nTrigger test results:") + for record in result: + node = record["p"] + print(json.dumps({"labels": list(node.labels), "properties": dict(node)}, indent=2)) + + # Clean up + cleanup = """ + MATCH (n) + DETACH DELETE n + """ + session.run(cleanup) + print("\nCleaned up database") + + driver.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/nginx.md b/docs/modules/nginx.md new file mode 100644 index 000000000..2c2645c53 --- /dev/null +++ b/docs/modules/nginx.md @@ -0,0 +1,23 @@ +# Nginx + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Nginx. + +## Adding this module to your project dependencies + +Please run the following command to add the Nginx module to your python dependencies: + +```bash +pip install testcontainers[nginx] +``` + +## Usage example + + + +[Creating a Nginx container](nginx_example.py) + + diff --git a/docs/modules/nginx_example.py b/docs/modules/nginx_example.py new file mode 100644 index 000000000..e5ac30980 --- /dev/null +++ b/docs/modules/nginx_example.py @@ -0,0 +1,116 @@ +import json +import os +from pathlib import Path + +import requests + +from testcontainers.community.nginx import NginxContainer + + +def basic_example(): + with NginxContainer() as nginx: + # Get connection parameters + host = nginx.get_container_host_ip() + port = nginx.get_exposed_port(nginx.port) + nginx_url = f"http://{host}:{port}" + print(f"Nginx URL: {nginx_url}") + + # Create test HTML file + test_html = """ + + + + Test Page + + +

Hello from Nginx!

+

This is a test page.

+ + + """ + + # Create test directory and file + test_dir = Path("/tmp/nginx_test") + test_dir.mkdir(exist_ok=True) + test_file = test_dir / "index.html" + test_file.write_text(test_html) + + # Copy test file to container + nginx.get_container().copy_to_container(test_file, "/usr/share/nginx/html/") + print("Copied test file to container") + + # Test basic HTTP request + response = requests.get(nginx_url) + print(f"\nBasic request status: {response.status_code}") + print(f"Content type: {response.headers.get('content-type')}") + print(f"Content length: {response.headers.get('content-length')}") + + # Test HEAD request + head_response = requests.head(nginx_url) + print("\nHEAD request headers:") + print(json.dumps(dict(head_response.headers), indent=2)) + + # Create test configuration + test_config = """ + server { + listen 80; + server_name test.local; + + location /test { + return 200 'Test location'; + } + + location /redirect { + return 301 /test; + } + + location /error { + return 404 'Not Found'; + } + } + """ + + # Write and copy configuration + config_file = test_dir / "test.conf" + config_file.write_text(test_config) + nginx.get_container().copy_to_container(config_file, "/etc/nginx/conf.d/") + print("\nCopied test configuration") + + # Reload Nginx configuration + nginx.get_container().exec_run("nginx -s reload") + print("Reloaded Nginx configuration") + + # Test custom location + test_response = requests.get(f"{nginx_url}/test") + print(f"\nTest location response: {test_response.text}") + + # Test redirect + redirect_response = requests.get(f"{nginx_url}/redirect", allow_redirects=False) + print(f"\nRedirect status: {redirect_response.status_code}") + print(f"Redirect location: {redirect_response.headers.get('location')}") + + # Test error + error_response = requests.get(f"{nginx_url}/error") + print(f"\nError status: {error_response.status_code}") + print(f"Error response: {error_response.text}") + + # Get Nginx version + version_response = requests.get(nginx_url) + server = version_response.headers.get("server") + print(f"\nNginx version: {server}") + + # Test with different HTTP methods + methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] + print("\nHTTP method tests:") + for method in methods: + response = requests.request(method, nginx_url) + print(f"{method}: {response.status_code}") + + # Clean up + os.remove(test_file) + os.remove(config_file) + os.rmdir(test_dir) + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/ollama.md b/docs/modules/ollama.md new file mode 100644 index 000000000..0a92fa34d --- /dev/null +++ b/docs/modules/ollama.md @@ -0,0 +1,23 @@ +# Ollama + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for Ollama. + +## Adding this module to your project dependencies + +Please run the following command to add the Ollama module to your python dependencies: + +```bash +pip install testcontainers[ollama] requests +``` + +## Usage example + + + +[Creating an Ollama container](ollama_example.py) + + diff --git a/docs/modules/ollama_example.py b/docs/modules/ollama_example.py new file mode 100644 index 000000000..a5ab6c2e0 --- /dev/null +++ b/docs/modules/ollama_example.py @@ -0,0 +1,50 @@ +import requests + +from testcontainers.community.ollama import OllamaContainer + + +def basic_example(): + with OllamaContainer() as ollama: + # Get API endpoint + api_url = ollama.get_api_url() + + # Pull a model + model_name = "llama2" + print(f"Pulling model: {model_name}") + response = requests.post(f"{api_url}/api/pull", json={"name": model_name}) + print(f"Pull response: {response.json()}") + + # Generate text + prompt = "Write a short poem about programming." + print(f"\nGenerating text for prompt: {prompt}") + + response = requests.post( + f"{api_url}/api/generate", json={"model": model_name, "prompt": prompt, "stream": False} + ) + + result = response.json() + print("\nGenerated text:") + print(result["response"]) + + # Embed text + text_to_embed = "The quick brown fox jumps over the lazy dog" + print(f"\nGenerating embedding for: {text_to_embed}") + + response = requests.post(f"{api_url}/api/embeddings", json={"model": model_name, "prompt": text_to_embed}) + + embedding = response.json() + print("\nEmbedding:") + print(f"Length: {len(embedding['embedding'])}") + print(f"First 5 values: {embedding['embedding'][:5]}") + + # List available models + response = requests.get(f"{api_url}/api/tags") + models = response.json() + + print("\nAvailable models:") + for model in models["models"]: + print(f"Name: {model['name']}, Size: {model['size']}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/opensearch.md b/docs/modules/opensearch.md new file mode 100644 index 000000000..a9931b4aa --- /dev/null +++ b/docs/modules/opensearch.md @@ -0,0 +1,23 @@ +# OpenSearch + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for OpenSearch. + +## Adding this module to your project dependencies + +Please run the following command to add the OpenSearch module to your python dependencies: + +```bash +pip install testcontainers[opensearch] opensearch-py +``` + +## Usage example + + + +[Creating an OpenSearch container](opensearch_example.py) + + diff --git a/modules/generic/tests/samples/advance_1/app/__init__.py b/docs/modules/opensearch_example.py similarity index 100% rename from modules/generic/tests/samples/advance_1/app/__init__.py rename to docs/modules/opensearch_example.py diff --git a/docs/modules/oracle-free.md b/docs/modules/oracle-free.md new file mode 100644 index 000000000..87e5e9b84 --- /dev/null +++ b/docs/modules/oracle-free.md @@ -0,0 +1,23 @@ +# Oracle Free + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Oracle Free. + +## Adding this module to your project dependencies + +Please run the following command to add the Oracle Free module to your python dependencies: + +```bash +pip install testcontainers[oracle-free] +``` + +## Usage example + + + +[Creating an Oracle Free container](oracle-free_example.py) + + diff --git a/docs/modules/oracle-free_example.py b/docs/modules/oracle-free_example.py new file mode 100644 index 000000000..8abad4d01 --- /dev/null +++ b/docs/modules/oracle-free_example.py @@ -0,0 +1,140 @@ +import oracledb + +from testcontainers.oracle_free import OracleFreeContainer + + +def basic_example(): + with OracleFreeContainer() as oracle: + # Get connection parameters + host = oracle.get_container_host_ip() + port = oracle.get_exposed_port(oracle.port) + username = oracle.username + password = oracle.password + service_name = oracle.service_name + + # Create connection string + dsn = f"{host}:{port}/{service_name}" + + # Connect to Oracle + connection = oracledb.connect(user=username, password=password, dsn=dsn) + print("Connected to Oracle") + + # Create cursor + cursor = connection.cursor() + + # Create test table + cursor.execute(""" + CREATE TABLE test_table ( + id NUMBER GENERATED ALWAYS AS IDENTITY, + name VARCHAR2(50), + value NUMBER, + category VARCHAR2(10), + created_at TIMESTAMP DEFAULT SYSTIMESTAMP + ) + """) + print("Created test table") + + # Insert test data + test_data = [("test1", 100, "A"), ("test2", 200, "B"), ("test3", 300, "A")] + + cursor.executemany( + """ + INSERT INTO test_table (name, value, category) + VALUES (:1, :2, :3) + """, + test_data, + ) + print("Inserted test data") + + # Commit changes + connection.commit() + + # Query data + print("\nQuery results:") + cursor.execute("SELECT * FROM test_table WHERE category = 'A'") + for row in cursor: + print({"id": row[0], "name": row[1], "value": row[2], "category": row[3], "created_at": row[4].isoformat()}) + + # Create view + cursor.execute(""" + CREATE OR REPLACE VIEW test_view AS + SELECT category, COUNT(*) as count, AVG(value) as avg_value + FROM test_table + GROUP BY category + """) + print("\nCreated view") + + # Query view + print("\nView results:") + cursor.execute("SELECT * FROM test_view") + for row in cursor: + print({"category": row[0], "count": row[1], "avg_value": float(row[2])}) + + # Create index + cursor.execute("CREATE INDEX test_idx ON test_table (value)") + print("\nCreated index") + + # Query using index + print("\nQuery using index:") + cursor.execute("SELECT * FROM test_table WHERE value > 150") + for row in cursor: + print({"id": row[0], "name": row[1], "value": row[2], "category": row[3], "created_at": row[4].isoformat()}) + + # Get table metadata + cursor.execute(""" + SELECT column_name, data_type, data_length, nullable + FROM user_tab_columns + WHERE table_name = 'TEST_TABLE' + ORDER BY column_id + """) + print("\nTable metadata:") + for row in cursor: + print({"column": row[0], "type": row[1], "length": row[2], "nullable": row[3]}) + + # Create sequence + cursor.execute(""" + CREATE SEQUENCE test_seq + START WITH 1 + INCREMENT BY 1 + NOCACHE + NOCYCLE + """) + print("\nCreated sequence") + + # Use sequence + cursor.execute("SELECT test_seq.NEXTVAL FROM DUAL") + next_val = cursor.fetchone()[0] + print(f"Next sequence value: {next_val}") + + # Create procedure + cursor.execute(""" + CREATE OR REPLACE PROCEDURE test_proc ( + p_category IN VARCHAR2, + p_count OUT NUMBER + ) AS + BEGIN + SELECT COUNT(*) + INTO p_count + FROM test_table + WHERE category = p_category; + END; + """) + print("\nCreated procedure") + + # Execute procedure + cursor.execute(""" + DECLARE + v_count NUMBER; + BEGIN + test_proc('A', v_count); + DBMS_OUTPUT.PUT_LINE('Count for category A: ' || v_count); + END; + """) + + # Clean up + cursor.close() + connection.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/postgres.md b/docs/modules/postgres.md new file mode 100644 index 000000000..9d6769af8 --- /dev/null +++ b/docs/modules/postgres.md @@ -0,0 +1,23 @@ +# PostgreSQL + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for PostgreSQL. + +## Adding this module to your project dependencies + +Please run the following command to add the PostgreSQL module to your python dependencies: + +```bash +pip install testcontainers[postgres] sqlalchemy psycopg2 +``` + +## Usage example + + + +[Creating a PostgreSQL container](postgres_example.py) + + diff --git a/docs/modules/postgres_example.py b/docs/modules/postgres_example.py new file mode 100644 index 000000000..bc6a80366 --- /dev/null +++ b/docs/modules/postgres_example.py @@ -0,0 +1,99 @@ +import pandas as pd +import sqlalchemy +from sqlalchemy import text + +from testcontainers.community.postgres import PostgresContainer + + +def basic_example(): + with PostgresContainer() as postgres: + # Get connection URL + connection_url = postgres.get_connection_url() + + # Create SQLAlchemy engine + engine = sqlalchemy.create_engine(connection_url) + print("Connected to PostgreSQL") + + # Create a test table + create_table_sql = """ + CREATE TABLE test_table ( + id SERIAL PRIMARY KEY, + name VARCHAR(50), + value DECIMAL(10,2), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """ + + with engine.begin() as connection: + connection.execute(text(create_table_sql)) + print("Created test table") + + # Insert test data + test_data = [ + {"name": "test1", "value": 100.0}, + {"name": "test2", "value": 200.0}, + {"name": "test3", "value": 300.0}, + ] + + with engine.begin() as connection: + for data in test_data: + connection.execute(text("INSERT INTO test_table (name, value) VALUES (:name, :value)"), data) + print("Inserted test data") + + # Query data + with engine.connect() as connection: + result = connection.execute(text("SELECT * FROM test_table ORDER BY id")) + rows = result.fetchall() + + print("\nQuery results:") + for row in rows: + print(f"ID: {row[0]}, Name: {row[1]}, Value: {row[2]}, Created: {row[3]}") + + # Execute a more complex query + with engine.connect() as connection: + result = connection.execute( + text(""" + SELECT + name, + AVG(value) as avg_value, + COUNT(*) as count, + MIN(created_at) as first_created, + MAX(created_at) as last_created + FROM test_table + GROUP BY name + ORDER BY avg_value DESC + """) + ) + + print("\nAggregation results:") + for row in result: + print(f"Name: {row[0]}, Avg: {row[1]:.2f}, Count: {row[2]}, First: {row[3]}, Last: {row[4]}") + + # Convert to pandas DataFrame + df = pd.read_sql("SELECT * FROM test_table ORDER BY id", engine) + print("\nDataFrame:") + print(df) + + # Create and query a view + create_view_sql = """ + CREATE OR REPLACE VIEW test_view AS + SELECT + name, + AVG(value) as avg_value, + COUNT(*) as count + FROM test_table + GROUP BY name + """ + + with engine.begin() as connection: + connection.execute(text(create_view_sql)) + print("\nCreated view") + + result = connection.execute(text("SELECT * FROM test_view")) + print("\nView results:") + for row in result: + print(f"Name: {row[0]}, Avg: {row[1]:.2f}, Count: {row[2]}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/qdrant.md b/docs/modules/qdrant.md new file mode 100644 index 000000000..2d53146ba --- /dev/null +++ b/docs/modules/qdrant.md @@ -0,0 +1,23 @@ +# Qdrant + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Qdrant. + +## Adding this module to your project dependencies + +Please run the following command to add the Qdrant module to your python dependencies: + +```bash +pip install testcontainers[qdrant] +``` + +## Usage example + + + +[Creating a Qdrant container](qdrant_example.py) + + diff --git a/docs/modules/qdrant_example.py b/docs/modules/qdrant_example.py new file mode 100644 index 000000000..d8ce029d8 --- /dev/null +++ b/docs/modules/qdrant_example.py @@ -0,0 +1,149 @@ +import json +from datetime import datetime + +import numpy as np +from qdrant_client import QdrantClient +from qdrant_client.http import models + +from testcontainers.community.qdrant import QdrantContainer + + +def basic_example(): + with QdrantContainer() as qdrant: + # Get connection parameters + host = qdrant.get_container_host_ip() + port = qdrant.get_exposed_port(qdrant.port) + + # Create Qdrant client + client = QdrantClient(host=host, port=port) + print("Connected to Qdrant") + + # Create collection + collection_name = "test_collection" + vector_size = 128 + + client.create_collection( + collection_name=collection_name, + vectors_config=models.VectorParams(size=vector_size, distance=models.Distance.COSINE), + ) + print(f"Created collection: {collection_name}") + + # Generate test vectors and payloads + num_vectors = 5 + vectors = np.random.rand(num_vectors, vector_size).tolist() + + payloads = [ + { + "text": "AI and machine learning are transforming industries", + "category": "Technology", + "tags": ["AI", "ML", "innovation"], + "timestamp": datetime.utcnow().isoformat(), + }, + { + "text": "New study reveals benefits of meditation", + "category": "Health", + "tags": ["wellness", "mental health"], + "timestamp": datetime.utcnow().isoformat(), + }, + { + "text": "Global warming reaches critical levels", + "category": "Environment", + "tags": ["climate", "sustainability"], + "timestamp": datetime.utcnow().isoformat(), + }, + { + "text": "Stock market shows strong growth", + "category": "Finance", + "tags": ["investing", "markets"], + "timestamp": datetime.utcnow().isoformat(), + }, + { + "text": "New restaurant opens in downtown", + "category": "Food", + "tags": ["dining", "local"], + "timestamp": datetime.utcnow().isoformat(), + }, + ] + + # Upload vectors with payloads + client.upsert( + collection_name=collection_name, + points=models.Batch(ids=list(range(num_vectors)), vectors=vectors, payloads=payloads), + ) + print("Uploaded vectors with payloads") + + # Search vectors + search_result = client.search(collection_name=collection_name, query_vector=vectors[0], limit=3) + print("\nSearch results:") + for scored_point in search_result: + print( + json.dumps( + {"id": scored_point.id, "score": scored_point.score, "payload": scored_point.payload}, indent=2 + ) + ) + + # Filtered search + filter_result = client.search( + collection_name=collection_name, + query_vector=vectors[0], + query_filter=models.Filter( + must=[models.FieldCondition(key="category", match=models.MatchValue(value="Technology"))] + ), + limit=2, + ) + print("\nFiltered search results:") + for scored_point in filter_result: + print( + json.dumps( + {"id": scored_point.id, "score": scored_point.score, "payload": scored_point.payload}, indent=2 + ) + ) + + # Create payload index + client.create_payload_index( + collection_name=collection_name, field_name="category", field_schema=models.PayloadFieldSchema.KEYWORD + ) + print("\nCreated payload index on category field") + + # Create vector index + client.create_payload_index( + collection_name=collection_name, field_name="tags", field_schema=models.PayloadFieldSchema.KEYWORD + ) + print("Created payload index on tags field") + + # Scroll through collection + scroll_result = client.scroll(collection_name=collection_name, limit=10, with_payload=True, with_vectors=True) + print("\nScrolled through collection:") + for point in scroll_result[0]: + print(json.dumps({"id": point.id, "payload": point.payload}, indent=2)) + + # Get collection info + collection_info = client.get_collection(collection_name) + print("\nCollection info:") + print( + json.dumps( + { + "name": collection_info.name, + "vectors_count": collection_info.vectors_count, + "points_count": collection_info.points_count, + "status": collection_info.status, + }, + indent=2, + ) + ) + + # Update payload + client.set_payload(collection_name=collection_name, payload={"new_field": "updated value"}, points=[0, 1]) + print("\nUpdated payload for points 0 and 1") + + # Delete points + client.delete(collection_name=collection_name, points_selector=models.PointIdsList(points=[4])) + print("Deleted point with id 4") + + # Clean up + client.delete_collection(collection_name) + print("\nDeleted collection") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/rabbitmq.md b/docs/modules/rabbitmq.md new file mode 100644 index 000000000..ec754d984 --- /dev/null +++ b/docs/modules/rabbitmq.md @@ -0,0 +1,23 @@ +# RabbitMQ + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for RabbitMQ. + +## Adding this module to your project dependencies + +Please run the following command to add the RabbitMQ module to your python dependencies: + +```bash +pip install testcontainers[rabbitmq] pika +``` + +## Usage example + + + +[Creating a RabbitMQ container](rabbitmq_example.py) + + diff --git a/docs/modules/rabbitmq_example.py b/docs/modules/rabbitmq_example.py new file mode 100644 index 000000000..fc79bbde7 --- /dev/null +++ b/docs/modules/rabbitmq_example.py @@ -0,0 +1,98 @@ +import json +import time +from threading import Thread + +import pika + +from testcontainers.community.rabbitmq import RabbitMQContainer + + +def basic_example(): + with RabbitMQContainer() as rabbitmq: + # Get connection parameters + host = rabbitmq.get_container_host_ip() + port = rabbitmq.get_exposed_port(rabbitmq.port) + username = rabbitmq.username + password = rabbitmq.password + + # Create connection + credentials = pika.PlainCredentials(username, password) + parameters = pika.ConnectionParameters(host=host, port=port, credentials=credentials) + connection = pika.BlockingConnection(parameters) + channel = connection.channel() + print("Connected to RabbitMQ") + + # Declare exchange + exchange_name = "test_exchange" + channel.exchange_declare(exchange=exchange_name, exchange_type="direct", durable=True) + print(f"Declared exchange: {exchange_name}") + + # Declare queues + queues = {"queue1": "routing_key1", "queue2": "routing_key2"} + + for queue_name, routing_key in queues.items(): + channel.queue_declare(queue=queue_name, durable=True) + channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key=routing_key) + print(f"Declared and bound queue: {queue_name}") + + # Define message handler + def message_handler(ch, method, properties, body): + message = json.loads(body) + print(f"\nReceived message on {method.routing_key}:") + print(json.dumps(message, indent=2)) + ch.basic_ack(delivery_tag=method.delivery_tag) + + # Start consuming in a separate thread + def consume_messages(): + channel.basic_qos(prefetch_count=1) + for queue_name in queues: + channel.basic_consume(queue=queue_name, on_message_callback=message_handler) + channel.start_consuming() + + consumer_thread = Thread(target=consume_messages) + consumer_thread.daemon = True + consumer_thread.start() + + # Publish messages + test_messages = [ + { + "queue": "queue1", + "routing_key": "routing_key1", + "message": {"id": 1, "content": "Message for queue 1", "timestamp": time.time()}, + }, + { + "queue": "queue2", + "routing_key": "routing_key2", + "message": {"id": 2, "content": "Message for queue 2", "timestamp": time.time()}, + }, + ] + + for msg in test_messages: + channel.basic_publish( + exchange=exchange_name, + routing_key=msg["routing_key"], + body=json.dumps(msg["message"]), + properties=pika.BasicProperties( + delivery_mode=2, # make message persistent + content_type="application/json", + ), + ) + print(f"Published message to {msg['queue']}") + + # Wait for messages to be processed + time.sleep(2) + + # Get queue information + print("\nQueue information:") + for queue_name in queues: + queue = channel.queue_declare(queue=queue_name, passive=True) + print(f"{queue_name}:") + print(f" Messages: {queue.method.message_count}") + print(f" Consumers: {queue.method.consumer_count}") + + # Clean up + connection.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/redis.md b/docs/modules/redis.md new file mode 100644 index 000000000..aced9cbdf --- /dev/null +++ b/docs/modules/redis.md @@ -0,0 +1,23 @@ +# Redis + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Redis. + +## Adding this module to your project dependencies + +Please run the following command to add the Redis module to your python dependencies: + +```bash +pip install testcontainers[redis] redis +``` + +## Usage example + + + +[Creating a Redis container](redis_example.py) + + diff --git a/docs/modules/redis_example.py b/docs/modules/redis_example.py new file mode 100644 index 000000000..374b45619 --- /dev/null +++ b/docs/modules/redis_example.py @@ -0,0 +1,84 @@ +from datetime import timedelta + +import redis + +from testcontainers.community.redis import RedisContainer + + +def basic_example(): + with RedisContainer() as redis_container: + # Get connection parameters + host = redis_container.get_container_host_ip() + port = redis_container.get_exposed_port(redis_container.port) + + # Create Redis client + client = redis.Redis(host=host, port=port, decode_responses=True) + print("Connected to Redis") + + # String operations + client.set("greeting", "Hello, Redis!") + value = client.get("greeting") + print(f"\nString value: {value}") + + # List operations + client.lpush("tasks", "task1", "task2", "task3") + tasks = client.lrange("tasks", 0, -1) + print("\nTasks list:") + for task in tasks: + print(f"- {task}") + + # Set operations + client.sadd("tags", "python", "redis", "docker", "testing") + tags = client.smembers("tags") + print("\nTags set:") + for tag in tags: + print(f"- {tag}") + + # Hash operations + user_data = {"name": "John Doe", "email": "john@example.com", "age": "30"} + client.hset("user:1", mapping=user_data) + user = client.hgetall("user:1") + print("\nUser hash:") + for field, value in user.items(): + print(f"{field}: {value}") + + # Sorted set operations + scores = {"player1": 100, "player2": 200, "player3": 150} + client.zadd("leaderboard", scores) + leaderboard = client.zrevrange("leaderboard", 0, -1, withscores=True) + print("\nLeaderboard:") + for player, score in leaderboard: + print(f"{player}: {score}") + + # Key expiration + client.setex("temp_key", timedelta(seconds=10), "This will expire") + ttl = client.ttl("temp_key") + print(f"\nTemp key TTL: {ttl} seconds") + + # Pipeline operations + with client.pipeline() as pipe: + pipe.set("pipeline_key1", "value1") + pipe.set("pipeline_key2", "value2") + pipe.set("pipeline_key3", "value3") + pipe.execute() + print("\nPipeline operations completed") + + # Pub/Sub operations + pubsub = client.pubsub() + pubsub.subscribe("test_channel") + + # Publish a message + client.publish("test_channel", "Hello from Redis!") + + # Get the message + message = pubsub.get_message() + if message and message["type"] == "message": + print(f"\nReceived message: {message['data']}") + + # Clean up + pubsub.unsubscribe() + pubsub.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/registry.md b/docs/modules/registry.md new file mode 100644 index 000000000..cc160b91c --- /dev/null +++ b/docs/modules/registry.md @@ -0,0 +1,23 @@ +# Registry + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Registry. + +## Adding this module to your project dependencies + +Please run the following command to add the Registry module to your python dependencies: + +```bash +pip install testcontainers[registry] +``` + +## Usage example + + + +[Creating a Registry container](registry_example.py) + + diff --git a/docs/modules/registry_example.py b/docs/modules/registry_example.py new file mode 100644 index 000000000..6b8289d17 --- /dev/null +++ b/docs/modules/registry_example.py @@ -0,0 +1,92 @@ +import json + +import requests + +from testcontainers.community.registry import RegistryContainer + + +def basic_example(): + with RegistryContainer() as registry: + # Get connection parameters + host = registry.get_container_host_ip() + port = registry.get_exposed_port(registry.port) + registry_url = f"http://{host}:{port}" + print(f"Registry URL: {registry_url}") + + # Get registry version + version_response = requests.get(f"{registry_url}/v2/") + print(f"Registry version: {version_response.headers.get('Docker-Distribution-Api-Version')}") + + # List repositories + catalog_response = requests.get(f"{registry_url}/v2/_catalog") + repositories = catalog_response.json()["repositories"] + print("\nRepositories:") + print(json.dumps(repositories, indent=2)) + + # Create test repository + test_repo = "test-repo" + test_tag = "latest" + + # Create a simple manifest + manifest = { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 1000, + "digest": "sha256:1234567890abcdef", + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 2000, + "digest": "sha256:abcdef1234567890", + } + ], + } + + # Upload manifest + manifest_url = f"{registry_url}/v2/{test_repo}/manifests/{test_tag}" + headers = {"Content-Type": "application/vnd.docker.distribution.manifest.v2+json"} + manifest_response = requests.put(manifest_url, json=manifest, headers=headers) + print(f"\nUploaded manifest: {manifest_response.status_code}") + + # List tags for repository + tags_url = f"{registry_url}/v2/{test_repo}/tags/list" + tags_response = requests.get(tags_url) + tags = tags_response.json()["tags"] + print("\nTags:") + print(json.dumps(tags, indent=2)) + + # Get manifest + manifest_response = requests.get(manifest_url, headers=headers) + manifest_data = manifest_response.json() + print("\nManifest:") + print(json.dumps(manifest_data, indent=2)) + + # Get manifest digest + digest = manifest_response.headers.get("Docker-Content-Digest") + print(f"\nManifest digest: {digest}") + + # Delete manifest + delete_response = requests.delete(manifest_url) + print(f"\nDeleted manifest: {delete_response.status_code}") + + # Verify deletion + verify_response = requests.get(manifest_url) + print(f"Manifest exists: {verify_response.status_code == 200}") + + # Get registry configuration + config_url = f"{registry_url}/v2/" + config_response = requests.get(config_url) + print("\nRegistry configuration:") + print(json.dumps(dict(config_response.headers), indent=2)) + + # Get registry health + health_url = f"{registry_url}/v2/" + health_response = requests.get(health_url) + print(f"\nRegistry health: {health_response.status_code == 200}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/scylla.md b/docs/modules/scylla.md new file mode 100644 index 000000000..48fbfff6c --- /dev/null +++ b/docs/modules/scylla.md @@ -0,0 +1,23 @@ +# Scylla + +Since testcontainers-python :material-tag: v4.8.0 + +## Introduction + +The Testcontainers module for Scylla. + +## Adding this module to your project dependencies + +Please run the following command to add the Scylla module to your python dependencies: + +```bash +pip install testcontainers[scylla] +``` + +## Usage example + + + +[Creating a Scylla container](scylla_example.py) + + diff --git a/docs/modules/scylla_example.py b/docs/modules/scylla_example.py new file mode 100644 index 000000000..7cc913955 --- /dev/null +++ b/docs/modules/scylla_example.py @@ -0,0 +1,153 @@ +import json +from datetime import datetime + +from cassandra.auth import PlainTextAuthProvider +from cassandra.cluster import Cluster + +from testcontainers.community.scylla import ScyllaContainer + + +def basic_example(): + with ScyllaContainer() as scylla: + # Get connection parameters + host = scylla.get_container_host_ip() + port = scylla.get_exposed_port(scylla.port) + username = scylla.username + password = scylla.password + + # Create Scylla client + auth_provider = PlainTextAuthProvider(username=username, password=password) + cluster = Cluster([host], port=port, auth_provider=auth_provider) + session = cluster.connect() + print("Connected to Scylla") + + # Create keyspace + session.execute(""" + CREATE KEYSPACE IF NOT EXISTS test_keyspace + WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1} + """) + print("Created keyspace") + + # Use keyspace + session.set_keyspace("test_keyspace") + + # Create table + session.execute(""" + CREATE TABLE IF NOT EXISTS test_table ( + id UUID PRIMARY KEY, + name text, + value int, + category text, + created_at timestamp + ) + """) + print("Created table") + + # Insert test data + test_data = [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "test1", + "value": 100, + "category": "A", + "created_at": datetime.utcnow(), + }, + { + "id": "550e8400-e29b-41d4-a716-446655440001", + "name": "test2", + "value": 200, + "category": "B", + "created_at": datetime.utcnow(), + }, + { + "id": "550e8400-e29b-41d4-a716-446655440002", + "name": "test3", + "value": 300, + "category": "A", + "created_at": datetime.utcnow(), + }, + ] + + insert_stmt = session.prepare(""" + INSERT INTO test_table (id, name, value, category, created_at) + VALUES (uuid(), ?, ?, ?, ?) + """) + + for data in test_data: + session.execute(insert_stmt, (data["name"], data["value"], data["category"], data["created_at"])) + print("Inserted test data") + + # Query data + print("\nQuery results:") + rows = session.execute("SELECT * FROM test_table WHERE category = 'A' ALLOW FILTERING") + for row in rows: + print( + json.dumps( + { + "id": str(row.id), + "name": row.name, + "value": row.value, + "category": row.category, + "created_at": row.created_at.isoformat(), + }, + indent=2, + ) + ) + + # Create materialized view + session.execute(""" + CREATE MATERIALIZED VIEW IF NOT EXISTS test_view AS + SELECT category, name, value, created_at + FROM test_table + WHERE category IS NOT NULL AND name IS NOT NULL + PRIMARY KEY (category, name) + """) + print("\nCreated materialized view") + + # Query materialized view + print("\nMaterialized view results:") + rows = session.execute("SELECT * FROM test_view WHERE category = 'A'") + for row in rows: + print( + json.dumps( + { + "category": row.category, + "name": row.name, + "value": row.value, + "created_at": row.created_at.isoformat(), + }, + indent=2, + ) + ) + + # Create secondary index + session.execute("CREATE INDEX IF NOT EXISTS ON test_table (value)") + print("\nCreated secondary index") + + # Query using secondary index + print("\nQuery using secondary index:") + rows = session.execute("SELECT * FROM test_table WHERE value > 150 ALLOW FILTERING") + for row in rows: + print( + json.dumps( + { + "id": str(row.id), + "name": row.name, + "value": row.value, + "category": row.category, + "created_at": row.created_at.isoformat(), + }, + indent=2, + ) + ) + + # Get table metadata + table_meta = session.cluster.metadata.keyspaces["test_keyspace"].tables["test_table"] + print("\nTable metadata:") + print(f"Columns: {[col.name for col in table_meta.columns.values()]}") + print(f"Partition key: {[col.name for col in table_meta.partition_key]}") + print(f"Clustering key: {[col.name for col in table_meta.clustering_key]}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/selenium.md b/docs/modules/selenium.md new file mode 100644 index 000000000..9d07effb4 --- /dev/null +++ b/docs/modules/selenium.md @@ -0,0 +1,23 @@ +# Selenium + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Selenium. + +## Adding this module to your project dependencies + +Please run the following command to add the Selenium module to your python dependencies: + +```bash +pip install testcontainers[selenium] selenium urllib3 +``` + +## Usage example + + + +[Creating a Selenium container](selenium_example.py) + + diff --git a/docs/modules/selenium_example.py b/docs/modules/selenium_example.py new file mode 100644 index 000000000..355e3d6a6 --- /dev/null +++ b/docs/modules/selenium_example.py @@ -0,0 +1,49 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +from testcontainers.community.selenium import SeleniumContainer + + +def basic_example(): + with SeleniumContainer() as selenium: + # Get the Selenium WebDriver + driver = selenium.get_driver() + + try: + # Navigate to a test page + driver.get("https://www.python.org") + print("Navigated to python.org") + + # Wait for the search box to be present + search_box = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "id-search-field"))) + + # Type in the search box + search_box.send_keys("selenium") + print("Entered search term") + + # Click the search button + search_button = driver.find_element(By.ID, "submit") + search_button.click() + print("Clicked search button") + + # Wait for search results + WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "list-recent-events"))) + + # Get search results + results = driver.find_elements(By.CSS_SELECTOR, ".list-recent-events li") + print("\nSearch results:") + for result in results[:3]: # Print first 3 results + print(result.text) + + # Take a screenshot + driver.save_screenshot("python_search_results.png") + print("\nScreenshot saved as 'python_search_results.png'") + + finally: + # Clean up + driver.quit() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/sftp.md b/docs/modules/sftp.md new file mode 100644 index 000000000..cbe1b3f99 --- /dev/null +++ b/docs/modules/sftp.md @@ -0,0 +1,23 @@ +# SFTP + +Since testcontainers-python :material-tag: v4.7.1 + +## Introduction + +The Testcontainers module for SFTP. + +## Adding this module to your project dependencies + +Please run the following command to add the SFTP module to your python dependencies: + +```bash +pip install testcontainers[sftp] paramiko cryptography +``` + +## Usage example + + + +[Creating an SFTP container](sftp_example.py) + + diff --git a/docs/modules/sftp_example.py b/docs/modules/sftp_example.py new file mode 100644 index 000000000..279729e13 --- /dev/null +++ b/docs/modules/sftp_example.py @@ -0,0 +1,137 @@ +import json +import os +from datetime import datetime + +import paramiko + +from testcontainers.community.sftp import SftpContainer + + +def basic_example(): + with SftpContainer() as sftp: + # Get connection parameters + host = sftp.get_container_host_ip() + port = sftp.get_exposed_port(sftp.port) + username = sftp.username + password = sftp.password + + # Create SSH client + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh.connect(host, port, username, password) + print("Connected to SFTP server") + + # Create SFTP client + sftp_client = ssh.open_sftp() + + # Create test directory + test_dir = "/home/testuser/test_dir" + sftp_client.mkdir(test_dir) + print(f"Created directory: {test_dir}") + + # Create and upload test files + test_files = [ + {"name": "test1.txt", "content": "This is test file 1"}, + {"name": "test2.txt", "content": "This is test file 2"}, + {"name": "test3.txt", "content": "This is test file 3"}, + ] + + for file_info in test_files: + local_path = f"/tmp/{file_info['name']}" + remote_path = f"{test_dir}/{file_info['name']}" + + # Create local file + with open(local_path, "w") as f: + f.write(file_info["content"]) + + # Upload file + sftp_client.put(local_path, remote_path) + print(f"Uploaded file: {file_info['name']}") + + # Remove local file + os.remove(local_path) + + # List directory contents + print("\nDirectory contents:") + for entry in sftp_client.listdir_attr(test_dir): + print( + json.dumps( + { + "filename": entry.filename, + "size": entry.st_size, + "modified": datetime.fromtimestamp(entry.st_mtime).isoformat(), + }, + indent=2, + ) + ) + + # Download and read file + print("\nReading file contents:") + for file_info in test_files: + remote_path = f"{test_dir}/{file_info['name']}" + local_path = f"/tmp/{file_info['name']}" + + # Download file + sftp_client.get(remote_path, local_path) + + # Read and print contents + with open(local_path) as f: + content = f.read() + print(f"\n{file_info['name']}:") + print(content) + + # Remove local file + os.remove(local_path) + + # Create nested directory + nested_dir = f"{test_dir}/nested" + sftp_client.mkdir(nested_dir) + print(f"\nCreated nested directory: {nested_dir}") + + # Move file to nested directory + old_path = f"{test_dir}/test1.txt" + new_path = f"{nested_dir}/test1.txt" + sftp_client.rename(old_path, new_path) + print("Moved file to nested directory") + + # List nested directory + print("\nNested directory contents:") + for entry in sftp_client.listdir_attr(nested_dir): + print( + json.dumps( + { + "filename": entry.filename, + "size": entry.st_size, + "modified": datetime.fromtimestamp(entry.st_mtime).isoformat(), + }, + indent=2, + ) + ) + + # Get file attributes + print("\nFile attributes:") + for file_info in test_files: + remote_path = f"{test_dir}/{file_info['name']}" + try: + attrs = sftp_client.stat(remote_path) + print(f"\n{file_info['name']}:") + print( + json.dumps( + { + "size": attrs.st_size, + "permissions": oct(attrs.st_mode)[-3:], + "modified": datetime.fromtimestamp(attrs.st_mtime).isoformat(), + }, + indent=2, + ) + ) + except FileNotFoundError: + print(f"File not found: {file_info['name']}") + + # Clean up + sftp_client.close() + ssh.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/toxiproxy.md b/docs/modules/toxiproxy.md new file mode 100644 index 000000000..4354f667d --- /dev/null +++ b/docs/modules/toxiproxy.md @@ -0,0 +1,26 @@ +# Toxiproxy + +Since testcontainers-python :material-tag: v4.16.0 + +## Introduction + +The Testcontainers module for [Toxiproxy](https://github.com/Shopify/toxiproxy), a TCP proxy for +simulating adverse network conditions. Put Toxiproxy in front of a dependency and inject latency, +bandwidth limits, connection drops and other failures to test how your application behaves when its +dependencies misbehave. + +## Adding this module to your project dependencies + +Please run the following command to add the Toxiproxy module to your python dependencies: + +```bash +pip install testcontainers[toxiproxy] +``` + +## Usage example + + + +[Injecting latency with Toxiproxy](toxiproxy_example.py) + + diff --git a/docs/modules/toxiproxy_example.py b/docs/modules/toxiproxy_example.py new file mode 100644 index 000000000..3890d2daf --- /dev/null +++ b/docs/modules/toxiproxy_example.py @@ -0,0 +1,31 @@ +import time + +import requests + +from testcontainers.community.nginx import NginxContainer +from testcontainers.community.toxiproxy import ToxiproxyContainer +from testcontainers.core.network import Network + + +def latency_example(): + with Network() as network: + # An upstream service and Toxiproxy share a network so Toxiproxy can reach it. + nginx = NginxContainer("nginx:alpine").with_network(network).with_network_aliases("nginx") + toxiproxy = ToxiproxyContainer().with_network(network) + with nginx, toxiproxy: + # Route traffic to the upstream "nginx:80" through Toxiproxy. + proxy = toxiproxy.create_proxy("nginx", "nginx:80") + url = f"http://{proxy.host}:{proxy.proxy_port}/" + + print(f"Status without toxics: {requests.get(url).status_code}") + + # Inject 1 second of downstream latency. + proxy.add_toxic("latency", {"latency": 1000}) + + start = time.monotonic() + requests.get(url) + print(f"Request took {time.monotonic() - start:.2f}s with the latency toxic") + + +if __name__ == "__main__": + latency_example() diff --git a/docs/modules/trino.md b/docs/modules/trino.md new file mode 100644 index 000000000..89d724809 --- /dev/null +++ b/docs/modules/trino.md @@ -0,0 +1,23 @@ +# Trino + +Since testcontainers-python :material-tag: v4.7.2 + +## Introduction + +The Testcontainers module for Trino. + +## Adding this module to your project dependencies + +Please run the following command to add the Trino module to your python dependencies: + +```bash +pip install testcontainers[trino] trino +``` + +## Usage example + + + +[Creating a Trino container](trino_example.py) + + diff --git a/docs/modules/trino_example.py b/docs/modules/trino_example.py new file mode 100644 index 000000000..b2466ca23 --- /dev/null +++ b/docs/modules/trino_example.py @@ -0,0 +1,66 @@ +import trino +from trino.exceptions import TrinoQueryError + +from testcontainers.community.trino import TrinoContainer + + +def basic_example(): + with TrinoContainer() as trino_container: + # Get connection parameters + host = trino_container.get_container_host_ip() + port = trino_container.get_exposed_port(trino_container.port) + + # Create Trino client + conn = trino.dbapi.connect(host=host, port=port, user="test", catalog="memory", schema="default") + cur = conn.cursor() + + # Create a test table + try: + cur.execute(""" + CREATE TABLE memory.default.test_table ( + id BIGINT, + name VARCHAR, + value DOUBLE + ) + """) + print("Created test table") + except TrinoQueryError as e: + print(f"Table might already exist: {e}") + + # Insert test data + test_data = [(1, "test1", 100.0), (2, "test2", 200.0), (3, "test3", 300.0)] + + for row in test_data: + cur.execute("INSERT INTO memory.default.test_table VALUES (%s, %s, %s)", row) + print("Inserted test data") + + # Query data + cur.execute("SELECT * FROM memory.default.test_table ORDER BY id") + rows = cur.fetchall() + + print("\nQuery results:") + for row in rows: + print(f"ID: {row[0]}, Name: {row[1]}, Value: {row[2]}") + + # Execute a more complex query + cur.execute(""" + SELECT + name, + AVG(value) as avg_value, + COUNT(*) as count + FROM memory.default.test_table + GROUP BY name + ORDER BY avg_value DESC + """) + + print("\nAggregation results:") + for row in cur.fetchall(): + print(f"Name: {row[0]}, Average Value: {row[1]}, Count: {row[2]}") + + # Clean up + cur.close() + conn.close() + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/valkey.md b/docs/modules/valkey.md new file mode 100644 index 000000000..db45a7fcb --- /dev/null +++ b/docs/modules/valkey.md @@ -0,0 +1,23 @@ +# Valkey + +Since testcontainers-python :material-tag: v4.14.3 + +## Introduction + +The Testcontainers module for Valkey. + +## Adding this module to your project dependencies + +Please run the following command to add the Valkey module to your python dependencies: + +```bash +pip install testcontainers[valkey] +``` + +## Usage example + + + +[Creating a Valkey container](valkey_example.py) + + diff --git a/docs/modules/valkey_example.py b/docs/modules/valkey_example.py new file mode 100644 index 000000000..1704a4e5a --- /dev/null +++ b/docs/modules/valkey_example.py @@ -0,0 +1,82 @@ +""" +Valkey container usage examples with valkey-glide sync client. + +Requires: pip install valkey-glide-sync +""" + +from glide_sync import GlideClient, GlideClientConfiguration, NodeAddress, ServerCredentials + +from testcontainers.community.valkey import ValkeyContainer + + +def basic_example(): + with ValkeyContainer() as valkey_container: + host = valkey_container.get_host() + port = valkey_container.get_exposed_port() + connection_url = valkey_container.get_connection_url() + + print(f"Valkey connection URL: {connection_url}") + print(f"Host: {host}, Port: {port}") + + config = GlideClientConfiguration([NodeAddress(host, port)]) + client = GlideClient.create(config) + + pong = client.ping() + print(f"PING response: {pong}") + + client.set("key", "value") + print("SET response: OK") + + value = client.get("key") + print(f"GET response: {value}") + + client.close() + + +def password_example(): + with ValkeyContainer().with_password("mypassword") as valkey_container: + host = valkey_container.get_host() + port = valkey_container.get_exposed_port() + connection_url = valkey_container.get_connection_url() + + print(f"\nValkey with password connection URL: {connection_url}") + + config = GlideClientConfiguration( + [NodeAddress(host, port)], + credentials=ServerCredentials(password="mypassword"), + ) + client = GlideClient.create(config) + + pong = client.ping() + print(f"PING response: {pong}") + + client.close() + + +def version_example(): + with ValkeyContainer().with_image_tag("8.0") as valkey_container: + print(f"\nUsing image: {valkey_container.image}") + connection_url = valkey_container.get_connection_url() + print(f"Connection URL: {connection_url}") + + +def bundle_example(): + with ValkeyContainer().with_bundle() as valkey_container: + print(f"\nUsing bundle image: {valkey_container.image}") + host = valkey_container.get_host() + port = valkey_container.get_exposed_port() + + config = GlideClientConfiguration([NodeAddress(host, port)]) + client = GlideClient.create(config) + + pong = client.ping() + print(f"PING response: {pong}") + + client.close() + + +if __name__ == "__main__": + basic_example() + password_example() + version_example() + bundle_example() diff --git a/docs/modules/vault.md b/docs/modules/vault.md new file mode 100644 index 000000000..139c88af4 --- /dev/null +++ b/docs/modules/vault.md @@ -0,0 +1,23 @@ +# Vault + +Since testcontainers-python :material-tag: v4.7.0 + +## Introduction + +The Testcontainers module for Vault. + +## Adding this module to your project dependencies + +Please run the following command to add the Vault module to your python dependencies: + +```bash +pip install testcontainers[vault] hvac +``` + +## Usage example + + + +[Creating a Vault container](vault_example.py) + + diff --git a/docs/modules/vault_example.py b/docs/modules/vault_example.py new file mode 100644 index 000000000..872f5f577 --- /dev/null +++ b/docs/modules/vault_example.py @@ -0,0 +1,75 @@ +import json + +import hvac + +from testcontainers.community.vault import VaultContainer + + +def basic_example(): + with VaultContainer() as vault: + # Get connection parameters + host = vault.get_container_host_ip() + port = vault.get_exposed_port(vault.port) + token = vault.token + + # Create Vault client + client = hvac.Client(url=f"http://{host}:{port}", token=token) + print("Connected to Vault") + + # Enable KV secrets engine + client.sys.enable_secrets_engine(backend_type="kv", path="secret", options={"version": "2"}) + print("Enabled KV secrets engine") + + # Write secrets + test_secrets = { + "database": {"username": "admin", "password": "secret123", "host": "localhost"}, + "api": {"key": "api-key-123", "endpoint": "https://api.example.com"}, + } + + for path, secret in test_secrets.items(): + client.secrets.kv.v2.create_or_update_secret(path=path, secret=secret) + print(f"Created secret at: {path}") + + # Read secrets + print("\nReading secrets:") + for path in test_secrets: + secret = client.secrets.kv.v2.read_secret_version(path=path) + print(f"\nSecret at {path}:") + print(json.dumps(secret["data"]["data"], indent=2)) + + # Enable and configure AWS secrets engine + client.sys.enable_secrets_engine(backend_type="aws", path="aws") + print("\nEnabled AWS secrets engine") + + # Configure AWS credentials + client.secrets.aws.configure_root( + access_key="test-access-key", secret_key="test-secret-key", region="us-east-1" + ) + print("Configured AWS credentials") + + # Create a role + client.secrets.aws.create_role( + name="test-role", + credential_type="iam_user", + policy_document=json.dumps( + { + "Version": "2012-10-17", + "Statement": [{"Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*"}], + } + ), + ) + print("Created AWS role") + + # Generate AWS credentials + aws_creds = client.secrets.aws.generate_credentials(name="test-role") + print("\nGenerated AWS credentials:") + print(json.dumps(aws_creds["data"], indent=2)) + + # List enabled secrets engines + print("\nEnabled secrets engines:") + for path, engine in client.sys.list_mounted_secrets_engines()["data"].items(): + print(f"Path: {path}, Type: {engine['type']}") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/modules/weaviate.md b/docs/modules/weaviate.md new file mode 100644 index 000000000..83ae7e22f --- /dev/null +++ b/docs/modules/weaviate.md @@ -0,0 +1,23 @@ +# Weaviate + +Since testcontainers-python :material-tag: v4.6.0 + +## Introduction + +The Testcontainers module for Weaviate. + +## Adding this module to your project dependencies + +Please run the following command to add the Weaviate module to your python dependencies: + +```bash +pip install testcontainers[weaviate] weaviate-client +``` + +## Usage example + + + +[Creating a Weaviate container](weaviate_example.py) + + diff --git a/docs/modules/weaviate_example.py b/docs/modules/weaviate_example.py new file mode 100644 index 000000000..0feb6da0b --- /dev/null +++ b/docs/modules/weaviate_example.py @@ -0,0 +1,143 @@ +import json +from datetime import datetime + +import weaviate + +from testcontainers.community.weaviate import WeaviateContainer + + +def basic_example(): + with WeaviateContainer() as weaviate_container: + # Get connection parameters + host = weaviate_container.get_container_host_ip() + port = weaviate_container.get_exposed_port(weaviate_container.port) + + # Create Weaviate client + client = weaviate.Client( + url=f"http://{host}:{port}", auth_client_secret=weaviate.AuthApiKey(api_key=weaviate_container.api_key) + ) + print("Connected to Weaviate") + + # Create schema + schema = { + "classes": [ + { + "class": "Article", + "description": "A class for news articles", + "vectorizer": "text2vec-transformers", + "properties": [ + {"name": "title", "dataType": ["text"], "description": "The title of the article"}, + {"name": "content", "dataType": ["text"], "description": "The content of the article"}, + {"name": "category", "dataType": ["text"], "description": "The category of the article"}, + {"name": "publishedAt", "dataType": ["date"], "description": "When the article was published"}, + ], + } + ] + } + + client.schema.create(schema) + print("Created schema") + + # Add objects + articles = [ + { + "title": "AI Breakthrough in Natural Language Processing", + "content": "Researchers have made significant progress in understanding and generating human language.", + "category": "Technology", + "publishedAt": datetime.utcnow().isoformat(), + }, + { + "title": "New Study Shows Benefits of Exercise", + "content": "Regular physical activity has been linked to improved mental health and longevity.", + "category": "Health", + "publishedAt": datetime.utcnow().isoformat(), + }, + { + "title": "Global Climate Summit Reaches Agreement", + "content": "World leaders have agreed on new measures to combat climate change.", + "category": "Environment", + "publishedAt": datetime.utcnow().isoformat(), + }, + ] + + for article in articles: + client.data_object.create(data_object=article, class_name="Article") + print("Added test articles") + + # Query objects + result = client.query.get("Article", ["title", "category", "publishedAt"]).do() + print("\nAll articles:") + print(json.dumps(result, indent=2)) + + # Semantic search + semantic_result = ( + client.query.get("Article", ["title", "content", "category"]) + .with_near_text({"concepts": ["artificial intelligence"]}) + .with_limit(2) + .do() + ) + print("\nSemantic search results:") + print(json.dumps(semantic_result, indent=2)) + + # Filtered search + filtered_result = ( + client.query.get("Article", ["title", "category"]) + .with_where({"path": ["category"], "operator": "Equal", "valueText": "Technology"}) + .do() + ) + print("\nFiltered search results:") + print(json.dumps(filtered_result, indent=2)) + + # Create cross-reference + cross_ref_schema = { + "classes": [ + { + "class": "Author", + "description": "A class for article authors", + "vectorizer": "text2vec-transformers", + "properties": [ + {"name": "name", "dataType": ["text"], "description": "The name of the author"}, + {"name": "writes", "dataType": ["Article"], "description": "Articles written by the author"}, + ], + } + ] + } + + client.schema.create(cross_ref_schema) + print("\nCreated cross-reference schema") + + # Add author with cross-reference + author_uuid = client.data_object.create(data_object={"name": "John Doe"}, class_name="Author") + + article_uuid = result["data"]["Get"]["Article"][0]["_additional"]["id"] + client.data_object.reference.add( + from_uuid=author_uuid, + from_property_name="writes", + to_uuid=article_uuid, + from_class_name="Author", + to_class_name="Article", + ) + print("Added author with cross-reference") + + # Query with cross-reference + cross_ref_result = ( + client.query.get("Author", ["name"]) + .with_additional(["id"]) + .with_references({"writes": {"properties": ["title", "category"]}}) + .do() + ) + print("\nCross-reference query results:") + print(json.dumps(cross_ref_result, indent=2)) + + # Create aggregation + agg_result = client.query.aggregate("Article").with_fields("category").with_meta_count().do() + print("\nAggregation results:") + print(json.dumps(agg_result, indent=2)) + + # Clean up + client.schema.delete_all() + print("\nCleaned up schema") + + +if __name__ == "__main__": + basic_example() diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 000000000..aaad2fc32 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,92 @@ +_Testcontainers for Python_ integrates seamlessly with Python testing frameworks like [pytest](https://docs.pytest.org/en/stable/). + +It's ideal for integration and end-to-end tests, allowing you to easily manage dependencies using Docker. + +## 1. System requirements + +Before you begin, review the [system requirements](system_requirements/index.md). + +## 2. Install _Testcontainers for Python_ + +Install testcontainers-python with pip: + +```bash +pip install testcontainers +``` + +## 3. Spin up Redis + +```python +import pytest +from testcontainers.community.redis import RedisContainer +import redis + +def test_with_redis(): + with RedisContainer() as redis_container: + # Get connection parameters + host = redis_container.get_container_host_ip() + port = redis_container.get_exposed_port(redis_container.port) + + # Create Redis client + client = redis.Redis(host=host, port=port, decode_responses=True) + + # Test Redis connection + client.set("test_key", "Hello, Redis!") + value = client.get("test_key") + assert value == "Hello, Redis!" +``` + +The `RedisContainer` class makes it easy to start a Redis container for testing: + +- The container starts automatically when entering the context manager (`with` statement). +- It stops and removes itself when exiting the context. +- `get_container_host_ip()` returns the host IP. +- `get_exposed_port()` returns the mapped host port. + +When using `get_exposed_port()`, think of it as running `docker run -p `. `dockerd` maps the container's internal port to a random available port on your host. + +In the example above, the default Redis port (6379) is exposed for TCP traffic. This setup allows your code to connect to Redis outside the container and supports parallel test execution. Each test gets its own Redis container on a unique, random port. + +The context manager (`with` statement) ensures containers are cleaned up after tests, so no containers are left running. + +!!!tip + + See [the garbage collector](features/garbage_collector.md) for another way to clean up resources. + +## 4. Connect your code to the container + +Typically, Python applications use the [redis-py](https://github.com/redis/redis-py) client. The following code retrieves the endpoint from the container and configures the client. + +```python +def test_redis_operations(): + with RedisContainer() as redis_container: + # Get connection parameters + host = redis_container.get_container_host_ip() + port = redis_container.get_exposed_port(redis_container.port) + + # Create Redis client + client = redis.Redis(host=host, port=port, decode_responses=True) + + # Test various Redis operations + # String operations + client.set("greeting", "Hello, Redis!") + value = client.get("greeting") + assert value == "Hello, Redis!" + + # List operations + client.lpush("tasks", "task1", "task2", "task3") + tasks = client.lrange("tasks", 0, -1) + assert tasks == ["task3", "task2", "task1"] +``` + +## 5. Run the test + +You can run the test via `pytest`: + +```bash +pytest test_redis.py +``` + +## 6. Want to go deeper with Redis? + +You can find a more elaborated Redis example in our [examples section](./modules/redis.md). diff --git a/docs/system_requirements/docker.md b/docs/system_requirements/docker.md new file mode 100644 index 000000000..cc12beb2e --- /dev/null +++ b/docs/system_requirements/docker.md @@ -0,0 +1,51 @@ +# Supported Docker environments + +## Overview + +Testcontainers requires a Docker-API compatible container runtime. +During development, Testcontainers is actively tested against recent versions of Docker on Linux, as well as against Docker Desktop on Mac and Windows. +These Docker environments are automatically detected and used by Testcontainers without any additional configuration being necessary. + +It is possible to configure Testcontainers to work with alternative container runtimes (see further down for specific runtimes, or [Docker host detection](../features/configuration.md#docker-host-detection) for general configuration mechanisms). +Alternative container runtimes are not actively tested in the main development workflow, so not all Testcontainers features might be available and additional manual configuration might be necessary. + +If you have further questions about configuration details for your setup or whether it supports running Testcontainers-based tests, +please contact the Testcontainers team and other users from the Testcontainers community on [Slack](https://slack.testcontainers.org/). + +## Podman + +In order to run testcontainers against [Podman](https://podman.io/), the env var below should be set. + +Testcontainers auto-detects Podman from the daemon's `version` response and adapts a few behaviors (compose binary selection, port-binding parsing) accordingly. + +**Linux (rootless):** + +```bash +systemctl --user start podman.socket +export DOCKER_HOST="unix://${XDG_RUNTIME_DIR}/podman/podman.sock" +``` + +**Linux (rootful):** + +```bash +sudo systemctl start podman.socket +export DOCKER_HOST="unix:///run/podman/podman.sock" +``` + +**macOS:** + +```bash +export DOCKER_HOST="unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')" +``` + +You can also persist this in `~/.testcontainers.properties` as `docker.host=...`, or use a Docker context (`docker context use my-podman`). + +### Docker Compose with Podman + +`DockerCompose` prefers `docker` if it is on `PATH` (e.g. via the `podman-docker` shim). Otherwise, when Podman is detected, it falls back to the `podman` binary. You can always override the binary explicitly: + +```python +DockerCompose(".", docker_command_path="podman") +``` + +Note that Podman does not support host port ranges (`published: "5000-5999"`) in compose files. diff --git a/docs/system_requirements/index.md b/docs/system_requirements/index.md new file mode 100644 index 000000000..9a37a1d9c --- /dev/null +++ b/docs/system_requirements/index.md @@ -0,0 +1,183 @@ +# Python versions + +The library supports Python >= 3.9 + +## Updating your Python version + +There are several common approaches for managing and isolating your Python environment when using Testcontainers (or any Python project). Each has its own trade-offs in terms of reproducibility, ease of use, and integration with tooling: + +### venv (built-in virtual environments) + +#### What it is + +Python’s built-in way to create lightweight environments. + +#### How to use + +```bash +python3 -m venv .venv # create an env in “.venv” +source .venv/bin/activate # on Unix/macOS +.venv\Scripts\activate # on Windows +pip install -r requirements.txt +``` + +| Pros | Cons | +| ----------------------------------------- | -------------------------------------------------- | +| No extra dependencies | You still manage `requirements.txt` by hand | +| Very lightweight | Doesn’t provide lockfiles or dependency resolution | +| Works everywhere Python 3.3+ is installed | | + +### virtualenv (stand-alone) + +#### What it is + +A more mature alternative to venv, sometimes faster and with more features. + +#### How to use + +```bash +pip install virtualenv +virtualenv .env +source .env/bin/activate +pip install -r requirements.txt +``` + +| Pros | Cons | +| --------------------------------------------------------------------------- | ---------------------------------------------------- | +| Slightly more flexible than `venv` (e.g. can target different interpreters) | Still manual management of versions and dependencies | + +### pipenv + +#### What it is + +A higher-level tool combining environment creation with Pipfile dependency management. + +#### How to use + +```bash +pip install pipenv +pipenv install --dev testcontainers +pipenv shell +``` + +Dependencies live in Pipfile; exact versions locked in Pipfile.lock. + +| Pros | Cons | +| ----------------------------------------- | --------------------------------------------------- | +| Automatic creation of a virtualenv | Can be slower, historically some performance quirks | +| Lockfile for reproducible installs | | +| `pipenv run …` to avoid activating shells | | + +### poetry + +#### What it is + +A modern dependency manager and packaging tool, with built-in virtualenv support. + +#### How to use + +```bash +curl -sSL https://install.python-poetry.org | python3 - +poetry init # walk you through pyproject.toml creation +poetry add --dev testcontainers +poetry shell +``` + +Your Python version constraints and dependencies are in pyproject.toml; lockfile is poetry.lock. + +| Pros | Cons | +| --------------------------------------------------- | ----------------------------------------------------- | +| Elegant TOML-based config | A bit of a learning curve if you’re used to plain Pip | +| Creates truly reproducible environments | | +| Supports publishing packages to PyPI out of the box | | + +### conda / mamba + +#### What it is + +Cross-language environment and package manager (Python/R/C++). + +#### How to use + +```bash +conda create -n tc-env python=3.10 +conda activate tc-env +conda install pip +pip install testcontainers +``` + +Or with Mamba for faster solves: + +```bash +mamba install pip +mamba install -c conda-forge testcontainers +``` + +| Pros | Cons | +| --------------------------------------------------------------- | --------------------------- | +| Manages non-Python dependencies easily (e.g., system libraries) | Larger disk footprint | +| Reproducible YAML environment files (`environment.yml`) | Less “pure” Python workflow | + +### Docker-based environments + +#### What it is + +Run your tests inside a Docker image, so everything (even Python itself) is containerized. + +#### How to use + +```bash +FROM python:3.10-slim +WORKDIR /app +COPY pyproject.toml poetry.lock ./ +RUN pip install poetry && poetry install --no-root +COPY . . +CMD ["pytest", "--maxfail=1", "--disable-warnings", "-q"] +``` + +| Pros | Cons | +| ---------------------------------------------------- | --------------------------------------------------- | +| True isolation from host machine (including OS libs) | Slower startup/testing cycle | +| Easy to share exact environment via Dockerfile | Extra complexity if you’re not already Docker-savvy | + +### tox for multi-env testing + +#### What it is + +A tool to automate testing across multiple Python versions/environments. + +#### How to use + +```bash +# tox.ini + +[tox] +envlist = py39,py310,py311 + +[testenv] +deps = pytest +testcontainers +commands = pytest +``` + +| Pros | Cons | +| --------------------------------------------------------- | ---------------------------- | +| Ensures compatibility across multiple Python interpreters | Adds another layer of config | +| Isolates each test run in its own venv | | + +## Choosing the Right Tool + +| Tool | Lockfile? | Built-in Env | Cross-Platform | Non-Python Deps | Reproducibility | +| ------------ | --------- | ------------ | -------------- | --------------- | --------------- | +| `venv` | No | Yes | Yes | No | Low | +| `virtualenv` | No | Yes | Yes | No | Low | +| `pipenv` | Yes | Yes | Yes | No | Medium | +| `poetry` | Yes | Yes | Yes | No | High | +| `conda` | Yes (YML) | Yes | Yes | Yes | High | +| Docker | – | Container | Yes | Yes | Very High | + +## Next Steps + +With any of these, once your environment is set up you can simply `pip install testcontainers` (or use Poetry’s `poetry add --dev testcontainers`) and begin writing your container-backed tests in Python. + +See [Supported Docker environments](docker.md) to continue diff --git a/docs/testcontainers-logo.svg b/docs/testcontainers-logo.svg new file mode 100644 index 000000000..cc5fb6188 --- /dev/null +++ b/docs/testcontainers-logo.svg @@ -0,0 +1,22 @@ + + + Testcontainers + + + + + + + + + + + + + + + + + + + diff --git a/docs/theme/main.html b/docs/theme/main.html new file mode 100644 index 000000000..1c0823892 --- /dev/null +++ b/docs/theme/main.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block analytics %} + +{% endblock %} + +{% block extrahead %} + + +{% endblock %} diff --git a/docs/theme/partials/header.html b/docs/theme/partials/header.html new file mode 100644 index 000000000..2c59cbb3d --- /dev/null +++ b/docs/theme/partials/header.html @@ -0,0 +1,140 @@ + + + +{% set class = "md-header" %} {% if "navigation.tabs.sticky" in features %} {% +set class = class ~ " md-header--shadow md-header--lifted" %} {% elif +"navigation.tabs" not in features %} {% set class = class ~ " md-header--shadow" +%} {% endif %} {% include "partials/tc-header.html" %} + + +
+ + + + {% if "navigation.tabs.sticky" in features %} {% if "navigation.tabs" in + features %} {% include "partials/tabs.html" %} {% endif %} {% endif %} +
diff --git a/docs/theme/partials/nav.html b/docs/theme/partials/nav.html new file mode 100644 index 000000000..90dcdc2ef --- /dev/null +++ b/docs/theme/partials/nav.html @@ -0,0 +1,79 @@ + + + +{% import "partials/nav-item.html" as item with context %} +{% set class = "md-nav md-nav--primary" %} +{% if "navigation.tabs" in features %} +{% set class = class ~ " md-nav--lifted" %} +{% endif %} +{% if "toc.integrate" in features %} +{% set class = class ~ " md-nav--integrated" %} +{% endif %} + + + diff --git a/docs/theme/partials/tc-header.html b/docs/theme/partials/tc-header.html new file mode 100644 index 000000000..246e9ff52 --- /dev/null +++ b/docs/theme/partials/tc-header.html @@ -0,0 +1,157 @@ +{% set header = ({ + "siteUrl": "https://testcontainers.com/", + "menuItems": [ + { + "label": "Desktop NEW", + "url": "https://testcontainers.com/desktop/" + }, + { + "label": "Cloud", + "url": "https://testcontainers.com/cloud/" + }, + { + "label": "Getting Started", + "url": "https://testcontainers.com/getting-started/" + }, + { + "label": "Guides", + "url": "https://testcontainers.com/guides/" + }, + { + "label": "Modules", + "url": "https://testcontainers.com/modules/" + }, + { + "label": "Docs", + "children": [ + { + "label": "Testcontainers for Java", + "url": "https://java.testcontainers.org/", + "image": "/language-logos/java.svg", + }, + { + "label": "Testcontainers for Go", + "url": "https://golang.testcontainers.org/", + "image": "/language-logos/go.svg", + }, + { + "label": "Testcontainers for .NET", + "url": "https://dotnet.testcontainers.org/", + "image": "/language-logos/dotnet.svg", + }, + { + "label": "Testcontainers for Node.js", + "url": "https://node.testcontainers.org/", + "image": "/language-logos/nodejs.svg", + }, + { + "label": "Testcontainers for Python", + "url": "https://testcontainers-python.readthedocs.io/en/latest/", + "image": "/language-logos/python.svg", + "external": true, + }, + { + "label": "Testcontainers for Rust", + "url": "https://docs.rs/testcontainers/latest/testcontainers/", + "image": "/language-logos/rust.svg", + "external": true, + }, + { + "label": "Testcontainers for Haskell", + "url": "https://github.com/testcontainers/testcontainers-hs", + "image": "/language-logos/haskell.svg", + "external": true, + }, + { + "label": "Testcontainers for Ruby", + "url": "https://github.com/testcontainers/testcontainers-ruby", + "image": "/language-logos/ruby.svg", + "external": true, + }, + ] + }, + { + "label": "Slack", + "url": "https://slack.testcontainers.org/", + "icon": "icon-slack", + }, + { + "label": "GitHub", + "url": "https://github.com/testcontainers", + "icon": "icon-github", + }, + ] +}) %} + + + + + + + + + + + diff --git a/doctests/conf.py b/doctests/conf.py index 0822df226..c71b119d7 100644 --- a/doctests/conf.py +++ b/doctests/conf.py @@ -1,5 +1,12 @@ +from pathlib import Path + +BASE_DIR = Path(__file__).parent.parent.resolve() +TEST_DIR = BASE_DIR / "tests" + extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", ] master_doc = "README" + +doctest_global_setup = f"TEST_DIR = '{TEST_DIR}'" diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..761670cd2 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,108 @@ +site_name: Testcontainers for Python +site_url: https://python.testcontainers.org +plugins: + - search + - codeinclude + - include-markdown + - markdownextradata +theme: + name: material + custom_dir: docs/theme + palette: + scheme: testcontainers + font: + text: Roboto + code: Roboto Mono + logo: logo.svg + favicon: favicon.ico +extra_css: + - "css/extra.css" + - "css/tc-header.css" +repo_name: "testcontainers-python" +repo_url: "https://github.com/testcontainers/testcontainers-python" +markdown_extensions: + - admonition + - codehilite: + linenums: false + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.snippets + - toc: + permalink: true + - attr_list + - pymdownx.emoji: + emoji_generator: !!python/name:material.extensions.emoji.to_svg + emoji_index: !!python/name:material.extensions.emoji.twemoji +nav: + - Home: index.md + - Quickstart: quickstart.md + - Features: + - Creating Containers: features/creating_container.md + - Configuration: features/configuration.md + - Authentication: features/authentication.md + - Executing Commands: features/executing_commands.md + - Container Logs: features/container_logs.md + - Building Images: features/building_images.md + - Copying Data: features/copying_data.md + - Wait Strategies: features/wait_strategies.md + - Docker Compose: features/docker_compose.md + - Networking: features/networking.md + - Garbage Collector: features/garbage_collector.md + - Advanced Features: features/advanced_features.md + - Modules: + - Databases: + - modules/arangodb.md + - modules/cassandra.md + - modules/chroma.md + - modules/clickhouse.md + - modules/cockroachdb.md + - modules/cosmosdb.md + - modules/cratedb.md + - modules/db2.md + - modules/elasticsearch.md + - modules/influxdb.md + - modules/mongodb.md + - modules/mssql.md + - modules/mysql.md + - modules/neo4j.md + - modules/opensearch.md + - modules/oracle-free.md + - modules/postgres.md + - modules/qdrant.md + - modules/redis.md + - modules/scylla.md + - modules/trino.md + - modules/valkey.md + - modules/weaviate.md + - modules/aws.md + - modules/azurite.md + - modules/generic.md + - modules/google.md + - modules/k3s.md + - modules/keycloak.md + - modules/kafka.md + - modules/localstack.md + - modules/mailpit.md + - modules/memcached.md + - modules/milvus.md + - modules/minio.md + - modules/mqtt.md + - modules/nats.md + - modules/nginx.md + - modules/ollama.md + - modules/rabbitmq.md + - modules/registry.md + - modules/selenium.md + - modules/sftp.md + - modules/test_module_import.md + - modules/toxiproxy.md + - modules/vault.md + - System Requirements: + - system_requirements/index.md + - system_requirements/docker.md + - Contributing: contributing.md + - Getting Help: getting_help.md +edit_uri: edit/main/docs/ +extra: + latest_version: 4.10.0 diff --git a/modules/arangodb/README.rst b/modules/arangodb/README.rst deleted file mode 100644 index d1a6cd255..000000000 --- a/modules/arangodb/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.arangodb.ArangoDbContainer -.. title:: testcontainers.arangodb.ArangoDbContainer diff --git a/modules/azurite/README.rst b/modules/azurite/README.rst deleted file mode 100644 index b6cf724c9..000000000 --- a/modules/azurite/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.azurite.AzuriteContainer -.. title:: testcontainers.azurite.AzuriteContainer diff --git a/modules/azurite/testcontainers/azurite/__init__.py b/modules/azurite/testcontainers/azurite/__init__.py deleted file mode 100644 index 969fcf35d..000000000 --- a/modules/azurite/testcontainers/azurite/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import os -import socket -from typing import Optional - -from testcontainers.core.container import DockerContainer -from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready - - -class AzuriteContainer(DockerContainer): - """ - The example below spins up an Azurite container and - shows an example to create a Blob service client with the container. The method - :code:`get_connection_string` can be used to create a client for Blob service, Queue service - and Table service. - - Example: - - .. doctest:: - - >>> from testcontainers.azurite import AzuriteContainer - >>> from azure.storage.blob import BlobServiceClient - - >>> with AzuriteContainer() as azurite_container: - ... connection_string = azurite_container.get_connection_string() - ... client = BlobServiceClient.from_connection_string( - ... connection_string, - ... api_version="2019-12-12" - ... ) - """ - - def __init__( - self, - image: str = "mcr.microsoft.com/azure-storage/azurite:latest", - *, - blob_service_port: int = 10_000, - queue_service_port: int = 10_001, - table_service_port: int = 10_002, - account_name: Optional[str] = None, - account_key: Optional[str] = None, - **kwargs, - ) -> None: - """Constructs an AzuriteContainer. - - Args: - image: Expects an image with tag. - **kwargs: Keyword arguments passed to super class. - """ - super().__init__(image=image, **kwargs) - self.account_name = account_name or os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") - self.account_key = account_key or os.environ.get( - "AZURITE_ACCOUNT_KEY", - "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/" "K1SZFPTOtr/KBHBeksoGMGw==", - ) - - raise_for_deprecated_parameter(kwargs, "ports_to_expose", "container.with_exposed_ports") - self.blob_service_port = blob_service_port - self.queue_service_port = queue_service_port - self.table_service_port = table_service_port - - self.with_exposed_ports(blob_service_port, queue_service_port, table_service_port) - self.with_env("AZURITE_ACCOUNTS", f"{self.account_name}:{self.account_key}") - - def get_connection_string(self) -> str: - host_ip = self.get_container_host_ip() - connection_string = ( - f"DefaultEndpointsProtocol=http;" f"AccountName={self.account_name};" f"AccountKey={self.account_key};" - ) - - if self.blob_service_port in self.ports: - connection_string += ( - f"BlobEndpoint=http://{host_ip}:" - f"{self.get_exposed_port(self.blob_service_port)}" - f"/{self.account_name};" - ) - - if self.queue_service_port in self.ports: - connection_string += ( - f"QueueEndpoint=http://{host_ip}:" - f"{self.get_exposed_port(self.queue_service_port)}" - f"/{self.account_name};" - ) - - if self.table_service_port in self.ports: - connection_string += ( - f"TableEndpoint=http://{host_ip}:" - f"{self.get_exposed_port(self.table_service_port)}" - f"/{self.account_name};" - ) - - return connection_string - - def start(self) -> "AzuriteContainer": - super().start() - self._connect() - return self - - @wait_container_is_ready(OSError) - def _connect(self) -> None: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((self.get_container_host_ip(), int(self.get_exposed_port(next(iter(self.ports)))))) diff --git a/modules/azurite/tests/test_azurite.py b/modules/azurite/tests/test_azurite.py deleted file mode 100644 index 74230ab14..000000000 --- a/modules/azurite/tests/test_azurite.py +++ /dev/null @@ -1,12 +0,0 @@ -from azure.storage.blob import BlobServiceClient - -from testcontainers.azurite import AzuriteContainer - - -def test_docker_run_azurite(): - with AzuriteContainer() as azurite_container: - blob_service_client = BlobServiceClient.from_connection_string( - azurite_container.get_connection_string(), api_version="2019-12-12" - ) - - blob_service_client.create_container("test-container") diff --git a/modules/cassandra/README.rst b/modules/cassandra/README.rst deleted file mode 100644 index 44216d6be..000000000 --- a/modules/cassandra/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.cassandra.CassandraContainer -.. title:: testcontainers.cassandra.CassandraContainer diff --git a/modules/cassandra/tests/test_cassandra.py b/modules/cassandra/tests/test_cassandra.py deleted file mode 100644 index 1aa5858b7..000000000 --- a/modules/cassandra/tests/test_cassandra.py +++ /dev/null @@ -1,14 +0,0 @@ -from cassandra.cluster import Cluster, DCAwareRoundRobinPolicy - -from testcontainers.cassandra import CassandraContainer - - -def test_docker_run_cassandra(): - with CassandraContainer("cassandra:4.1.4") as cassandra: - cluster = Cluster( - cassandra.get_contact_points(), - load_balancing_policy=DCAwareRoundRobinPolicy(cassandra.get_local_datacenter()), - ) - session = cluster.connect() - result = session.execute("SELECT release_version FROM system.local;") - assert result.one().release_version == "4.1.4" diff --git a/modules/chroma/README.rst b/modules/chroma/README.rst deleted file mode 100644 index f4e3199fe..000000000 --- a/modules/chroma/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.chroma.ChromaContainer -.. title:: testcontainers.minio.ChromaContainer diff --git a/modules/clickhouse/README.rst b/modules/clickhouse/README.rst deleted file mode 100644 index 0835d4c04..000000000 --- a/modules/clickhouse/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.clickhouse.ClickHouseContainer -.. title:: testcontainers.clickhouse.ClickHouseContainer diff --git a/modules/cockroachdb/README.rst b/modules/cockroachdb/README.rst deleted file mode 100644 index 7b53fc336..000000000 --- a/modules/cockroachdb/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.cockroachdb.CockroachDBContainer -.. title:: testcontainers.cockroachdb.CockroachDBContainer diff --git a/modules/cosmosdb/README.rst b/modules/cosmosdb/README.rst deleted file mode 100644 index 802cffa4e..000000000 --- a/modules/cosmosdb/README.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. autoclass:: testcontainers.cosmosdb.CosmosDBMongoEndpointContainer -.. title:: testcontainers.cosmosdb.CosmosDBMongoEndpointContainer - -.. autoclass:: testcontainers.cosmosdb.CosmosDBNoSQLEndpointContainer -.. title:: testcontainers.cosmosdb.CosmosDBNoSQLEndpointContainer diff --git a/modules/db2/README.rst b/modules/db2/README.rst deleted file mode 100644 index 1afd1f6d7..000000000 --- a/modules/db2/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.db2.Db2Container -.. title:: testcontainers.db2.Db2Container diff --git a/modules/elasticsearch/README.rst b/modules/elasticsearch/README.rst deleted file mode 100644 index c2ce1930f..000000000 --- a/modules/elasticsearch/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.elasticsearch.ElasticSearchContainer -.. title:: testcontainers.elasticsearch.ElasticSearchContainer diff --git a/modules/generic/README.rst b/modules/generic/README.rst deleted file mode 100644 index 4497ec922..000000000 --- a/modules/generic/README.rst +++ /dev/null @@ -1,52 +0,0 @@ -:code:`testcontainers-generic` is a set of generic containers modules that can be used to creat containers. - -.. autoclass:: testcontainers.generic.ServerContainer -.. title:: testcontainers.generic.ServerContainer - -FastAPI container that is using :code:`ServerContainer` - -.. doctest:: - - >>> from testcontainers.generic import ServerContainer - >>> from testcontainers.core.waiting_utils import wait_for_logs - - >>> with DockerImage(path="./modules/generic/tests/samples/fastapi", tag="fastapi-test:latest") as image: - ... with ServerContainer(port=80, image=image) as fastapi_server: - ... delay = wait_for_logs(fastapi_server, "Uvicorn running on http://0.0.0.0:80") - ... fastapi_server.get_api_url = lambda: fastapi_server._create_connection_url() + "/api/v1/" - ... client = fastapi_server.get_client() - ... response = client.get("/") - ... assert response.status_code == 200 - ... assert response.json() == {"Status": "Working"} - -A more advance use-case, where we are using a FastAPI container that is using Redis container: - -.. doctest:: - - >>> from testcontainers.redis import RedisContainer - >>> from testcontainers.generic import ServerContainer - - >>> with RedisContainer() as redis: - ... redis_container_port = redis.port - ... redis_container_ip_address = redis.get_docker_client().bridge_ip(redis._container.id) - - ... with DockerImage(path="./modules/generic/tests/samples/advance_1", tag="advance-1:latest") as image: - ... web_server = ServerContainer(port=80, image=image) - ... web_server.with_env(key="REDIS_HOST", value=redis_container_ip_address) - ... web_server.with_env(key="REDIS_PORT", value=redis_container_port) - - ... with web_server: - ... web_server.get_api_url = lambda: web_server._create_connection_url() - ... client = web_server.get_client() - - ... response = client.get("/") - ... assert response.status_code == 200, "Server request failed" - ... assert response.json() == {"Status": "ok"} - - ... test_data = {"key": "test_key", "value": "test_value"} - ... response = client.post("/set", params=test_data) - ... assert response.status_code == 200, "Failed to set data" - - ... response = client.get(f"/get/{test_data['key']}") - ... assert response.status_code == 200, "Failed to get data" - ... assert response.json() == {"key": test_data["key"], "value": test_data["value"]} diff --git a/modules/google/README.rst b/modules/google/README.rst deleted file mode 100644 index 903c3c4a1..000000000 --- a/modules/google/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -.. autoclass:: testcontainers.google.DatastoreContainer -.. title:: testcontainers.google.DatastoreContainer -.. autoclass:: testcontainers.google.PubSubContainer -.. title:: testcontainers.google.PubSubContainer diff --git a/modules/index.rst b/modules/index.rst deleted file mode 100644 index d2a67a3d4..000000000 --- a/modules/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -Community Modules -================= - -.. - glob: - https://stackoverflow.com/a/44572883/4971476 - -.. toctree:: - :glob: - - */README diff --git a/modules/influxdb/README.rst b/modules/influxdb/README.rst deleted file mode 100644 index 8424ebea3..000000000 --- a/modules/influxdb/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.influxdb.InfluxDbContainer -.. title:: testcontainers.influxdb.InfluxDbContainer diff --git a/modules/k3s/README.rst b/modules/k3s/README.rst deleted file mode 100644 index 11f7adc05..000000000 --- a/modules/k3s/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.k3s.K3SContainer -.. title:: testcontainers.k3s.K3SContainer diff --git a/modules/kafka/README.rst b/modules/kafka/README.rst deleted file mode 100644 index a54107a02..000000000 --- a/modules/kafka/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. autoclass:: testcontainers.kafka.KafkaContainer -.. title:: testcontainers.kafka.KafkaContainer -.. autoclass:: testcontainers.kafka.RedpandaContainer diff --git a/modules/keycloak/README.rst b/modules/keycloak/README.rst deleted file mode 100644 index 6eb045f4a..000000000 --- a/modules/keycloak/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.keycloak.KeycloakContainer -.. title:: testcontainers.keycloak.KeycloakContainer diff --git a/modules/keycloak/tests/test_keycloak.py b/modules/keycloak/tests/test_keycloak.py deleted file mode 100644 index 6bf003b74..000000000 --- a/modules/keycloak/tests/test_keycloak.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest -from testcontainers.keycloak import KeycloakContainer - - -@pytest.mark.parametrize("image_version", ["25.0", "24.0.1", "18.0"]) -def test_docker_run_keycloak(image_version: str): - with KeycloakContainer(f"quay.io/keycloak/keycloak:{image_version}") as keycloak_admin: - assert keycloak_admin.get_client().users_count() == 1 diff --git a/modules/localstack/README.rst b/modules/localstack/README.rst deleted file mode 100644 index 66cbf4d3c..000000000 --- a/modules/localstack/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.localstack.LocalStackContainer -.. title:: testcontainers.localstack.LocalStackContainer diff --git a/modules/mailpit/README.rst b/modules/mailpit/README.rst deleted file mode 100644 index f2c238a37..000000000 --- a/modules/mailpit/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. autoclass:: testcontainers.mailpit.MailpitUser -.. autoclass:: testcontainers.mailpit.MailpitContainer -.. title:: testcontainers.mailpit.MailpitContainer diff --git a/modules/memcached/README.rst b/modules/memcached/README.rst deleted file mode 100644 index 83cd9c82e..000000000 --- a/modules/memcached/README.rst +++ /dev/null @@ -1 +0,0 @@ -.. autoclass:: testcontainers.memcached.MemcachedContainer diff --git a/modules/memcached/setup.py b/modules/memcached/setup.py deleted file mode 100644 index 0f4dd3234..000000000 --- a/modules/memcached/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import find_namespace_packages, setup - -description = "Memcached component of testcontainers-python." - -setup( - name="testcontainers-memcached", - version="0.0.1rc1", - packages=find_namespace_packages(), - description=description, - long_description=description, - long_description_content_type="text/x-rst", - url="https://github.com/testcontainers/testcontainers-python", - install_requires=[ - "testcontainers-core", - ], - python_requires=">=3.7", -) diff --git a/modules/milvus/README.rst b/modules/milvus/README.rst deleted file mode 100644 index f823d7fe9..000000000 --- a/modules/milvus/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.milvus.MilvusContainer -.. title:: testcontainers.milvus.MilvusContainer diff --git a/modules/milvus/testcontainers/milvus/__init__.py b/modules/milvus/testcontainers/milvus/__init__.py deleted file mode 100644 index 39a1403e9..000000000 --- a/modules/milvus/testcontainers/milvus/__init__.py +++ /dev/null @@ -1,85 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import requests - -from testcontainers.core.config import testcontainers_config as c -from testcontainers.core.generic import DockerContainer -from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs - - -class MilvusContainer(DockerContainer): - """ - Milvus database container. - - Read mode about Milvus: https://milvus.io/docs - - Example: - - The example spins up a Milvus database and connects to it client using MilvisClient. - - .. doctest:: - - >>> from testcontainers.milvus import MilvusContainer - >>> with MilvusContainer("milvusdb/milvus:v2.4.4") as milvus_container: - ... milvus_container.get_exposed_port(milvus_container.port) in milvus_container.get_connection_url() - True - """ - - def __init__( - self, - image: str = "milvusdb/milvus:latest", - port: int = 19530, - **kwargs, - ) -> None: - super().__init__(image=image, **kwargs) - self.port = port - self.healthcheck_port = 9091 - self.with_exposed_ports(self.port, self.healthcheck_port) - self.cmd = "milvus run standalone" - - envs = {"ETCD_USE_EMBED": "true", "ETCD_DATA_DIR": "/var/lib/milvus/etcd", "COMMON_STORAGETYPE": "local"} - - for env, value in envs.items(): - self.with_env(env, value) - - def get_connection_url(self) -> str: - ip = self.get_container_host_ip() - port = self.get_exposed_port(self.port) - return f"http://{ip}:{port}" - - @wait_container_is_ready() - def _connect(self) -> None: - msg = "Welcome to use Milvus!" - wait_for_logs(self, f".*{msg}.*", c.max_tries, c.sleep_time) - self._healthcheck() - - def _get_healthcheck_url(self) -> str: - ip = self.get_container_host_ip() - port = self.get_exposed_port(self.healthcheck_port) - return f"http://{ip}:{port}" - - @wait_container_is_ready(requests.exceptions.HTTPError) - def _healthcheck(self) -> None: - healthcheck_url = self._get_healthcheck_url() - response = requests.get(f"{healthcheck_url}/healthz", timeout=1) - response.raise_for_status() - - def start(self) -> "MilvusContainer": - """This method starts the Milvus container and runs the healthcheck - to verify that the container is ready to use.""" - self.with_command(self.cmd) - super().start() - self._connect() - self._healthcheck() - return self diff --git a/modules/minio/README.rst b/modules/minio/README.rst deleted file mode 100644 index 409151787..000000000 --- a/modules/minio/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.minio.MinioContainer -.. title:: testcontainers.minio.MinioContainer diff --git a/modules/mongodb/README.rst b/modules/mongodb/README.rst deleted file mode 100644 index 37e836406..000000000 --- a/modules/mongodb/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.mongodb.MongoDbContainer -.. title:: testcontainers.mongodb.MongoDbContainer diff --git a/modules/mongodb/testcontainers/mongodb/__init__.py b/modules/mongodb/testcontainers/mongodb/__init__.py deleted file mode 100644 index 4a436b195..000000000 --- a/modules/mongodb/testcontainers/mongodb/__init__.py +++ /dev/null @@ -1,83 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -import os -from typing import Optional - -from pymongo import MongoClient - -from testcontainers.core.generic import DbContainer -from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_for_logs - - -class MongoDbContainer(DbContainer): - """ - Mongo document-based database container. - - Example: - - .. doctest:: - - >>> from testcontainers.mongodb import MongoDbContainer - - >>> with MongoDbContainer("mongo:7.0.7") as mongo: - ... db = mongo.get_connection_client().test - ... # Insert a database entry - ... result = db.restaurants.insert_one( - ... { - ... "name": "Vella", - ... "cuisine": "Italian", - ... "restaurant_id": "123456" - ... } - ... ) - ... # Find the restaurant document - ... result = db.restaurants.find_one({"name": "Vella"}) - ... result["restaurant_id"] - '123456' - """ - - def __init__( - self, - image: str = "mongo:latest", - port: int = 27017, - username: Optional[str] = None, - password: Optional[str] = None, - dbname: Optional[str] = None, - **kwargs, - ) -> None: - raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super().__init__(image=image, **kwargs) - self.username = username if username else os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") - self.password = password if password else os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") - self.dbname = dbname if dbname else os.environ.get("MONGO_DB", "test") - self.port = port - self.with_exposed_ports(self.port) - - def _configure(self) -> None: - self.with_env("MONGO_INITDB_ROOT_USERNAME", self.username) - self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.password) - self.with_env("MONGO_DB", self.dbname) - - def get_connection_url(self) -> str: - return self._create_connection_url( - dialect="mongodb", - username=self.username, - password=self.password, - port=self.port, - ) - - def _connect(self) -> None: - wait_for_logs(self, "Waiting for connections") - - def get_connection_client(self) -> MongoClient: - return MongoClient(self.get_connection_url()) diff --git a/modules/mongodb/tests/test_mongodb.py b/modules/mongodb/tests/test_mongodb.py deleted file mode 100644 index 9bf3600f2..000000000 --- a/modules/mongodb/tests/test_mongodb.py +++ /dev/null @@ -1,53 +0,0 @@ -import pytest -from pymongo import MongoClient -from pymongo.errors import OperationFailure - -from testcontainers.mongodb import MongoDbContainer - - -@pytest.mark.parametrize("version", ["7.0.7", "6.0.14", "5.0.26"]) -def test_docker_run_mongodb(version: str): - with MongoDbContainer(f"mongo:{version}") as mongo: - db = mongo.get_connection_client().test - doc = { - "address": { - "street": "2 Avenue", - "zipcode": "10075", - "building": "1480", - "coord": [-73.9557413, 40.7720266], - }, - "borough": "Manhattan", - "cuisine": "Italian", - "name": "Vella", - "restaurant_id": "41704620", - } - result = db.restaurants.insert_one(doc) - assert result.inserted_id - - cursor = db.restaurants.find({"borough": "Manhattan"}) - assert cursor.next()["restaurant_id"] == doc["restaurant_id"] - - -# This is a feature in the generic DbContainer class -# but it can't be tested on its own -# so is tested in various database modules: -# - mysql / mariadb -# - postgresql -# - sqlserver -# - mongodb -# - db2 -def test_quoted_password(): - user = "root" - password = "p@$%25+0&%rd :/!=?" - quoted_password = "p%40%24%2525+0%26%25rd %3A%2F%21%3D%3F" - # driver = "pymongo" - kwargs = { - "username": user, - "password": password, - } - with MongoDbContainer("mongo:7.0.7", **kwargs) as container: - host = container.get_container_host_ip() - port = container.get_exposed_port(27017) - expected_url = f"mongodb://{user}:{quoted_password}@{host}:{port}" - url = container.get_connection_url() - assert url == expected_url diff --git a/modules/mqtt/README.rst b/modules/mqtt/README.rst deleted file mode 100644 index 2e088cbbb..000000000 --- a/modules/mqtt/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.mqtt.MosquittoContainer -.. title:: testcontainers.mqtt.MosquittoContainer diff --git a/modules/mssql/README.rst b/modules/mssql/README.rst deleted file mode 100644 index 56368ca71..000000000 --- a/modules/mssql/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.mssql.SqlServerContainer -.. title:: testcontainers.mssql.SqlServerContainer diff --git a/modules/mysql/README.rst b/modules/mysql/README.rst deleted file mode 100644 index d69785cd7..000000000 --- a/modules/mysql/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.mysql.MySqlContainer -.. title:: testcontainers.mysql.MySqlContainer diff --git a/modules/nats/README.rst b/modules/nats/README.rst deleted file mode 100644 index 785892939..000000000 --- a/modules/nats/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.nats.NatsContainer -.. title:: testcontainers.nats.NatsContainer diff --git a/modules/neo4j/README.rst b/modules/neo4j/README.rst deleted file mode 100644 index 885d4a598..000000000 --- a/modules/neo4j/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.neo4j.Neo4jContainer -.. title:: testcontainers.neo4j.Neo4jContainer diff --git a/modules/nginx/README.rst b/modules/nginx/README.rst deleted file mode 100644 index a949a93d1..000000000 --- a/modules/nginx/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.nginx.NginxContainer -.. title:: testcontainers.nginx.NginxContainer diff --git a/modules/ollama/README.rst b/modules/ollama/README.rst deleted file mode 100644 index dc18fe265..000000000 --- a/modules/ollama/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.ollama.OllamaContainer -.. title:: testcontainers.ollama.OllamaContainer diff --git a/modules/opensearch/README.rst b/modules/opensearch/README.rst deleted file mode 100644 index fdc450fd6..000000000 --- a/modules/opensearch/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.opensearch.OpenSearchContainer -.. title:: testcontainers.opensearch.OpenSearchContainer diff --git a/modules/oracle-free/README.rst b/modules/oracle-free/README.rst deleted file mode 100644 index bbd5d3b5d..000000000 --- a/modules/oracle-free/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.oracle.OracleDbContainer -.. title:: testcontainers.oracle.OracleDbContainer diff --git a/modules/postgres/README.rst b/modules/postgres/README.rst deleted file mode 100644 index bce939244..000000000 --- a/modules/postgres/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.postgres.PostgresContainer -.. title:: testcontainers.postgres.PostgresContainer diff --git a/modules/qdrant/README.rst b/modules/qdrant/README.rst deleted file mode 100644 index 643096a37..000000000 --- a/modules/qdrant/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.qdrant.QdrantContainer -.. title:: testcontainers.qdrant.QdrantContainer diff --git a/modules/rabbitmq/README.rst b/modules/rabbitmq/README.rst deleted file mode 100644 index 1b31362d3..000000000 --- a/modules/rabbitmq/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.rabbitmq.RabbitMqContainer -.. title:: testcontainers.rabbitmq.RabbitMqContainer diff --git a/modules/redis/README.rst b/modules/redis/README.rst deleted file mode 100644 index 1dfcf9e35..000000000 --- a/modules/redis/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.redis.RedisContainer -.. title:: testcontainers.redis.RedisContainer diff --git a/modules/scylla/README.rst b/modules/scylla/README.rst deleted file mode 100644 index fd1ea03fb..000000000 --- a/modules/scylla/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.scylla.ScyllaContainer -.. title:: testcontainers.scylla.ScyllaContainer diff --git a/modules/selenium/README.rst b/modules/selenium/README.rst deleted file mode 100644 index 5beef79cd..000000000 --- a/modules/selenium/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.selenium.BrowserWebDriverContainer -.. title:: testcontainers.selenium.BrowserWebDriverContainer diff --git a/modules/sftp/README.rst b/modules/sftp/README.rst deleted file mode 100644 index 2287d59c9..000000000 --- a/modules/sftp/README.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. autoclass:: testcontainers.sftp.SFTPContainer -.. autoclass:: testcontainers.sftp.SFTPUser -.. title:: testcontainers.sftp.SFTPContainer diff --git a/modules/test_module_import/README.rst b/modules/test_module_import/README.rst deleted file mode 100644 index 3d2e7543a..000000000 --- a/modules/test_module_import/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.test_module_import.NewSubModuleContainer -.. title:: testcontainers.test_module_import.NewSubModuleContainer diff --git a/modules/test_module_import/testcontainers/test_module_import/__init__.py b/modules/test_module_import/testcontainers/test_module_import/__init__.py deleted file mode 100644 index 74074699e..000000000 --- a/modules/test_module_import/testcontainers/test_module_import/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .new_sub_module import NewSubModuleContainer # noqa: F401 diff --git a/modules/test_module_import/testcontainers/test_module_import/new_sub_module.py b/modules/test_module_import/testcontainers/test_module_import/new_sub_module.py deleted file mode 100644 index 6f25d2777..000000000 --- a/modules/test_module_import/testcontainers/test_module_import/new_sub_module.py +++ /dev/null @@ -1,27 +0,0 @@ -from testcontainers.generic.server import ServerContainer - - -class NewSubModuleContainer(ServerContainer): - """ - This class is a mock container for testing purposes. It is used to test importing from other modules. - - .. doctest:: - - >>> import httpx - >>> from testcontainers.core.image import DockerImage - >>> from testcontainers.test_module_import import NewSubModuleContainer - - >>> with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-new-mod:latest") as image: - ... with NewSubModuleContainer(port=9000, image=image) as new_mod: - ... url = new_mod._create_connection_url() - ... response = httpx.get(f"{url}", timeout=5) - ... assert response.status_code == 200, "Response status code is not 200" - ... assert new_mod.additional_capability() == "NewSubModuleContainer" - - """ - - def __init__(self, port: int, image: str) -> None: - super().__init__(port, image) - - def additional_capability(self) -> str: - return "NewSubModuleContainer" diff --git a/modules/test_module_import/tests/test_mock_one.py b/modules/test_module_import/tests/test_mock_one.py deleted file mode 100644 index 915c95b11..000000000 --- a/modules/test_module_import/tests/test_mock_one.py +++ /dev/null @@ -1,14 +0,0 @@ -import httpx - -from testcontainers.core.waiting_utils import wait_for_logs -from testcontainers.core.image import DockerImage -from testcontainers.test_module_import import NewSubModuleContainer - - -def test_like_doctest(): - with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-new-mod:latest") as image: - with NewSubModuleContainer(port=9000, image=image) as new_mod: - url = new_mod._create_connection_url() - response = httpx.get(f"{url}", timeout=5) - assert response.status_code == 200, "Response status code is not 200" - assert new_mod.additional_capability() == "NewSubModuleContainer" diff --git a/modules/trino/README.rst b/modules/trino/README.rst deleted file mode 100644 index 95b4be930..000000000 --- a/modules/trino/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.trino.TrinoContainer -.. title:: testcontainers.trino.TrinoContainer diff --git a/modules/vault/README.rst b/modules/vault/README.rst deleted file mode 100644 index 71c079dfa..000000000 --- a/modules/vault/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.vault.VaultContainer -.. title:: testcontainers.vault.VaultContainer diff --git a/modules/weaviate/README.rst b/modules/weaviate/README.rst deleted file mode 100644 index 560934164..000000000 --- a/modules/weaviate/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -.. autoclass:: testcontainers.weaviate.WeaviateContainer -.. title:: testcontainers.weaviate.WeaviateContainer diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index bd54659e5..000000000 --- a/poetry.lock +++ /dev/null @@ -1,4681 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 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" -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 = "annotated-types" -version = "0.6.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = true -python-versions = ">=3.8" -files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, -] - -[[package]] -name = "anyio" -version = "4.3.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, - {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, -] - -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7)", "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)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "argon2-cffi" -version = "23.1.0" -description = "Argon2 for Python" -optional = true -python-versions = ">=3.7" -files = [ - {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, - {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, -] - -[package.dependencies] -argon2-cffi-bindings = "*" - -[package.extras] -dev = ["argon2-cffi[tests,typing]", "tox (>4)"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] -tests = ["hypothesis", "pytest"] -typing = ["mypy"] - -[[package]] -name = "argon2-cffi-bindings" -version = "21.2.0" -description = "Low-level CFFI bindings for Argon2" -optional = true -python-versions = ">=3.6" -files = [ - {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, - {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, -] - -[package.dependencies] -cffi = ">=1.0.1" - -[package.extras] -dev = ["cogapp", "pre-commit", "pytest", "wheel"] -tests = ["pytest"] - -[[package]] -name = "asn1crypto" -version = "1.5.1" -description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" -optional = false -python-versions = "*" -files = [ - {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, - {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, -] - -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = true -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = true -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "authlib" -version = "1.3.0" -description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." -optional = true -python-versions = ">=3.8" -files = [ - {file = "Authlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:9637e4de1fb498310a56900b3e2043a206b03cb11c05422014b0302cbc814be3"}, - {file = "Authlib-1.3.0.tar.gz", hash = "sha256:959ea62a5b7b5123c5059758296122b57cd2585ae2ed1c0622c21b371ffdae06"}, -] - -[package.dependencies] -cryptography = "*" - -[[package]] -name = "azure-core" -version = "1.30.1" -description = "Microsoft Azure Core Library for Python" -optional = true -python-versions = ">=3.7" -files = [ - {file = "azure-core-1.30.1.tar.gz", hash = "sha256:26273a254131f84269e8ea4464f3560c731f29c0c1f69ac99010845f239c1a8f"}, - {file = "azure_core-1.30.1-py3-none-any.whl", hash = "sha256:7c5ee397e48f281ec4dd773d67a0a47a0962ed6fa833036057f9ea067f688e74"}, -] - -[package.dependencies] -requests = ">=2.21.0" -six = ">=1.11.0" -typing-extensions = ">=4.6.0" - -[package.extras] -aio = ["aiohttp (>=3.0)"] - -[[package]] -name = "azure-cosmos" -version = "4.7.0" -description = "Microsoft Azure Cosmos Client Library for Python" -optional = true -python-versions = ">=3.8" -files = [ - {file = "azure-cosmos-4.7.0.tar.gz", hash = "sha256:72d714033134656302a2e8957c4b93590673bd288b0ca60cb123e348ae99a241"}, - {file = "azure_cosmos-4.7.0-py3-none-any.whl", hash = "sha256:03d8c7740ddc2906fb16e07b136acc0fe6a6a02656db46c5dd6f1b127b58cc96"}, -] - -[package.dependencies] -azure-core = ">=1.25.1" -typing-extensions = ">=4.6.0" - -[[package]] -name = "azure-storage-blob" -version = "12.19.1" -description = "Microsoft Azure Blob Storage Client Library for Python" -optional = true -python-versions = ">=3.7" -files = [ - {file = "azure-storage-blob-12.19.1.tar.gz", hash = "sha256:13e16ba42fc54ac2c7e8f976062173a5c82b9ec0594728e134aac372965a11b0"}, - {file = "azure_storage_blob-12.19.1-py3-none-any.whl", hash = "sha256:c5530dc51c21c9564e4eb706cd499befca8819b10dd89716d3fc90d747556243"}, -] - -[package.dependencies] -azure-core = ">=1.28.0,<2.0.0" -cryptography = ">=2.1.4" -isodate = ">=0.6.1" -typing-extensions = ">=4.3.0" - -[package.extras] -aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] - -[[package]] -name = "babel" -version = "2.14.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, -] - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "backoff" -version = "2.2.1" -description = "Function decoration for backoff and retry" -optional = true -python-versions = ">=3.7,<4.0" -files = [ - {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, - {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, -] - -[[package]] -name = "bcrypt" -version = "4.1.2" -description = "Modern password hashing for your software and your servers" -optional = false -python-versions = ">=3.7" -files = [ - {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, - {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, - {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, - {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, - {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, - {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, - {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - -[[package]] -name = "boto3" -version = "1.34.59" -description = "The AWS SDK for Python" -optional = true -python-versions = ">= 3.8" -files = [ - {file = "boto3-1.34.59-py3-none-any.whl", hash = "sha256:004e67b078be58d34469406f93cc8b95bc43becef4bbe44523a0b8e51f84c668"}, - {file = "boto3-1.34.59.tar.gz", hash = "sha256:162edf182e53c198137a28432a626dba103f787a8f5000ed4758b73ccd203fa0"}, -] - -[package.dependencies] -botocore = ">=1.34.59,<1.35.0" -jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.10.0,<0.11.0" - -[package.extras] -crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] - -[[package]] -name = "botocore" -version = "1.34.59" -description = "Low-level, data-driven core of boto 3." -optional = true -python-versions = ">= 3.8" -files = [ - {file = "botocore-1.34.59-py3-none-any.whl", hash = "sha256:4bc112dafb1679ab571117593f7656604726a3da0e5ae5bad00ea772fa40e75c"}, - {file = "botocore-1.34.59.tar.gz", hash = "sha256:24edb4d21d7c97dea0c6c4a80d36b3809b1443a30b0bd5e317d6c319dfac823f"}, -] - -[package.dependencies] -jmespath = ">=0.7.1,<2.0.0" -python-dateutil = ">=2.1,<3.0.0" -urllib3 = [ - {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}, - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, -] - -[package.extras] -crt = ["awscrt (==0.19.19)"] - -[[package]] -name = "cachetools" -version = "5.3.3" -description = "Extensible memoizing collections and decorators" -optional = true -python-versions = ">=3.7" -files = [ - {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, - {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, -] - -[[package]] -name = "cassandra-driver" -version = "3.29.1" -description = "DataStax Driver for Apache Cassandra" -optional = false -python-versions = "*" -files = [ - {file = "cassandra-driver-3.29.1.tar.gz", hash = "sha256:38e9c2a2f2a9664bb03f1f852d5fccaeff2163942b5db35dffcf8bf32a51cfe5"}, - {file = "cassandra_driver-3.29.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8f175c7616a63ca48cb8bd4acc443e2a3d889964d5157cead761f23cc8db7bd"}, - {file = "cassandra_driver-3.29.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7d66398952b9cd21c40edff56e22b6d3bce765edc94b207ddb5896e7bc9aa088"}, - {file = "cassandra_driver-3.29.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bbc6f575ef109ce5d4abfa2033bf36c394032abd83e32ab671159ce68e7e17b"}, - {file = "cassandra_driver-3.29.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78f241af75696adb3e470209e2fbb498804c99e2b197d24d74774eee6784f283"}, - {file = "cassandra_driver-3.29.1-cp310-cp310-win32.whl", hash = "sha256:54d9e651a742d6ca3d874ef8d06a40fa032d2dba97142da2d36f60c5675e39f8"}, - {file = "cassandra_driver-3.29.1-cp310-cp310-win_amd64.whl", hash = "sha256:630dc5423cd40eba0ee9db31065e2238098ff1a25a6b1bd36360f85738f26e4b"}, - {file = "cassandra_driver-3.29.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b841d38c96bb878d31df393954863652d6d3a85f47bcc00fd1d70a5ea73023f"}, - {file = "cassandra_driver-3.29.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19cc7375f673e215bd4cbbefae2de9f07830be7dabef55284a2d2ff8d8691efe"}, - {file = "cassandra_driver-3.29.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b74b355be3dcafe652fffda8f14f385ccc1a8dae9df28e6080cc660da39b45f"}, - {file = "cassandra_driver-3.29.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e6dac7eddd3f4581859f180383574068a3f113907811b4dad755a8ace4c3fbd"}, - {file = "cassandra_driver-3.29.1-cp311-cp311-win32.whl", hash = "sha256:293a79dba417112b56320ed0013d71fd7520f5fc4a5fd2ac8000c762c6dd5b07"}, - {file = "cassandra_driver-3.29.1-cp311-cp311-win_amd64.whl", hash = "sha256:7c2374fdf1099047a6c9c8329c79d71ad11e61d9cca7de92a0f49655da4bdd8a"}, - {file = "cassandra_driver-3.29.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4431a0c836f33a33c733c84997fbdb6398be005c4d18a8c8525c469fdc29393c"}, - {file = "cassandra_driver-3.29.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23b08381b171a9e42ace483a82457edcddada9e8367e31677b97538cde2dc34"}, - {file = "cassandra_driver-3.29.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4beb29a0139e63a10a5b9a3c7b72c30a4e6e20c9f0574f9d22c0d4144fe3d348"}, - {file = "cassandra_driver-3.29.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b206423cc454a78f16b411e7cb641dddc26168ac2e18f2c13665f5f3c89868c"}, - {file = "cassandra_driver-3.29.1-cp312-cp312-win32.whl", hash = "sha256:ac898cca7303a3a2a3070513eee12ef0f1be1a0796935c5b8aa13dae8c0a7f7e"}, - {file = "cassandra_driver-3.29.1-cp312-cp312-win_amd64.whl", hash = "sha256:4ad0c9fb2229048ad6ff8c6ddbf1fdc78b111f2b061c66237c2257fcc4a31b14"}, - {file = "cassandra_driver-3.29.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4282c5deac462e4bb0f6fd0553a33d514dbd5ee99d0812594210080330ddd1a2"}, - {file = "cassandra_driver-3.29.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:41ca7eea069754002418d3bdfbd3dfd150ea12cb9db474ab1a01fa4679a05bcb"}, - {file = "cassandra_driver-3.29.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6639ccb268c4dc754bc45e03551711780d0e02cb298ab26cde1f42b7bcc74f8"}, - {file = "cassandra_driver-3.29.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a9d7d3b1be24a7f113b5404186ccccc977520401303a8fe78ba34134cad2482"}, - {file = "cassandra_driver-3.29.1-cp38-cp38-win32.whl", hash = "sha256:81c8fd556c6e1bb93577e69c1f10a3fadf7ddb93958d226ccbb72389396e9a92"}, - {file = "cassandra_driver-3.29.1-cp38-cp38-win_amd64.whl", hash = "sha256:cfe70ed0f27af949de2767ea9cef4092584e8748759374a55bf23c30746c7b23"}, - {file = "cassandra_driver-3.29.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a2c03c1d834ac1a0ae39f9af297a8cd38829003ce910b08b324fb3abe488ce2b"}, - {file = "cassandra_driver-3.29.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9a3e1e2b01f3b7a5cf75c97401bce830071d99c42464352087d7475e0161af93"}, - {file = "cassandra_driver-3.29.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90c42006665a4e490b0766b70f3d637f36a30accbef2da35d6d4081c0e0bafc3"}, - {file = "cassandra_driver-3.29.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c1aca41f45772f9759e8246030907d92bc35fbbdc91525a3cb9b49939b80ad7"}, - {file = "cassandra_driver-3.29.1-cp39-cp39-win32.whl", hash = "sha256:ce4a66245d4a0c8b07fdcb6398698c2c42eb71245fb49cff39435bb702ff7be6"}, - {file = "cassandra_driver-3.29.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cae69ceb1b1d9383e988a1b790115253eacf7867ceb15ed2adb736e3ce981be"}, -] - -[package.dependencies] -geomet = ">=0.1,<0.3" - -[package.extras] -cle = ["cryptography (>=35.0)"] -graph = ["gremlinpython (==3.4.6)"] - -[[package]] -name = "certifi" -version = "2024.2.2" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] - -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "chromadb-client" -version = "0.4.25.dev0" -description = "Chroma Client." -optional = true -python-versions = ">=3.8" -files = [ - {file = "chromadb-client-0.4.25.dev0.tar.gz", hash = "sha256:18762d04720db1ca9ac6347ecd04371064e414b22401aadc2e78a1893fd46595"}, - {file = "chromadb_client-0.4.25.dev0-py3-none-any.whl", hash = "sha256:da52dd28e02bb168be6ab82177726c27f770f5c190ef7c3484b12c6014f2cc07"}, -] - -[package.dependencies] -numpy = ">=1.22.5" -opentelemetry-api = ">=1.2.0" -opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0" -opentelemetry-sdk = ">=1.2.0" -orjson = ">=3.9.12" -overrides = ">=7.3.1" -posthog = ">=2.4.0" -pydantic = ">=1.9" -PyYAML = ">=6.0.0" -requests = ">=2.28" -tenacity = ">=8.2.3" -typing-extensions = ">=4.5.0" - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "clickhouse-driver" -version = "0.2.7" -description = "Python driver with native interface for ClickHouse" -optional = true -python-versions = ">=3.7, <4" -files = [ - {file = "clickhouse-driver-0.2.7.tar.gz", hash = "sha256:299cfbe6d561955d88eeab6e09f3de31e2f6daccc6fdd904a59e46357d2d28d9"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c44fefc2fd44f432d5b162bfe34ad76840137c34167d46a18c554a7c7c6e3566"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e018452a7bf8d8c0adf958afbc5b0d29e402fc09a1fb34e9186293eae57f3b4e"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff8b09f8b13df28d2f91ee3d0d2edd9589cbda76b74acf60669112219cea8c9d"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54aa91c9512fd5a73f038cae4f67ca2ff0b2f8a84de846179a31530936ef4e20"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8342a7ba31ccb393ee31dfd61173aa84c995b4ac0b44d404adc8463534233d5"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:199000f8adf38fade0b5a52c273a396168105539de741a18ba3e68d7fc06e0e6"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60a2a40602b207506e505cfb184a81cd4b752bde17153bc0b32c3931ddb792f"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5db3a26b18146b2b0b06d3f32ce588af5afaa38c719daf6f9606981514228a8b"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5579a31da1f3cf49630e43fbbb11cab891b78161abdcb33908b79820b7cd3a23"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cc39f0fb761aed96917b0f55679174a50f9591afc0e696e745cd698ef822661f"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9aa0f7c740e4e61886c6d388792c5d1a2084d4b5462e6dcfc24e30ca7e7f8e68"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2caee88b6eec7b33ddbccd24501ad99ff8ff2b0a6a4471945cbfb28947a9a791"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-win32.whl", hash = "sha256:a4aef432cc7120a971eebb7ca2fddac4472e810b57e403d3a371b0c69cbb2bb0"}, - {file = "clickhouse_driver-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f307de7df6bc23ad5ec8a1ba1db157f4d14de673ddd4798f37790f23255605b0"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbf3ca8919bf856ca6588669a863065fb732a32a6387095f64d19038fd99db9f"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab68b3d9b9d1386adfd3a57edd47b62858a145bf7ccc7f11b31d308195d966e5"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985a9d60044c5ad39c6e018b852c7105ec4ebfdf4c3abe23183b4867454e570a"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c94330054c8d92d2286898906f843f26e2f96fc2aa11a9a96a7b5593d299bf0"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92938f55c8f797e50e624a4b96e685178d043cdf0ede306a7fd4e7dda19b8dfd"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bd53e9bf49c3013d06f9e6d2812872d44b150f7a2d1cf18e1498257d42330e"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f1f8ed5404e283a9ded499c33eade2423fdc15e31f8a711d75e91f890d0f70b"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a398085e4a1766d907ac32c282d4172db38a44243bde303372396208d1cbf4bb"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:fa1808593123b6056f93808f0afbc7938f06a8149cb4e381aa7b1a234c1d3c18"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:0512d54ae23bd4a69278e04f42b651d7c71b63ba6043e2c6bd97b11329692f99"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5bc2b67e7e68f74ccebf95a8b3a13f13a7c34b89b32c9813103221de14c06c8b"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:04a37cdafc671cb796af3e566cef0aeb39111d82aebeecd9106a049434953b26"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-win32.whl", hash = "sha256:019538c7c23e976538e5081dd2f77a8a40bf663c638a62d857ff05f42b0c9052"}, - {file = "clickhouse_driver-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5166643683584bc53fcadda73c65f6a9077feb472f3d167ecef1a1a7024973aa"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:59affab7b5a3c4aab5b6a730f606575efdefea213458de2eb14927ee4e0640f4"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dcb93dd07fe65ac4f1a2bc0b8967911d4ad2152dbee000f025ea5cb575da5ecb"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55a48019b79181ae1ca90e980e74c5d413c3f8829f6744e2b056646c2d435a1a"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507463c9157240fd7c3246781e8c30df8db3c80bf68925b36ff3ad4a80c4b924"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e2d8d2295ee9e0cfab8ad77cb635a05da2160334b4f16ed8c3d00fbf39a2343"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e38c44546dcdb956b5ab0944cb3d51e8c98f816e75bab1a2254c478865bc6e7b"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6690a2bdd9e7531fe50b53193279f8b35cbcd5c5ee36c0fcc112518a7d24f16"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bc6b4ba0a6467fd09021aa1d87a44fb4589600d61b010fca41e0dfffd0dee322"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:254bbd400eb87ff547a08755bc714f712e11f7a6d3ebbbb7aaa1dd454fb16d44"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7bbbe3f8b87fc1489bc15fa9c88cc9fac9d4d7d683d076f058c2c83e6ee422fd"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:745e5b18f0957d932151527f1523d0e516c199de8c589638e5f55ab2559886f3"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fa0357fb5f26149e3df86a117d3678329b85d8827b78a5a09bbf224d8dd4541"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-win32.whl", hash = "sha256:ace652af7ca94ba3cb3a04a5c363e135dc5009f31d8201903e21db9d5daf2358"}, - {file = "clickhouse_driver-0.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:c0ba68489544df89e4138a14b0ec3e1e5eb102d5d3283a91d9b837c420c0ab97"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:66267e4ba21fa66c97ce784a5de2202d3b7d4db3e50bfcdde92830a68f6fae30"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cf55c285b75c178487407721baef4980b3c6515c9c0c1a6c1ea8b001afe658e"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:deeb66bb56490db2157f199c6d9aa2c53f046677be430cc834fc1e74eec6e654"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfe5b4020939abeeb407b4eead598c954b1573d2d2b4f174f793b196d378b9d9"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84d39506b5f8d86a1195ebde1c66aba168f34ebce6ebd828888f0625cac54774"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f93a27db2dcbbd3ecad36e8df4395d047cb7410e2dc69f6d037674e15442f4ee"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ebc29e501e47ecbfd44c89c0e5c87b2a722049d38b9e93fdd4bea510a82e16ac"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f9cc8c186fea09a94d89e5c9c4e8d05ec3a80e2f6d25673c48efec8117a13cfc"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:0757dfde5410c42230b24825ea3ab904a78160520e5ceb953482e133e368733b"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c9f88818cf411f928c29ba295c677cd95773bd256b8490f5655fb489e0c6658c"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e19952f158ebe274c65ffeb294ba378d75048a48f31b77573948d606bed019d5"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:008b1f32c7c68564de8051482b72a5289b6933bca9d9b1ad1474dd448d6768ba"}, - {file = "clickhouse_driver-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:622933cc9834c39f03de5d43a12f13fc7133d31d6d2597e67866d4a549ca9e60"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:92540581e5b5f36d915f14d05c30244870fb123c74b38c645fa47663053c5471"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02dfadc6111b64e01c20b8c11266cab97d4f06685a392a183af437f2f1afb990"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ca17fece86fe85d97705024bec881978271931b3d00db273c9d63244f7d606"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76474f1315ca3ab484ae28ad085b8f756c8b9a755882f93912b2149290482033"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5c0ff12368b34aaf58dd948b0819e5b54d261911de334d3f048328dc9354013"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd441b17294e90e313b08fabf84fcc782c191d2b9b2a924f163928202db6fcc"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62aa158f61d7d84c58e8cd75b3b8340b28607e5a70132395078f578d518aaae3"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcb2a39a1fef8bf1b581f06125c2a84a5b92c939b079d1a95126e3207b05dc77"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f29cc641a65e89a51a15f6d195f565ad2761d1bd653408c6b4046c987c5fb99"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ac1a43690696bda46c9a23fc6fd79b6fe22d428a18e880bdbdf5e6aeb31008c5"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:1dd5ea4584c42f85d96ddfa7d07da2abb35a797c45e4d3a66ace149ee4977cad"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a736c0af858a3c83af03848b18754ab18dc594cc7f3bf6be0b1fac682def182c"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-win32.whl", hash = "sha256:6cb8ca47f5818c1bc5814b9ff775e383f3c50059b1fd28a02cb9be1b666929f8"}, - {file = "clickhouse_driver-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:a90e7dc92985669a5e6569356bb3028d9d475f95006d4487cb0789aa53f9489c"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04b77cd6c583da9135db4a62c5a7999ae248c2dbfc0cb8e8a3d8a853b1fbfa11"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c7671f8c0e8960d766b2e0eaefcae3088fccdd3920e9cd3dee8e344cfd0a6929"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:502d7cd28522b95a399e993ffd48487e8c12c50ce2d4e89b77b938f945304405"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:969739279f4010e7b5b6b2c9d2ab56a463aed11fdaed5e02424c1b3915f144f8"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed34b60f741eeb02407ea72180d77cbfc368c1be6fc2f2ff8319d1856ce67e10"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a667b48927f4420eb8c03fa33369edfbdf359a788897a01ac945263a2a611461"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f93aa3a90f3847872d7464ec9076482b2e812c4e7d61682daedffdf3471be00"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:190890667215691fdf2155c3b233b39146054ab1cd854c7d91221e6ed633d71e"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ff280aeac5e96c764cd31ba1077c95601337b9a97fb0b9ed4d24c64431f2c322"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01e63e35d2ab55b8eb48facf6e951968c80d27ee6703aa6c91c73d9d0a4d0efe"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a29fb24b910dafc8c11ba882797d13ec0323a97dce80a57673116fa893d1b669"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5f229a7853fc767e63143ea69889d49f6fd5623adc2f7b0f7eb360117d7e91a5"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-win32.whl", hash = "sha256:b7f34ad2ed509f48f8ed1f9b96e89765173a7b35d286c7350aa85934a11c0f49"}, - {file = "clickhouse_driver-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:78b166597afbe490cc0cdac44fed8c8b81668f87125601dda17b154f237eef5d"}, - {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:16ab64beb8d079cb9b3200539539a35168f524eedf890c9acefb719e25bdc96e"}, - {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03e28fd50fc7c54874bf8e638a2ea87f73ae35bfbbf90123fdb395f38d62f159"}, - {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0677b8350acd8d186b6acd0026b62dd262d6fee428a5fa3ad9561908d4b02c39"}, - {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a2f3c9e2182809131701bb28a606dec90525c7ab20490714714a4b3eb015454b"}, - {file = "clickhouse_driver-0.2.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e03a1a1b30cc58c9bd2cbe25bf5e40b1f1d16d52d44ddefb3af50435d1ed613c"}, - {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1be8081306a4beb12444ed8e3208e1eb6c01ed207c471b33009c13504c88139"}, - {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:933b40722cbca9b1123a5bb2fb4bafafd234deae0f3481125cb6b6fa1d39aa84"}, - {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3054b5022f9bf15a5f4663a7cd190f466e70a2d7b8d45429d8742c515b556c10"}, - {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61744760ee046c9a268cb801ca21bfe44c4873db9901a7cd0f3ca8830205feff"}, - {file = "clickhouse_driver-0.2.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5e28427e05a72e7a4c3672e36703a2d80107ee0b3ab537e3380d726c96b07821"}, - {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c483f5ec836ae87803478f2a7b9daf15343078edd6a8be7364dd9db64905bbd0"}, - {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28220b794874e68bc2f06dbfff5748f1c5a3236922f59e127abd58d44ae20a3f"}, - {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c09877b59b34d5b3043ad70ec31543173cac8b64b4a8afaa89416b22fb28da5"}, - {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3580f78db27119f7380627873214ae1342066f1ecb35700c1d7bf418dd70ae73"}, - {file = "clickhouse_driver-0.2.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0842ac1b2f7a9ca46dac2027849b241bccd8eb8ff1c59cb0a5874042b267b733"}, - {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a3fb585e2d3514196258a4a3b0267510c03477f3c2380239ade4c056ba689a7"}, - {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ea25287566d45efbaee0857ad25e8b33ffd7fd73e89424d79fe7f532962915"}, - {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee4a4935667b59b4816a5ca77300f5dbe5a7416860551d17376426b8fefc1175"}, - {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:358058cfceea9b43c4af9de81842563746f16984b34525a15b41eacf8fc2bed2"}, - {file = "clickhouse_driver-0.2.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ae760fb843dec0b5c398536ca8dfaf243f494ba8fc68132ae1bd62004b0c396a"}, -] - -[package.dependencies] -pytz = "*" -tzlocal = "*" - -[package.extras] -lz4 = ["clickhouse-cityhash (>=1.0.2.1)", "lz4", "lz4 (<=3.0.1)"] -numpy = ["numpy (>=1.12.0)", "pandas (>=0.24.0)"] -zstd = ["clickhouse-cityhash (>=1.0.2.1)", "zstd"] - -[[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" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.4.3" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, - {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, - {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, - {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, - {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, - {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, - {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, - {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cryptography" -version = "42.0.5" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, -] - -[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", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - -[[package]] -name = "deprecation" -version = "2.1.0" -description = "A library to handle automated deprecations" -optional = true -python-versions = "*" -files = [ - {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, - {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, -] - -[package.dependencies] -packaging = "*" - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "dnspython" -version = "2.6.1" -description = "DNS toolkit" -optional = true -python-versions = ">=3.8" -files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, -] - -[package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] -trio = ["trio (>=0.23)"] -wmi = ["wmi (>=1.5.1)"] - -[[package]] -name = "docker" -version = "7.1.0" -description = "A Python library for the Docker Engine API." -optional = false -python-versions = ">=3.8" -files = [ - {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"}, - {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"}, -] - -[package.dependencies] -pywin32 = {version = ">=304", markers = "sys_platform == \"win32\""} -requests = ">=2.26.0" -urllib3 = ">=1.26.0" - -[package.extras] -dev = ["coverage (==7.2.7)", "pytest (==7.4.2)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.1.0)", "ruff (==0.1.8)"] -docs = ["myst-parser (==0.18.0)", "sphinx (==5.1.1)"] -ssh = ["paramiko (>=2.4.3)"] -websockets = ["websocket-client (>=1.3.0)"] - -[[package]] -name = "docutils" -version = "0.20.1" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, -] - -[[package]] -name = "environs" -version = "9.5.0" -description = "simplified environment variable parsing" -optional = false -python-versions = ">=3.6" -files = [ - {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, - {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, -] - -[package.dependencies] -marshmallow = ">=3.0.0" -python-dotenv = "*" - -[package.extras] -dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"] -django = ["dj-database-url", "dj-email-url", "django-cache-url"] -lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] -tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"] - -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.13.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "geomet" -version = "0.2.1.post1" -description = "GeoJSON <-> WKT/WKB conversion utilities" -optional = false -python-versions = ">2.6, !=3.3.*, <4" -files = [ - {file = "geomet-0.2.1.post1-py3-none-any.whl", hash = "sha256:a41a1e336b381416d6cbed7f1745c848e91defaa4d4c1bdc1312732e46ffad2b"}, - {file = "geomet-0.2.1.post1.tar.gz", hash = "sha256:91d754f7c298cbfcabd3befdb69c641c27fe75e808b27aa55028605761d17e95"}, -] - -[package.dependencies] -click = "*" -six = "*" - -[[package]] -name = "google-api-core" -version = "2.17.1" -description = "Google API client core library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "google-api-core-2.17.1.tar.gz", hash = "sha256:9df18a1f87ee0df0bc4eea2770ebc4228392d8cc4066655b320e2cfccb15db95"}, - {file = "google_api_core-2.17.1-py3-none-any.whl", hash = "sha256:610c5b90092c360736baccf17bd3efbcb30dd380e7a6dc28a71059edb8bd0d8e"}, -] - -[package.dependencies] -google-auth = ">=2.14.1,<3.0.dev0" -googleapis-common-protos = ">=1.56.2,<2.0.dev0" -grpcio = [ - {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, - {version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, -] -grpcio-status = [ - {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, - {version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" -requests = ">=2.18.0,<3.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] -grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] -grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] - -[[package]] -name = "google-auth" -version = "2.28.2" -description = "Google Authentication Library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "google-auth-2.28.2.tar.gz", hash = "sha256:80b8b4969aa9ed5938c7828308f20f035bc79f9d8fb8120bf9dc8db20b41ba30"}, - {file = "google_auth-2.28.2-py2.py3-none-any.whl", hash = "sha256:9fd67bbcd40f16d9d42f950228e9cf02a2ded4ae49198b27432d0cded5a74c38"}, -] - -[package.dependencies] -cachetools = ">=2.0.0,<6.0" -pyasn1-modules = ">=0.2.1" -rsa = ">=3.1.4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] -enterprise-cert = ["cryptography (==36.0.2)", "pyopenssl (==22.0.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] -reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] - -[[package]] -name = "google-cloud-core" -version = "2.4.1" -description = "Google Cloud API client core library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "google-cloud-core-2.4.1.tar.gz", hash = "sha256:9b7749272a812bde58fff28868d0c5e2f585b82f37e09a1f6ed2d4d10f134073"}, - {file = "google_cloud_core-2.4.1-py2.py3-none-any.whl", hash = "sha256:a9e6a4422b9ac5c29f79a0ede9485473338e2ce78d91f2370c01e730eab22e61"}, -] - -[package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" - -[package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] - -[[package]] -name = "google-cloud-datastore" -version = "2.19.0" -description = "Google Cloud Datastore API client library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "google-cloud-datastore-2.19.0.tar.gz", hash = "sha256:07fc5870a0261f25466c557c134df95a96dfd2537abd088b9d537fbabe99b974"}, - {file = "google_cloud_datastore-2.19.0-py2.py3-none-any.whl", hash = "sha256:c52086670d4c3779ea7bd8f8353b093a9b5e81c6606f36ffcdf46e6ce9fc80c0"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-cloud-core = ">=1.4.0,<3.0.0dev" -proto-plus = [ - {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" - -[package.extras] -libcst = ["libcst (>=0.2.5)"] - -[[package]] -name = "google-cloud-pubsub" -version = "2.20.1" -description = "Google Cloud Pub/Sub API client library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "google-cloud-pubsub-2.20.1.tar.gz", hash = "sha256:b6d06f1827968273c42b57a09f642462649c9504dc0f8756f99770f4e3e755ad"}, - {file = "google_cloud_pubsub-2.20.1-py2.py3-none-any.whl", hash = "sha256:06dd62181e2f248f32b9077f4dc07b413191a84fc06d7323b208602d887207bc"}, -] - -[package.dependencies] -google-api-core = {version = ">=1.34.0,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} -google-auth = ">=2.14.1,<3.0.0dev" -grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" -grpcio = ">=1.51.3,<2.0dev" -grpcio-status = ">=1.33.2" -proto-plus = [ - {version = ">=1.22.2,<2.0.0dev", markers = "python_version >= \"3.11\""}, - {version = ">=1.22.0,<2.0.0dev", markers = "python_version < \"3.11\""}, -] -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" - -[package.extras] -libcst = ["libcst (>=0.3.10)"] - -[[package]] -name = "googleapis-common-protos" -version = "1.62.0" -description = "Common protobufs used in Google APIs" -optional = true -python-versions = ">=3.7" -files = [ - {file = "googleapis-common-protos-1.62.0.tar.gz", hash = "sha256:83f0ece9f94e5672cced82f592d2a5edf527a96ed1794f0bab36d5735c996277"}, - {file = "googleapis_common_protos-1.62.0-py2.py3-none-any.whl", hash = "sha256:4750113612205514f9f6aa4cb00d523a94f3e8c06c5ad2fee466387dc4875f07"}, -] - -[package.dependencies] -grpcio = {version = ">=1.44.0,<2.0.0.dev0", optional = true, markers = "extra == \"grpc\""} -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0" - -[package.extras] -grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "grpc-google-iam-v1" -version = "0.13.0" -description = "IAM API client library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "grpc-google-iam-v1-0.13.0.tar.gz", hash = "sha256:fad318608b9e093258fbf12529180f400d1c44453698a33509cc6ecf005b294e"}, - {file = "grpc_google_iam_v1-0.13.0-py2.py3-none-any.whl", hash = "sha256:53902e2af7de8df8c1bd91373d9be55b0743ec267a7428ea638db3775becae89"}, -] - -[package.dependencies] -googleapis-common-protos = {version = ">=1.56.0,<2.0.0dev", extras = ["grpc"]} -grpcio = ">=1.44.0,<2.0.0dev" -protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" - -[[package]] -name = "grpcio" -version = "1.62.1" -description = "HTTP/2-based RPC framework" -optional = false -python-versions = ">=3.7" -files = [ - {file = "grpcio-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:179bee6f5ed7b5f618844f760b6acf7e910988de77a4f75b95bbfaa8106f3c1e"}, - {file = "grpcio-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:48611e4fa010e823ba2de8fd3f77c1322dd60cb0d180dc6630a7e157b205f7ea"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b2a0e71b0a2158aa4bce48be9f8f9eb45cbd17c78c7443616d00abbe2a509f6d"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbe80577c7880911d3ad65e5ecc997416c98f354efeba2f8d0f9112a67ed65a5"}, - {file = "grpcio-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58f6c693d446964e3292425e1d16e21a97a48ba9172f2d0df9d7b640acb99243"}, - {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:77c339403db5a20ef4fed02e4d1a9a3d9866bf9c0afc77a42234677313ea22f3"}, - {file = "grpcio-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b5a4ea906db7dec694098435d84bf2854fe158eb3cd51e1107e571246d4d1d70"}, - {file = "grpcio-1.62.1-cp310-cp310-win32.whl", hash = "sha256:4187201a53f8561c015bc745b81a1b2d278967b8de35f3399b84b0695e281d5f"}, - {file = "grpcio-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:844d1f3fb11bd1ed362d3fdc495d0770cfab75761836193af166fee113421d66"}, - {file = "grpcio-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:833379943d1728a005e44103f17ecd73d058d37d95783eb8f0b28ddc1f54d7b2"}, - {file = "grpcio-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:c7fcc6a32e7b7b58f5a7d27530669337a5d587d4066060bcb9dee7a8c833dfb7"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:fa7d28eb4d50b7cbe75bb8b45ed0da9a1dc5b219a0af59449676a29c2eed9698"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48f7135c3de2f298b833be8b4ae20cafe37091634e91f61f5a7eb3d61ec6f660"}, - {file = "grpcio-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f11fd63365ade276c9d4a7b7df5c136f9030e3457107e1791b3737a9b9ed6a"}, - {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b49fd8fe9f9ac23b78437da94c54aa7e9996fbb220bac024a67469ce5d0825f"}, - {file = "grpcio-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:482ae2ae78679ba9ed5752099b32e5fe580443b4f798e1b71df412abf43375db"}, - {file = "grpcio-1.62.1-cp311-cp311-win32.whl", hash = "sha256:1faa02530b6c7426404372515fe5ddf66e199c2ee613f88f025c6f3bd816450c"}, - {file = "grpcio-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bd90b8c395f39bc82a5fb32a0173e220e3f401ff697840f4003e15b96d1befc"}, - {file = "grpcio-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:b134d5d71b4e0837fff574c00e49176051a1c532d26c052a1e43231f252d813b"}, - {file = "grpcio-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:d1f6c96573dc09d50dbcbd91dbf71d5cf97640c9427c32584010fbbd4c0e0037"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:359f821d4578f80f41909b9ee9b76fb249a21035a061a327f91c953493782c31"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a485f0c2010c696be269184bdb5ae72781344cb4e60db976c59d84dd6354fac9"}, - {file = "grpcio-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50b09b4dc01767163d67e1532f948264167cd27f49e9377e3556c3cba1268e1"}, - {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3227c667dccbe38f2c4d943238b887bac588d97c104815aecc62d2fd976e014b"}, - {file = "grpcio-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3952b581eb121324853ce2b191dae08badb75cd493cb4e0243368aa9e61cfd41"}, - {file = "grpcio-1.62.1-cp312-cp312-win32.whl", hash = "sha256:83a17b303425104d6329c10eb34bba186ffa67161e63fa6cdae7776ff76df73f"}, - {file = "grpcio-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:6696ffe440333a19d8d128e88d440f91fb92c75a80ce4b44d55800e656a3ef1d"}, - {file = "grpcio-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:e3393b0823f938253370ebef033c9fd23d27f3eae8eb9a8f6264900c7ea3fb5a"}, - {file = "grpcio-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:83e7ccb85a74beaeae2634f10eb858a0ed1a63081172649ff4261f929bacfd22"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:882020c87999d54667a284c7ddf065b359bd00251fcd70279ac486776dbf84ec"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a10383035e864f386fe096fed5c47d27a2bf7173c56a6e26cffaaa5a361addb1"}, - {file = "grpcio-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:960edebedc6b9ada1ef58e1c71156f28689978188cd8cff3b646b57288a927d9"}, - {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:23e2e04b83f347d0aadde0c9b616f4726c3d76db04b438fd3904b289a725267f"}, - {file = "grpcio-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978121758711916d34fe57c1f75b79cdfc73952f1481bb9583399331682d36f7"}, - {file = "grpcio-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9084086190cc6d628f282e5615f987288b95457292e969b9205e45b442276407"}, - {file = "grpcio-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:22bccdd7b23c420a27fd28540fb5dcbc97dc6be105f7698cb0e7d7a420d0e362"}, - {file = "grpcio-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:8999bf1b57172dbc7c3e4bb3c732658e918f5c333b2942243f10d0d653953ba9"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:d9e52558b8b8c2f4ac05ac86344a7417ccdd2b460a59616de49eb6933b07a0bd"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1714e7bc935780bc3de1b3fcbc7674209adf5208ff825799d579ffd6cd0bd505"}, - {file = "grpcio-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8842ccbd8c0e253c1f189088228f9b433f7a93b7196b9e5b6f87dba393f5d5d"}, - {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f1e7b36bdff50103af95a80923bf1853f6823dd62f2d2a2524b66ed74103e49"}, - {file = "grpcio-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bba97b8e8883a8038606480d6b6772289f4c907f6ba780fa1f7b7da7dfd76f06"}, - {file = "grpcio-1.62.1-cp38-cp38-win32.whl", hash = "sha256:a7f615270fe534548112a74e790cd9d4f5509d744dd718cd442bf016626c22e4"}, - {file = "grpcio-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:e6c8c8693df718c5ecbc7babb12c69a4e3677fd11de8886f05ab22d4e6b1c43b"}, - {file = "grpcio-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:73db2dc1b201d20ab7083e7041946910bb991e7e9761a0394bbc3c2632326483"}, - {file = "grpcio-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:407b26b7f7bbd4f4751dbc9767a1f0716f9fe72d3d7e96bb3ccfc4aace07c8de"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:f8de7c8cef9261a2d0a62edf2ccea3d741a523c6b8a6477a340a1f2e417658de"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd5c8a1af40ec305d001c60236308a67e25419003e9bb3ebfab5695a8d0b369"}, - {file = "grpcio-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0477cb31da67846a33b1a75c611f88bfbcd427fe17701b6317aefceee1b96f"}, - {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:60dcd824df166ba266ee0cfaf35a31406cd16ef602b49f5d4dfb21f014b0dedd"}, - {file = "grpcio-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:973c49086cabab773525f6077f95e5a993bfc03ba8fc32e32f2c279497780585"}, - {file = "grpcio-1.62.1-cp39-cp39-win32.whl", hash = "sha256:12859468e8918d3bd243d213cd6fd6ab07208195dc140763c00dfe901ce1e1b4"}, - {file = "grpcio-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7209117bbeebdfa5d898205cc55153a51285757902dd73c47de498ad4d11332"}, - {file = "grpcio-1.62.1.tar.gz", hash = "sha256:6c455e008fa86d9e9a9d85bb76da4277c0d7d9668a3bfa70dbe86e9f3c759947"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.62.1)"] - -[[package]] -name = "grpcio-health-checking" -version = "1.62.1" -description = "Standard Health Checking Service for gRPC" -optional = true -python-versions = ">=3.6" -files = [ - {file = "grpcio-health-checking-1.62.1.tar.gz", hash = "sha256:9e56180a941b1d32a077d7491e0611d0483c396358afd5349bf00152612e4583"}, - {file = "grpcio_health_checking-1.62.1-py3-none-any.whl", hash = "sha256:9ce761c09fc383e7aa2f7e6c0b0b65d5a1157c1b98d1f5871f7c38aca47d49b9"}, -] - -[package.dependencies] -grpcio = ">=1.62.1" -protobuf = ">=4.21.6" - -[[package]] -name = "grpcio-status" -version = "1.62.1" -description = "Status proto mapping for gRPC" -optional = true -python-versions = ">=3.6" -files = [ - {file = "grpcio-status-1.62.1.tar.gz", hash = "sha256:3431c8abbab0054912c41df5c72f03ddf3b7a67be8a287bb3c18a3456f96ff77"}, - {file = "grpcio_status-1.62.1-py3-none-any.whl", hash = "sha256:af0c3ab85da31669f21749e8d53d669c061ebc6ce5637be49a46edcb7aa8ab17"}, -] - -[package.dependencies] -googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.62.1" -protobuf = ">=4.21.6" - -[[package]] -name = "grpcio-tools" -version = "1.62.1" -description = "Protobuf code generator for gRPC" -optional = true -python-versions = ">=3.7" -files = [ - {file = "grpcio-tools-1.62.1.tar.gz", hash = "sha256:a4991e5ee8a97ab791296d3bf7e8700b1445635cc1828cc98df945ca1802d7f2"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:f2b404bcae7e2ef9b0b9803b2a95119eb7507e6dc80ea4a64a78be052c30cebc"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:fdd987a580b4474769adfd40144486f54bcc73838d5ec5d3647a17883ea78e76"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:07af1a6442e2313cff22af93c2c4dd37ae32b5239b38e0d99e2cbf93de65429f"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41384c9ee18e61ef20cad2774ef71bd8854b63efce263b5177aa06fccb84df1f"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c38006f7702d2ff52122e4c77a47348709374050c76216e84b30a9f06e45afa"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08fecc3c5b4e6dd3278f2b9d12837e423c7dcff551ca1e587018b4a0fc5f8019"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a01e8dcd0f041f6fa6d815c54a2017d032950e310c41d514a8bc041e872c4d12"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-win32.whl", hash = "sha256:dd933b8e0b3c13fe3543d58f849a6a5e0d7987688cb6801834278378c724f695"}, - {file = "grpcio_tools-1.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:2b04844a9382f1bde4b4174e476e654ab3976168d2469cb4b29e352f4f35a5aa"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:024380536ba71a96cdf736f0954f6ad03f5da609c09edbcc2ca02fdd639e0eed"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:21f14b99e0cd38ad56754cc0b62b2bf3cf75f9f7fc40647da54669e0da0726fe"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:975ac5fb482c23f3608c16e06a43c8bab4d79c2e2564cdbc25cf753c6e998775"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50739aaab0c8076ad5957204e71f2e0c9876e11fd8338f7f09de12c2d75163c5"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598c54318f0326cf5020aa43fc95a15e933aba4a71943d3bff2677d2d21ddfa1"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f309bdb33a61f8e049480d41498ee2e525cfb5e959958b326abfdf552bf9b9cb"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f358effd3c11d66c150e0227f983d54a5cd30e14038566dadcf25f9f6844e6e8"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-win32.whl", hash = "sha256:b76aead9b73f1650a091870fe4e9ed15ac4d8ed136f962042367255199c23594"}, - {file = "grpcio_tools-1.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:d66a5d47eaa427039752fa0a83a425ff2a487b6a0ac30556fd3be2f3a27a0130"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:575535d039b97d63e6a9abee626d6c7cd47bd8cb73dd00a5c84a98254a2164a4"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:22644c90e43d1a888477899af917979e17364fdd6e9bbb92679cd6a54c4d36c3"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:156d3e1b227c16e903003a56881dbe60e40f2b4bd66f0bc3b27c53e466e6384d"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ad7c5691625a85327e5b683443baf73ae790fd5afc938252041ed5cd665e377"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e140bbc08eea8abf51c0274f45fb1e8350220e64758998d7f3c7f985a0b2496"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7444fcab861911525470d398e5638b70d5cbea3b4674a3de92b5c58c5c515d4d"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e643cd14a5d1e59865cba68a5a6f0175d987f36c5f4cb0db80dee9ed60b4c174"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-win32.whl", hash = "sha256:1344a773d2caa9bb7fbea7e879b84f33740c808c34a5bd2a2768e526117a6b44"}, - {file = "grpcio_tools-1.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:2eea1db3748b2f37b4dce84d8e0c15d9bc811094807cabafe7b0ea47f424dfd5"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:45d2e6cf04d27286b6f73e6e20ba3f0a1f6d8f5535e5dcb1356200419bb457f4"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:46ae58e6926773e7315e9005f0f17aacedbc0895a8752bec087d24efa2f1fb21"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:4c28086df31478023a36f45e50767872ab3aed2419afff09814cb61c88b77db4"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fba5b339f4797548591036c9481e6895bf920fab7d3dc664d2697f8fb7c0bf"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23eb3d47f78f509fcd201749b1f1e44b76f447913f7fbb3b8bae20f109086295"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fd5d47707bd6bc2b707ece765c362d2a1d2e8f6cd92b04c99fab49a929f3610c"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d1924a6a943df7c73b9ef0048302327c75962b567451479710da729ead241228"}, - {file = "grpcio_tools-1.62.1-cp37-cp37m-win_amd64.whl", hash = "sha256:fe71ca30aabe42591e84ecb9694c0297dc699cc20c5b24d2cb267fb0fc01f947"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:1819fd055c1ae672d1d725ec75eefd1f700c18acba0ed9332202be31d69c401d"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:5dbe1f7481dd14b6d477b4bace96d275090bc7636b9883975a08b802c94e7b78"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:771c051c5ece27ad03e4f2e33624a925f0ad636c01757ab7dbb04a37964af4ba"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98209c438b38b6f1276dbc27b1c04e346a75bfaafe72a25a548f2dc5ce71d226"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2152308e5321cb90fb45aaa84d03d6dedb19735a8779aaf36c624f97b831842d"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ed1f27dc2b2262c8b8d9036276619c1bb18791311c16ccbf1f31b660f2aad7cf"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2744947b6c5e907af21133431809ccca535a037356864e32c122efed8cb9de1f"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-win32.whl", hash = "sha256:13b20e269d14ad629ff9a2c9a2450f3dbb119d5948de63b27ffe624fa7aea85a"}, - {file = "grpcio_tools-1.62.1-cp38-cp38-win_amd64.whl", hash = "sha256:999823758e9eacd0095863d06cd6d388be769f80c9abb65cdb11c4f2cfce3fea"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:941f8a5c31986053e75fa466bcfa743c2bf1b513b7978cf1f4ab4e96a8219d27"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:b9c02c88c77ef6057c6cbeea8922d7c2424aabf46bfc40ddf42a32765ba91061"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:6abd4eb3ccb444383a40156139acc3aaa73745d395139cb6bc8e2a3429e1e627"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:449503213d142f8470b331a1c2f346f8457f16c7fe20f531bc2500e271f7c14c"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a11bcf609d00cfc9baed77ab308223cabc1f0b22a05774a26dd4c94c0c80f1f"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5d7bdea33354b55acf40bb4dd3ba7324d6f1ef6b4a1a4da0807591f8c7e87b9a"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d03b645852d605f43003020e78fe6d573cae6ee6b944193e36b8b317e7549a20"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-win32.whl", hash = "sha256:52b185dfc3bf32e70929310367dbc66185afba60492a6a75a9b1141d407e160c"}, - {file = "grpcio_tools-1.62.1-cp39-cp39-win_amd64.whl", hash = "sha256:63a273b70896d3640b7a883eb4a080c3c263d91662d870a2e9c84b7bbd978e7b"}, -] - -[package.dependencies] -grpcio = ">=1.62.1" -protobuf = ">=4.21.6,<5.0dev" -setuptools = "*" - -[[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" -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 = "h2" -version = "4.1.0" -description = "HTTP/2 State-Machine based protocol implementation" -optional = true -python-versions = ">=3.6.1" -files = [ - {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, - {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, -] - -[package.dependencies] -hpack = ">=4.0,<5" -hyperframe = ">=6.0,<7" - -[[package]] -name = "hpack" -version = "4.0.0" -description = "Pure-Python HPACK header compression" -optional = true -python-versions = ">=3.6.1" -files = [ - {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, - {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, -] - -[[package]] -name = "httpcore" -version = "1.0.5" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] - -[[package]] -name = "httpx" -version = "0.27.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "hvac" -version = "2.1.0" -description = "HashiCorp Vault API client" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "hvac-2.1.0-py3-none-any.whl", hash = "sha256:73bc91e58c3fc7c6b8107cdaca9cb71fa0a893dfd80ffbc1c14e20f24c0c29d7"}, - {file = "hvac-2.1.0.tar.gz", hash = "sha256:b48bcda11a4ab0a7b6c47232c7ba7c87fda318ae2d4a7662800c465a78742894"}, -] - -[package.dependencies] -requests = ">=2.27.1,<3.0.0" - -[package.extras] -parser = ["pyhcl (>=0.4.4,<0.5.0)"] - -[[package]] -name = "hyperframe" -version = "6.0.1" -description = "HTTP/2 framing layer for Python" -optional = true -python-versions = ">=3.6.1" -files = [ - {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, - {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, -] - -[[package]] -name = "ibm-db" -version = "3.2.3" -description = "Python DBI driver for DB2 (LUW, zOS, i5) and IDS" -optional = true -python-versions = "*" -files = [ - {file = "ibm_db-3.2.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:3399466141c29704f4e8ba709a67ba27ab413239c0244c3c4510126e946ff603"}, - {file = "ibm_db-3.2.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e12ff6426d4f718e1ff6615e64a2880bd570826f19a031c82dbf296714cafd7d"}, - {file = "ibm_db-3.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:442a416a47e0d6ae3de671d227906487a1d731f36da8dc9ba341bd384b97f973"}, - {file = "ibm_db-3.2.3-cp310-cp310-win32.whl", hash = "sha256:8f508caca6407947f4156cae853942d1079736505231246ee51475d7f5af1792"}, - {file = "ibm_db-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:91154c151784be5234c9f327239f1a98fc4e4a5f112c3c94189e04cfab3d5cb0"}, - {file = "ibm_db-3.2.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:4fbf78b69d61997dad8ee1fdc273d0b287b43f25fe2ee8c945c034bddb527f1d"}, - {file = "ibm_db-3.2.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a13f20b40ca856ec2a5638f8f4e65287c23ff5e1d808fa58fd8d208678a00323"}, - {file = "ibm_db-3.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5a8ab31130beea18dcd3dd447d6e35ec840ccaed1d3add8ed04ac5c4f44f94c"}, - {file = "ibm_db-3.2.3-cp311-cp311-win32.whl", hash = "sha256:afa8c0a55be2b27ff7f3d50ae0b332562d3048af17557b86854e8e67429fdf0a"}, - {file = "ibm_db-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:d46fb0554631c18fc1f5b615112c68c1b250b7f977dc10cdb53db9258ca69f20"}, - {file = "ibm_db-3.2.3-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7c5011f47edf179c04b67e3472c25f40103679936b17a04dc00a9a7282aeb2b6"}, - {file = "ibm_db-3.2.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b10593eda579395d84254165dc5f5e5eff97d87b9a491181b8632f3db7aa17de"}, - {file = "ibm_db-3.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0170e4c09fb5cf146d26cce0700a40e6a3ab767996f8b4c668847ad1ddc3a06"}, - {file = "ibm_db-3.2.3-cp312-cp312-win32.whl", hash = "sha256:69b2ebf47122eff50497ba48dee7a32087e2698a771c5d86fc683e7baecd5e96"}, - {file = "ibm_db-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7c18f230f8202a386873c73bfe9b00d1c052c35e9a501e07700cb83e7a59c48f"}, - {file = "ibm_db-3.2.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:9fdb360b9de86422f8827774680f28ffeba98b702e86f689acaa0f97b62e1693"}, - {file = "ibm_db-3.2.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34bc14c9f4d7f56ab8c7650e06ff3982695dafea2aa90a3a3533e3bcd5ea7be8"}, - {file = "ibm_db-3.2.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067c2a76e230ffe703a89829c4063b9fa951230c91001846c5221bb6ef4a1ef"}, - {file = "ibm_db-3.2.3-cp37-cp37m-win32.whl", hash = "sha256:4141333b42e10eaf97c5712205e873fc4977bd77c2aef416385620f2a01cc32f"}, - {file = "ibm_db-3.2.3-cp37-cp37m-win_amd64.whl", hash = "sha256:a1b981485d82d9d23d2c19de2fa1087a6ed5ea134944b1ab10eb0b7758cec512"}, - {file = "ibm_db-3.2.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:60d7db80d645eb41c1ddfa9279550784566bdd701c4c52da206e28f9f91e8030"}, - {file = "ibm_db-3.2.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def6c72fcd31fc3e4fe91ffac94db2f6c3365ae4fe7bc1c284fca741f7a0861e"}, - {file = "ibm_db-3.2.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:611c1ea3c32067083072365eae86b939edb4bc730f6016b670f2264220ac2d63"}, - {file = "ibm_db-3.2.3-cp38-cp38-win32.whl", hash = "sha256:238460936016ec6bbe43dd5612829a6ad19a2f483dde57294869c48809e4c902"}, - {file = "ibm_db-3.2.3-cp38-cp38-win_amd64.whl", hash = "sha256:6217177a6246ddf86463e090200e7c60459a62af5513b78793ac9f196ef34571"}, - {file = "ibm_db-3.2.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4611a10dc4b9eca06aadca5ea697c9af71b16ba0f1076fa7dd66d1698a23d2a6"}, - {file = "ibm_db-3.2.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f736bbd6fc2bec483f82b8e3243a12737fb46bbd0f50b1378c67a28cf2f9649"}, - {file = "ibm_db-3.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b90e5fc0ae75567539cd95d5ded86f7c5507084f6aa52eb16ea0dcc88b25382"}, - {file = "ibm_db-3.2.3-cp39-cp39-win32.whl", hash = "sha256:60db181462194cc1d5fa22514cb73d84c4edf79c12d98c8d16796e72ad179c8b"}, - {file = "ibm_db-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:48008a611a6ca724261866c81680f638e1a4116efb21da4fbc26188679a124ca"}, - {file = "ibm_db-3.2.3.tar.gz", hash = "sha256:ec7075246849437ed79c60447b05a4bee78a3f6ca2646f4e60a028333c72957a"}, -] - -[[package]] -name = "ibm-db-sa" -version = "0.4.1" -description = "SQLAlchemy support for IBM Data Servers" -optional = true -python-versions = "*" -files = [ - {file = "ibm_db_sa-0.4.1-py3-none-any.whl", hash = "sha256:49926ba9799e6ebd9ddd847141537c83d179ecf32fe24b7e997ac4614d3f616a"}, - {file = "ibm_db_sa-0.4.1.tar.gz", hash = "sha256:a46df130a3681646490925cf4e1bca12b46283f71eea39b70b4f9a56e95341ac"}, -] - -[package.dependencies] -ibm-db = ">=2.0.0" -sqlalchemy = ">=0.7.3" - -[[package]] -name = "identify" -version = "2.5.35" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[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.*" -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 = "7.0.2" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, - {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] - -[[package]] -name = "influxdb" -version = "5.3.1" -description = "InfluxDB client" -optional = true -python-versions = "*" -files = [ - {file = "influxdb-5.3.1-py2.py3-none-any.whl", hash = "sha256:65040a1f53d1a2a4f88a677e89e3a98189a7d30cf2ab61c318aaa89733280747"}, - {file = "influxdb-5.3.1.tar.gz", hash = "sha256:46f85e7b04ee4b3dee894672be6a295c94709003a7ddea8820deec2ac4d8b27a"}, -] - -[package.dependencies] -msgpack = "*" -python-dateutil = ">=2.6.0" -pytz = "*" -requests = ">=2.17.0" -six = ">=1.10.0" - -[package.extras] -test = ["mock", "nose", "nose-cov", "requests-mock"] - -[[package]] -name = "influxdb-client" -version = "1.41.0" -description = "InfluxDB 2.0 Python client library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "influxdb_client-1.41.0-py3-none-any.whl", hash = "sha256:420d7c5b7ed0f778de0eac923aded3ea3a4eb6b247e3fbb7a187e0a577a5a3be"}, - {file = "influxdb_client-1.41.0.tar.gz", hash = "sha256:4b85bad3991f3de24818366c87c8868a64917fea2d21bbcc2b579fbe5d904990"}, -] - -[package.dependencies] -certifi = ">=14.05.14" -python-dateutil = ">=2.5.3" -reactivex = ">=4.0.4" -setuptools = ">=21.0.0" -urllib3 = ">=1.26.0" - -[package.extras] -async = ["aiocsv (>=1.2.2)", "aiohttp (>=3.8.1)"] -ciso = ["ciso8601 (>=2.1.1)"] -extra = ["numpy", "pandas (>=1.0.0)"] -test = ["aioresponses (>=0.7.3)", "coverage (>=4.0.3)", "flake8 (>=5.0.3)", "httpretty (==1.0.5)", "jinja2 (==3.1.3)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "psutil (>=5.6.3)", "py (>=1.4.31)", "pytest (>=5.0.0)", "pytest-cov (>=3.0.0)", "pytest-timeout (>=2.1.0)", "randomize (>=0.13)", "sphinx (==1.8.5)", "sphinx-rtd-theme"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -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 = "isodate" -version = "0.6.1" -description = "An ISO 8601 date/time/duration parser and formatter" -optional = true -python-versions = "*" -files = [ - {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, - {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, -] - -[package.dependencies] -six = "*" - -[[package]] -name = "jaraco-classes" -version = "3.3.1" -description = "Utility functions for Python class constructs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jaraco.classes-3.3.1-py3-none-any.whl", hash = "sha256:86b534de565381f6b3c1c830d13f931d7be1a75f0081c57dff615578676e2206"}, - {file = "jaraco.classes-3.3.1.tar.gz", hash = "sha256:cb28a5ebda8bc47d8c8015307d93163464f9f2b91ab4006e09ff0ce07e8bfb30"}, -] - -[package.dependencies] -more-itertools = "*" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "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 = "jeepney" -version = "0.8.0" -description = "Low-level, pure Python DBus protocol wrapper." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, - {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, -] - -[package.extras] -test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] - -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jmespath" -version = "1.0.1" -description = "JSON Matching Expressions" -optional = true -python-versions = ">=3.7" -files = [ - {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, - {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, -] - -[[package]] -name = "jwcrypto" -version = "1.5.6" -description = "Implementation of JOSE Web standards" -optional = true -python-versions = ">= 3.8" -files = [ - {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, - {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, -] - -[package.dependencies] -cryptography = ">=3.4" -typing-extensions = ">=4.5.0" - -[[package]] -name = "kafka-python-ng" -version = "2.2.0" -description = "Pure Python client for Apache Kafka" -optional = false -python-versions = ">=3.8" -files = [ - {file = "kafka-python-ng-2.2.0.tar.gz", hash = "sha256:31d7082fd0ea78702a1eb3c20b5cbb3663d599d916d64a2a517a55ef7c9ebe58"}, - {file = "kafka_python_ng-2.2.0-py2.py3-none-any.whl", hash = "sha256:8f7f1f18ee0d09d905530e8990cf27b0cda0c05faf8098d74284c1069c5e6097"}, -] - -[package.extras] -boto = ["botocore"] -crc32c = ["crc32c"] -lz4 = ["lz4"] -snappy = ["python-snappy"] -zstd = ["zstandard"] - -[[package]] -name = "keyring" -version = "24.3.1" -description = "Store and access your passwords safely." -optional = false -python-versions = ">=3.8" -files = [ - {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, - {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} -"jaraco.classes" = "*" -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] -completion = ["shtab (>=1.1.0)"] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "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 = "kubernetes" -version = "29.0.0" -description = "Kubernetes python client" -optional = true -python-versions = ">=3.6" -files = [ - {file = "kubernetes-29.0.0-py2.py3-none-any.whl", hash = "sha256:ab8cb0e0576ccdfb71886366efb102c6a20f268d817be065ce7f9909c631e43e"}, - {file = "kubernetes-29.0.0.tar.gz", hash = "sha256:c4812e227ae74d07d53c88293e564e54b850452715a59a927e7e1bc6b9a60459"}, -] - -[package.dependencies] -certifi = ">=14.05.14" -google-auth = ">=1.0.1" -oauthlib = ">=3.2.2" -python-dateutil = ">=2.5.3" -pyyaml = ">=5.4.1" -requests = "*" -requests-oauthlib = "*" -six = ">=1.9.0" -urllib3 = ">=1.24.2" -websocket-client = ">=0.32.0,<0.40.0 || >0.40.0,<0.41.dev0 || >=0.43.dev0" - -[package.extras] -adal = ["adal (>=1.0.2)"] - -[[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" -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 = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "marshmallow" -version = "3.21.3" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -files = [ - {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, - {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] -docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] -tests = ["pytest", "pytz", "simplejson"] - -[[package]] -name = "mdurl" -version = "0.1.2" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -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 = "milvus-lite" -version = "2.4.7" -description = "A lightweight version of Milvus wrapped with Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "milvus_lite-2.4.7-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:c828190118b104b05b8c8e0b5a4147811c86b54b8fb67bc2e726ad10fc0b544e"}, - {file = "milvus_lite-2.4.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e1537633c39879714fb15082be56a4b97f74c905a6e98e302ec01320561081af"}, - {file = "milvus_lite-2.4.7-py3-none-manylinux2014_aarch64.whl", hash = "sha256:fcb909d38c83f21478ca9cb500c84264f988c69f62715ae9462e966767fb76dd"}, - {file = "milvus_lite-2.4.7-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f016474d663045787dddf1c3aad13b7d8b61fd329220318f858184918143dcbf"}, -] - -[[package]] -name = "minio" -version = "7.2.5" -description = "MinIO Python SDK for Amazon S3 Compatible Cloud Storage" -optional = true -python-versions = "*" -files = [ - {file = "minio-7.2.5-py3-none-any.whl", hash = "sha256:ed9176c96d4271cb1022b9ecb8a538b1e55b32ae06add6de16425cab99ef2304"}, - {file = "minio-7.2.5.tar.gz", hash = "sha256:59d8906e2da248a9caac34d4958a859cc3a44abbe6447910c82b5abfa9d6a2e1"}, -] - -[package.dependencies] -argon2-cffi = "*" -certifi = "*" -pycryptodome = "*" -typing-extensions = "*" -urllib3 = "*" - -[[package]] -name = "monotonic" -version = "1.6" -description = "An implementation of time.monotonic() for Python 2 & < 3.3" -optional = true -python-versions = "*" -files = [ - {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, - {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, -] - -[[package]] -name = "more-itertools" -version = "10.2.0" -description = "More routines for operating on iterables, beyond itertools" -optional = false -python-versions = ">=3.8" -files = [ - {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, - {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, -] - -[[package]] -name = "msgpack" -version = "1.0.8" -description = "MessagePack serializer" -optional = true -python-versions = ">=3.8" -files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, -] - -[[package]] -name = "mypy" -version = "1.7.1" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, - {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, - {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, - {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, - {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, - {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, - {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nats-py" -version = "2.7.2" -description = "NATS client for Python" -optional = true -python-versions = ">=3.7" -files = [ - {file = "nats-py-2.7.2.tar.gz", hash = "sha256:0c97b4a57bed0ef1ff9ae6c19bc115ec7ca8ede5ab3e001fd00a377056a547cf"}, -] - -[package.extras] -aiohttp = ["aiohttp"] -fast-parse = ["fast-mail-parser"] -nkeys = ["nkeys"] - -[[package]] -name = "neo4j" -version = "5.18.0" -description = "Neo4j Bolt driver for Python" -optional = true -python-versions = ">=3.7" -files = [ - {file = "neo4j-5.18.0.tar.gz", hash = "sha256:4014406ae5b8b485a8ba46c9f00b6f5b4aaf88e7c3a50603445030c2aab701c9"}, -] - -[package.dependencies] -pytz = "*" - -[package.extras] -numpy = ["numpy (>=1.7.0,<2.0.0)"] -pandas = ["numpy (>=1.7.0,<2.0.0)", "pandas (>=1.1.0,<3.0.0)"] -pyarrow = ["pyarrow (>=1.0.0)"] - -[[package]] -name = "nh3" -version = "0.2.15" -description = "Python bindings to the ammonia HTML sanitization library." -optional = false -python-versions = "*" -files = [ - {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9c0d415f6b7f2338f93035bba5c0d8c1b464e538bfbb1d598acd47d7969284f0"}, - {file = "nh3-0.2.15-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6f42f99f0cf6312e470b6c09e04da31f9abaadcd3eb591d7d1a88ea931dca7f3"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac19c0d68cd42ecd7ead91a3a032fdfff23d29302dbb1311e641a130dfefba97"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0d77272ce6d34db6c87b4f894f037d55183d9518f948bba236fe81e2bb4e28"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8d595df02413aa38586c24811237e95937ef18304e108b7e92c890a06793e3bf"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86e447a63ca0b16318deb62498db4f76fc60699ce0a1231262880b38b6cff911"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3277481293b868b2715907310c7be0f1b9d10491d5adf9fce11756a97e97eddf"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60684857cfa8fdbb74daa867e5cad3f0c9789415aba660614fe16cd66cbb9ec7"}, - {file = "nh3-0.2.15-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b803a5875e7234907f7d64777dfde2b93db992376f3d6d7af7f3bc347deb305"}, - {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d02d0ff79dfd8208ed25a39c12cbda092388fff7f1662466e27d97ad011b770"}, - {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f3b53ba93bb7725acab1e030bc2ecd012a817040fd7851b332f86e2f9bb98dc6"}, - {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b1e97221cedaf15a54f5243f2c5894bb12ca951ae4ddfd02a9d4ea9df9e1a29d"}, - {file = "nh3-0.2.15-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a5167a6403d19c515217b6bcaaa9be420974a6ac30e0da9e84d4fc67a5d474c5"}, - {file = "nh3-0.2.15-cp37-abi3-win32.whl", hash = "sha256:427fecbb1031db085eaac9931362adf4a796428ef0163070c484b5a768e71601"}, - {file = "nh3-0.2.15-cp37-abi3-win_amd64.whl", hash = "sha256:bc2d086fb540d0fa52ce35afaded4ea526b8fc4d3339f783db55c95de40ef02e"}, - {file = "nh3-0.2.15.tar.gz", hash = "sha256:d1e30ff2d8d58fb2a14961f7aac1bbb1c51f9bdd7da727be35c63826060b0bf3"}, -] - -[[package]] -name = "nodeenv" -version = "1.8.0" -description = "Node.js virtual environment builder" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "oauthlib" -version = "3.2.2" -description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" -optional = true -python-versions = ">=3.6" -files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, -] - -[package.extras] -rsa = ["cryptography (>=3.0.0)"] -signals = ["blinker (>=1.4.0)"] -signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] - -[[package]] -name = "opensearch-py" -version = "2.4.2" -description = "Python client for OpenSearch" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -files = [ - {file = "opensearch-py-2.4.2.tar.gz", hash = "sha256:564f175af134aa885f4ced6846eb4532e08b414fff0a7976f76b276fe0e69158"}, - {file = "opensearch_py-2.4.2-py2.py3-none-any.whl", hash = "sha256:7867319132133e2974c09f76a54eb1d502b989229be52da583d93ddc743ea111"}, -] - -[package.dependencies] -certifi = ">=2022.12.07" -python-dateutil = "*" -requests = ">=2.4.0,<3.0.0" -six = "*" -urllib3 = ">=1.26.18" - -[package.extras] -async = ["aiohttp (>=3,<4)"] -develop = ["black", "botocore", "coverage (<8.0.0)", "jinja2", "mock", "myst-parser", "pytest (>=3.0.0)", "pytest-cov", "pytest-mock (<4.0.0)", "pytz", "pyyaml", "requests (>=2.0.0,<3.0.0)", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -docs = ["aiohttp (>=3,<4)", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-rtd-theme"] -kerberos = ["requests-kerberos"] - -[[package]] -name = "opentelemetry-api" -version = "1.16.0" -description = "OpenTelemetry Python API" -optional = true -python-versions = ">=3.7" -files = [ - {file = "opentelemetry_api-1.16.0-py3-none-any.whl", hash = "sha256:79e8f0cf88dbdd36b6abf175d2092af1efcaa2e71552d0d2b3b181a9707bf4bc"}, - {file = "opentelemetry_api-1.16.0.tar.gz", hash = "sha256:4b0e895a3b1f5e1908043ebe492d33e33f9ccdbe6d02d3994c2f8721a63ddddb"}, -] - -[package.dependencies] -deprecated = ">=1.2.6" -setuptools = ">=16.0" - -[[package]] -name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.16.0" -description = "OpenTelemetry Collector Protobuf over gRPC Exporter" -optional = true -python-versions = ">=3.7" -files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.16.0-py3-none-any.whl", hash = "sha256:ace2cedc43bc30e1b2475b14f72acf1a1528716965209d31fb0a72c59f0f4fe4"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.16.0.tar.gz", hash = "sha256:0853ea1e566c1fab5633e7f7bca2a650ba445b04ba02f93173920b0f5c561f63"}, -] - -[package.dependencies] -backoff = {version = ">=1.10.0,<3.0.0", markers = "python_version >= \"3.7\""} -googleapis-common-protos = ">=1.52,<2.0" -grpcio = ">=1.0.0,<2.0.0" -opentelemetry-api = ">=1.15,<2.0" -opentelemetry-proto = "1.16.0" -opentelemetry-sdk = ">=1.16.0,<1.17.0" - -[package.extras] -test = ["pytest-grpc"] - -[[package]] -name = "opentelemetry-proto" -version = "1.16.0" -description = "OpenTelemetry Python Proto" -optional = true -python-versions = ">=3.7" -files = [ - {file = "opentelemetry_proto-1.16.0-py3-none-any.whl", hash = "sha256:160326d300faf43c3f72c4a916516ee5b63289ceb9828294b698ef943697cbd5"}, - {file = "opentelemetry_proto-1.16.0.tar.gz", hash = "sha256:e58832dfec64621972a9836f8ae163fb3063946eb02bdf43fae0f76f8cf46d0a"}, -] - -[package.dependencies] -protobuf = ">=3.19,<5.0" - -[[package]] -name = "opentelemetry-sdk" -version = "1.16.0" -description = "OpenTelemetry Python SDK" -optional = true -python-versions = ">=3.7" -files = [ - {file = "opentelemetry_sdk-1.16.0-py3-none-any.whl", hash = "sha256:15f03915eec4839f885a5e6ed959cde59b8690c8c012d07c95b4b138c98dc43f"}, - {file = "opentelemetry_sdk-1.16.0.tar.gz", hash = "sha256:4d3bb91e9e209dbeea773b5565d901da4f76a29bf9dbc1c9500be3cabb239a4e"}, -] - -[package.dependencies] -opentelemetry-api = "1.16.0" -opentelemetry-semantic-conventions = "0.37b0" -setuptools = ">=16.0" -typing-extensions = ">=3.7.4" - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.37b0" -description = "OpenTelemetry Semantic Conventions" -optional = true -python-versions = ">=3.7" -files = [ - {file = "opentelemetry_semantic_conventions-0.37b0-py3-none-any.whl", hash = "sha256:462982278a42dab01f68641cd89f8460fe1f93e87c68a012a76fb426dcdba5ee"}, - {file = "opentelemetry_semantic_conventions-0.37b0.tar.gz", hash = "sha256:087ce2e248e42f3ffe4d9fa2303111de72bb93baa06a0f4655980bc1557c4228"}, -] - -[[package]] -name = "oracledb" -version = "2.1.1" -description = "Python interface to Oracle Database" -optional = true -python-versions = ">=3.7" -files = [ - {file = "oracledb-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0666088fcca29cfe8a8428888426655d2f7417adfb854ad0af79c40f1bae59aa"}, - {file = "oracledb-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8651ea5b9ef35493aa1bb80786458a26df3d728eb3e55b6bb9ddf2aa83f45be8"}, - {file = "oracledb-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06ede73b90c24387b47094aadae477a60e9c076d04275a7a5ae9deef0991e3a2"}, - {file = "oracledb-2.1.1-cp310-cp310-win32.whl", hash = "sha256:962e0fce942eefafe8c52481c1979dd53c6929d479544499d2e32a1eb4111837"}, - {file = "oracledb-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:ec1012362c6ba5a465d87730c14bbbc3bac3a0799cd4371c6514364f2121dd42"}, - {file = "oracledb-2.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a94793243936117b4a8ea4a17672aa77f71c9063a176f0330ea733a48679d82c"}, - {file = "oracledb-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cc8e853d33333c5e3cd4c2db0591c374c5fe06ebd713748b65c59be5b252054"}, - {file = "oracledb-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d29cb729cd94e7afbfbef8f0d272bd8e20b16be77ae747686b677e9e9df0ad3f"}, - {file = "oracledb-2.1.1-cp311-cp311-win32.whl", hash = "sha256:c0c29bdafe78d412bec20d91791dc91fc7feb9b63a39138a3ebd5835abf3639d"}, - {file = "oracledb-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:d539300667bf7af839baddb42896175c14d872cc0d903dace7850e91d90387f1"}, - {file = "oracledb-2.1.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:166216272dcaaa2647fde3b7e13f9d3ffe0f05dc70791aacf7ba5e2c65c96aca"}, - {file = "oracledb-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb64b15c75b4d62fa060516d6bbd9f8f42272d677a48f6293597945836e9f0d"}, - {file = "oracledb-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c16782bc37087947052f3d398be6e7d4bb3143a6863a5a1517f63c92c1b2b99"}, - {file = "oracledb-2.1.1-cp312-cp312-win32.whl", hash = "sha256:50791fadf26e97a8cedcc1bf16ed5c5f0f2f1a0254e3725889e52eba1adc8f22"}, - {file = "oracledb-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:29b000e4892f6c4eac6abe099bc43df9ccd586d9a02fa6391e0bd8e4eb88df37"}, - {file = "oracledb-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:48057c0309d2e5f4b78804bd73b4e32654ff912515da17163262142a5f451e0e"}, - {file = "oracledb-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc53191a389a2b6119488635c63e626e76df3394403d0e907260de9ad01c0a6c"}, - {file = "oracledb-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0750de1b381f6d3508904e079491a99d61742db109131dc664d4b3d146ee6f17"}, - {file = "oracledb-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:7262457d256c30a738cf3dde4ec175d2d382c6c7b9e3395665c04c25b34d28b0"}, - {file = "oracledb-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ec05d1a56e9f45741ed60e7c24a8c9cc0f1982241aa555d57b04ed0879b59228"}, - {file = "oracledb-2.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2611a9bd530829ba2332db8a7083699579263cf3fb1093b0e1f5b5a57239e18e"}, - {file = "oracledb-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bba26b23d3e2905eb4c7627a9a1c31ad72c64442e184173c48f19bde2a92184"}, - {file = "oracledb-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e7f9cb56e2d79a11f4b5a9233067b9eb327a30e6e8dad8349b02eca64482ab5"}, - {file = "oracledb-2.1.1-cp38-cp38-win32.whl", hash = "sha256:1658760585f8c41e6530e25332ccdde3ca0b95b4e7c2a9a69719da0420da7fe6"}, - {file = "oracledb-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:bc013fac5009e6ebc65be58e7d3645aadd04ef8627553cdfe6e66a157963087a"}, - {file = "oracledb-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:479bcec29826ca2a49d1b113ca343ad31db8b94063d50223ab8d32c069faf4b3"}, - {file = "oracledb-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c7b0aa7038d7444ea133a2439dc9b645fb5c81832dd24818c181494423f752"}, - {file = "oracledb-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce1f93bea32abd2d3dcfc17ed0546a20d36bc7a92b36ced58b525165fd63c8a"}, - {file = "oracledb-2.1.1-cp39-cp39-win32.whl", hash = "sha256:95c581d4df786d3aa6e2e47c8519b9036b28c1ea1416cf843b9572ba611a54d6"}, - {file = "oracledb-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:1bf3ba201f21e8183a33b32ec1224b5d65b98ac0b1bf843b98b0b65ef34c1b26"}, - {file = "oracledb-2.1.1.tar.gz", hash = "sha256:e2e817cfa6dff36c7131736f34e2aaec75d726fd38d1910d8318061a278228fa"}, -] - -[package.dependencies] -cryptography = ">=3.2.1" - -[[package]] -name = "orjson" -version = "3.10.0" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = true -python-versions = ">=3.8" -files = [ - {file = "orjson-3.10.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47af5d4b850a2d1328660661f0881b67fdbe712aea905dadd413bdea6f792c33"}, - {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c90681333619d78360d13840c7235fdaf01b2b129cb3a4f1647783b1971542b6"}, - {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:400c5b7c4222cb27b5059adf1fb12302eebcabf1978f33d0824aa5277ca899bd"}, - {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5dcb32e949eae80fb335e63b90e5808b4b0f64e31476b3777707416b41682db5"}, - {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7d507c7493252c0a0264b5cc7e20fa2f8622b8a83b04d819b5ce32c97cf57b"}, - {file = "orjson-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e286a51def6626f1e0cc134ba2067dcf14f7f4b9550f6dd4535fd9d79000040b"}, - {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8acd4b82a5f3a3ec8b1dc83452941d22b4711964c34727eb1e65449eead353ca"}, - {file = "orjson-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:30707e646080dd3c791f22ce7e4a2fc2438765408547c10510f1f690bd336217"}, - {file = "orjson-3.10.0-cp310-none-win32.whl", hash = "sha256:115498c4ad34188dcb73464e8dc80e490a3e5e88a925907b6fedcf20e545001a"}, - {file = "orjson-3.10.0-cp310-none-win_amd64.whl", hash = "sha256:6735dd4a5a7b6df00a87d1d7a02b84b54d215fb7adac50dd24da5997ffb4798d"}, - {file = "orjson-3.10.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9587053e0cefc284e4d1cd113c34468b7d3f17666d22b185ea654f0775316a26"}, - {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bef1050b1bdc9ea6c0d08468e3e61c9386723633b397e50b82fda37b3563d72"}, - {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d16c6963ddf3b28c0d461641517cd312ad6b3cf303d8b87d5ef3fa59d6844337"}, - {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4251964db47ef090c462a2d909f16c7c7d5fe68e341dabce6702879ec26d1134"}, - {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73bbbdc43d520204d9ef0817ac03fa49c103c7f9ea94f410d2950755be2c349c"}, - {file = "orjson-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:414e5293b82373606acf0d66313aecb52d9c8c2404b1900683eb32c3d042dbd7"}, - {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:feaed5bb09877dc27ed0d37f037ddef6cb76d19aa34b108db270d27d3d2ef747"}, - {file = "orjson-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5127478260db640323cea131ee88541cb1a9fbce051f0b22fa2f0892f44da302"}, - {file = "orjson-3.10.0-cp311-none-win32.whl", hash = "sha256:b98345529bafe3c06c09996b303fc0a21961820d634409b8639bc16bd4f21b63"}, - {file = "orjson-3.10.0-cp311-none-win_amd64.whl", hash = "sha256:658ca5cee3379dd3d37dbacd43d42c1b4feee99a29d847ef27a1cb18abdfb23f"}, - {file = "orjson-3.10.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4329c1d24fd130ee377e32a72dc54a3c251e6706fccd9a2ecb91b3606fddd998"}, - {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef0f19fdfb6553342b1882f438afd53c7cb7aea57894c4490c43e4431739c700"}, - {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c4f60db24161534764277f798ef53b9d3063092f6d23f8f962b4a97edfa997a0"}, - {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1de3fd5c7b208d836f8ecb4526995f0d5877153a4f6f12f3e9bf11e49357de98"}, - {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f93e33f67729d460a177ba285002035d3f11425ed3cebac5f6ded4ef36b28344"}, - {file = "orjson-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:237ba922aef472761acd697eef77fef4831ab769a42e83c04ac91e9f9e08fa0e"}, - {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98c1bfc6a9bec52bc8f0ab9b86cc0874b0299fccef3562b793c1576cf3abb570"}, - {file = "orjson-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30d795a24be16c03dca0c35ca8f9c8eaaa51e3342f2c162d327bd0225118794a"}, - {file = "orjson-3.10.0-cp312-none-win32.whl", hash = "sha256:6a3f53dc650bc860eb26ec293dfb489b2f6ae1cbfc409a127b01229980e372f7"}, - {file = "orjson-3.10.0-cp312-none-win_amd64.whl", hash = "sha256:983db1f87c371dc6ffc52931eb75f9fe17dc621273e43ce67bee407d3e5476e9"}, - {file = "orjson-3.10.0-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a667769a96a72ca67237224a36faf57db0c82ab07d09c3aafc6f956196cfa1b"}, - {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade1e21dfde1d37feee8cf6464c20a2f41fa46c8bcd5251e761903e46102dc6b"}, - {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23c12bb4ced1c3308eff7ba5c63ef8f0edb3e4c43c026440247dd6c1c61cea4b"}, - {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2d014cf8d4dc9f03fc9f870de191a49a03b1bcda51f2a957943fb9fafe55aac"}, - {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eadecaa16d9783affca33597781328e4981b048615c2ddc31c47a51b833d6319"}, - {file = "orjson-3.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd583341218826f48bd7c6ebf3310b4126216920853cbc471e8dbeaf07b0b80e"}, - {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90bfc137c75c31d32308fd61951d424424426ddc39a40e367704661a9ee97095"}, - {file = "orjson-3.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13b5d3c795b09a466ec9fcf0bd3ad7b85467d91a60113885df7b8d639a9d374b"}, - {file = "orjson-3.10.0-cp38-none-win32.whl", hash = "sha256:5d42768db6f2ce0162544845facb7c081e9364a5eb6d2ef06cd17f6050b048d8"}, - {file = "orjson-3.10.0-cp38-none-win_amd64.whl", hash = "sha256:33e6655a2542195d6fd9f850b428926559dee382f7a862dae92ca97fea03a5ad"}, - {file = "orjson-3.10.0-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4050920e831a49d8782a1720d3ca2f1c49b150953667eed6e5d63a62e80f46a2"}, - {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1897aa25a944cec774ce4a0e1c8e98fb50523e97366c637b7d0cddabc42e6643"}, - {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bf565a69e0082ea348c5657401acec3cbbb31564d89afebaee884614fba36b4"}, - {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6ebc17cfbbf741f5c1a888d1854354536f63d84bee537c9a7c0335791bb9009"}, - {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2817877d0b69f78f146ab305c5975d0618df41acf8811249ee64231f5953fee"}, - {file = "orjson-3.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57d017863ec8aa4589be30a328dacd13c2dc49de1c170bc8d8c8a98ece0f2925"}, - {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:22c2f7e377ac757bd3476ecb7480c8ed79d98ef89648f0176deb1da5cd014eb7"}, - {file = "orjson-3.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e62ba42bfe64c60c1bc84799944f80704e996592c6b9e14789c8e2a303279912"}, - {file = "orjson-3.10.0-cp39-none-win32.whl", hash = "sha256:60c0b1bdbccd959ebd1575bd0147bd5e10fc76f26216188be4a36b691c937077"}, - {file = "orjson-3.10.0-cp39-none-win_amd64.whl", hash = "sha256:175a41500ebb2fdf320bf78e8b9a75a1279525b62ba400b2b2444e274c2c8bee"}, - {file = "orjson-3.10.0.tar.gz", hash = "sha256:ba4d8cac5f2e2cff36bea6b6481cdb92b38c202bcec603d6f5ff91960595a1ed"}, -] - -[[package]] -name = "outcome" -version = "1.3.0.post0" -description = "Capture the outcome of Python function calls." -optional = true -python-versions = ">=3.7" -files = [ - {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, - {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, -] - -[package.dependencies] -attrs = ">=19.2.0" - -[[package]] -name = "overrides" -version = "7.7.0" -description = "A decorator to automatically detect mismatch when overriding a method." -optional = true -python-versions = ">=3.6" -files = [ - {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, - {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, -] - -[[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, -] - -[[package]] -name = "paho-mqtt" -version = "2.1.0" -description = "MQTT version 5.0/3.1.1 client class" -optional = false -python-versions = ">=3.7" -files = [ - {file = "paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee"}, - {file = "paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834"}, -] - -[package.extras] -proxy = ["pysocks"] - -[[package]] -name = "pandas" -version = "2.2.2" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "paramiko" -version = "3.4.0" -description = "SSH2 protocol library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, - {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, -] - -[package.dependencies] -bcrypt = ">=3.2" -cryptography = ">=3.3" -pynacl = ">=1.5" - -[package.extras] -all = ["gssapi (>=1.4.1)", "invoke (>=2.0)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] -invoke = ["invoke (>=2.0)"] - -[[package]] -name = "pg8000" -version = "1.30.5" -description = "PostgreSQL interface library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pg8000-1.30.5-py3-none-any.whl", hash = "sha256:1abf18da652b0ad8e9cbfe57ed841c350b5330c33d8151303555db1fe5ce57f8"}, - {file = "pg8000-1.30.5.tar.gz", hash = "sha256:072f7ad00cd723695cb2e9fc02c1dfb84c781455e97b8de6f4c4281eea08078c"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.2" -scramp = ">=1.4.4" - -[[package]] -name = "pika" -version = "1.3.2" -description = "Pika Python AMQP Client Library" -optional = true -python-versions = ">=3.7" -files = [ - {file = "pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f"}, - {file = "pika-1.3.2.tar.gz", hash = "sha256:b2a327ddddf8570b4965b3576ac77091b850262d34ce8c1d8cb4e4146aa4145f"}, -] - -[package.extras] -gevent = ["gevent"] -tornado = ["tornado"] -twisted = ["twisted"] - -[[package]] -name = "pkginfo" -version = "1.10.0" -description = "Query metadata from sdists / bdists / installed packages." -optional = false -python-versions = ">=3.6" -files = [ - {file = "pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097"}, - {file = "pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297"}, -] - -[package.extras] -testing = ["pytest", "pytest-cov", "wheel"] - -[[package]] -name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] - -[[package]] -name = "pluggy" -version = "1.4.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "portalocker" -version = "2.8.2" -description = "Wraps the portalocker recipe for easy usage" -optional = true -python-versions = ">=3.8" -files = [ - {file = "portalocker-2.8.2-py3-none-any.whl", hash = "sha256:cfb86acc09b9aa7c3b43594e19be1345b9d16af3feb08bf92f23d4dce513a28e"}, - {file = "portalocker-2.8.2.tar.gz", hash = "sha256:2b035aa7828e46c58e9b31390ee1f169b98e1066ab10b9a6a861fe7e25ee4f33"}, -] - -[package.dependencies] -pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} - -[package.extras] -docs = ["sphinx (>=1.7.1)"] -redis = ["redis"] -tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=6.0.0)", "types-redis"] - -[[package]] -name = "posthog" -version = "3.5.0" -description = "Integrate PostHog into any python application." -optional = true -python-versions = "*" -files = [ - {file = "posthog-3.5.0-py2.py3-none-any.whl", hash = "sha256:3c672be7ba6f95d555ea207d4486c171d06657eb34b3ce25eb043bfe7b6b5b76"}, - {file = "posthog-3.5.0.tar.gz", hash = "sha256:8f7e3b2c6e8714d0c0c542a2109b83a7549f63b7113a133ab2763a89245ef2ef"}, -] - -[package.dependencies] -backoff = ">=1.10.0" -monotonic = ">=1.5" -python-dateutil = ">2.1" -requests = ">=2.7,<3.0" -six = ">=1.5" - -[package.extras] -dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"] -sentry = ["django", "sentry-sdk"] -test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest", "pytest-timeout"] - -[[package]] -name = "pre-commit" -version = "3.6.2" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, - {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "proto-plus" -version = "1.23.0" -description = "Beautiful, Pythonic protocol buffers." -optional = true -python-versions = ">=3.6" -files = [ - {file = "proto-plus-1.23.0.tar.gz", hash = "sha256:89075171ef11988b3fa157f5dbd8b9cf09d65fffee97e29ce403cd8defba19d2"}, - {file = "proto_plus-1.23.0-py3-none-any.whl", hash = "sha256:a829c79e619e1cf632de091013a4173deed13a55f326ef84f05af6f50ff4c82c"}, -] - -[package.dependencies] -protobuf = ">=3.19.0,<5.0.0dev" - -[package.extras] -testing = ["google-api-core[grpc] (>=1.31.5)"] - -[[package]] -name = "protobuf" -version = "4.25.3" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, - {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, - {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, - {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, - {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, - {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, - {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, - {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, - {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, - {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, -] - -[[package]] -name = "psycopg" -version = "3.1.18" -description = "PostgreSQL database adapter for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"}, - {file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"}, -] - -[package.dependencies] -typing-extensions = ">=4.1" -tzdata = {version = "*", markers = "sys_platform == \"win32\""} - -[package.extras] -binary = ["psycopg-binary (==3.1.18)"] -c = ["psycopg-c (==3.1.18)"] -dev = ["black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "mypy (>=1.4.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"] -docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] -pool = ["psycopg-pool"] -test = ["anyio (>=3.6.2,<4.0)", "mypy (>=1.4.1)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] - -[[package]] -name = "psycopg2-binary" -version = "2.9.9" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, - {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, - {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, - {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, - {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, - {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, - {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, -] - -[[package]] -name = "pyasn1" -version = "0.5.1" -description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "pyasn1-0.5.1-py2.py3-none-any.whl", hash = "sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58"}, - {file = "pyasn1-0.5.1.tar.gz", hash = "sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c"}, -] - -[[package]] -name = "pyasn1-modules" -version = "0.3.0" -description = "A collection of ASN.1-based protocols modules" -optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "pyasn1_modules-0.3.0-py2.py3-none-any.whl", hash = "sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d"}, - {file = "pyasn1_modules-0.3.0.tar.gz", hash = "sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c"}, -] - -[package.dependencies] -pyasn1 = ">=0.4.6,<0.6.0" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] - -[[package]] -name = "pycryptodome" -version = "3.20.0" -description = "Cryptographic library for Python" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, - {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, -] - -[[package]] -name = "pydantic" -version = "2.6.4" -description = "Data validation using Python type hints" -optional = true -python-versions = ">=3.8" -files = [ - {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, - {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, -] - -[package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.16.3" -typing-extensions = ">=4.6.1" - -[package.extras] -email = ["email-validator (>=2.0.0)"] - -[[package]] -name = "pydantic-core" -version = "2.16.3" -description = "" -optional = true -python-versions = ">=3.8" -files = [ - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, - {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, - {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, - {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, - {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, - {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, - {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, - {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, - {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, - {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, - {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, - {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, - {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, - {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, -] - -[package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" - -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = true -python-versions = ">=3.7" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pymilvus" -version = "2.4.3" -description = "Python Sdk for Milvus" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pymilvus-2.4.3-py3-none-any.whl", hash = "sha256:38239e89f8d739f665141d0b80908990b5f59681e889e135c234a4a45669a5c8"}, - {file = "pymilvus-2.4.3.tar.gz", hash = "sha256:703ac29296cdce03d6dc2aaebbe959e57745c141a94150e371dc36c61c226cc1"}, -] - -[package.dependencies] -environs = "<=9.5.0" -grpcio = ">=1.49.1,<=1.63.0" -milvus-lite = ">=2.4.0,<2.5.0" -pandas = ">=1.2.4" -protobuf = ">=3.20.0" -setuptools = ">=67" -ujson = ">=2.0.0" - -[package.extras] -bulk-writer = ["azure-storage-blob", "minio (>=7.0.0)", "pyarrow (>=12.0.0)", "requests"] -dev = ["black", "grpcio (==1.62.2)", "grpcio-testing (==1.62.2)", "grpcio-tools (==1.62.2)", "pytest (>=5.3.4)", "pytest-cov (>=2.8.1)", "pytest-timeout (>=1.3.4)", "ruff (>0.4.0)"] -model = ["milvus-model (>=0.1.0)"] - -[[package]] -name = "pymongo" -version = "4.6.2" -description = "Python driver for MongoDB " -optional = true -python-versions = ">=3.7" -files = [ - {file = "pymongo-4.6.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7640d176ee5b0afec76a1bda3684995cb731b2af7fcfd7c7ef8dc271c5d689af"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux1_i686.whl", hash = "sha256:4e2129ec8f72806751b621470ac5d26aaa18fae4194796621508fa0e6068278a"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:c43205e85cbcbdf03cff62ad8f50426dd9d20134a915cfb626d805bab89a1844"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:91ddf95cedca12f115fbc5f442b841e81197d85aa3cc30b82aee3635a5208af2"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:0fbdbf2fba1b4f5f1522e9f11e21c306e095b59a83340a69e908f8ed9b450070"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:097791d5a8d44e2444e0c8c4d6e14570ac11e22bcb833808885a5db081c3dc2a"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:e0b208ebec3b47ee78a5c836e2e885e8c1e10f8ffd101aaec3d63997a4bdcd04"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1849fd6f1917b4dc5dbf744b2f18e41e0538d08dd8e9ba9efa811c5149d665a3"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa0bbbfbd1f8ebbd5facaa10f9f333b20027b240af012748555148943616fdf3"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4522ad69a4ab0e1b46a8367d62ad3865b8cd54cf77518c157631dac1fdc97584"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397949a9cc85e4a1452f80b7f7f2175d557237177120954eff00bf79553e89d3"}, - {file = "pymongo-4.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d511db310f43222bc58d811037b176b4b88dc2b4617478c5ef01fea404f8601"}, - {file = "pymongo-4.6.2-cp310-cp310-win32.whl", hash = "sha256:991e406db5da4d89fb220a94d8caaf974ffe14ce6b095957bae9273c609784a0"}, - {file = "pymongo-4.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:94637941fe343000f728e28d3fe04f1f52aec6376b67b85583026ff8dab2a0e0"}, - {file = "pymongo-4.6.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84593447a5c5fe7a59ba86b72c2c89d813fbac71c07757acdf162fbfd5d005b9"}, - {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aebddb2ec2128d5fc2fe3aee6319afef8697e0374f8a1fcca3449d6f625e7b4"}, - {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f706c1a644ed33eaea91df0a8fb687ce572b53eeb4ff9b89270cb0247e5d0e1"}, - {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18c422e6b08fa370ed9d8670c67e78d01f50d6517cec4522aa8627014dfa38b6"}, - {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d002ae456a15b1d790a78bb84f87af21af1cb716a63efb2c446ab6bcbbc48ca"}, - {file = "pymongo-4.6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f86ba0c781b497a3c9c886765d7b6402a0e3ae079dd517365044c89cd7abb06"}, - {file = "pymongo-4.6.2-cp311-cp311-win32.whl", hash = "sha256:ac20dd0c7b42555837c86f5ea46505f35af20a08b9cf5770cd1834288d8bd1b4"}, - {file = "pymongo-4.6.2-cp311-cp311-win_amd64.whl", hash = "sha256:e78af59fd0eb262c2a5f7c7d7e3b95e8596a75480d31087ca5f02f2d4c6acd19"}, - {file = "pymongo-4.6.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6125f73503407792c8b3f80165f8ab88a4e448d7d9234c762681a4d0b446fcb4"}, - {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba052446a14bd714ec83ca4e77d0d97904f33cd046d7bb60712a6be25eb31dbb"}, - {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b65433c90e07dc252b4a55dfd885ca0df94b1cf77c5b8709953ec1983aadc03"}, - {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2160d9c8cd20ce1f76a893f0daf7c0d38af093f36f1b5c9f3dcf3e08f7142814"}, - {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f251f287e6d42daa3654b686ce1fcb6d74bf13b3907c3ae25954978c70f2cd4"}, - {file = "pymongo-4.6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d227a60b00925dd3aeae4675575af89c661a8e89a1f7d1677e57eba4a3693c"}, - {file = "pymongo-4.6.2-cp312-cp312-win32.whl", hash = "sha256:311794ef3ccae374aaef95792c36b0e5c06e8d5cf04a1bdb1b2bf14619ac881f"}, - {file = "pymongo-4.6.2-cp312-cp312-win_amd64.whl", hash = "sha256:f673b64a0884edcc56073bda0b363428dc1bf4eb1b5e7d0b689f7ec6173edad6"}, - {file = "pymongo-4.6.2-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:fe010154dfa9e428bd2fb3e9325eff2216ab20a69ccbd6b5cac6785ca2989161"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1f5f4cd2969197e25b67e24d5b8aa2452d381861d2791d06c493eaa0b9c9fcfe"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c9519c9d341983f3a1bd19628fecb1d72a48d8666cf344549879f2e63f54463b"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:c68bf4a399e37798f1b5aa4f6c02886188ef465f4ac0b305a607b7579413e366"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:a509db602462eb736666989739215b4b7d8f4bb8ac31d0bffd4be9eae96c63ef"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:362a5adf6f3f938a8ff220a4c4aaa93e84ef932a409abecd837c617d17a5990f"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:ee30a9d4c27a88042d0636aca0275788af09cc237ae365cd6ebb34524bddb9cc"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:477914e13501bb1d4608339ee5bb618be056d2d0e7267727623516cfa902e652"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd343ca44982d480f1e39372c48e8e263fc6f32e9af2be456298f146a3db715"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3797e0a628534e07a36544d2bfa69e251a578c6d013e975e9e3ed2ac41f2d95"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97d81d357e1a2a248b3494d52ebc8bf15d223ee89d59ee63becc434e07438a24"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed694c0d1977cb54281cb808bc2b247c17fb64b678a6352d3b77eb678ebe1bd9"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ceaaff4b812ae368cf9774989dea81b9bbb71e5bed666feca6a9f3087c03e49"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7dd63f7c2b3727541f7f37d0fb78d9942eb12a866180fbeb898714420aad74e2"}, - {file = "pymongo-4.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e571434633f99a81e081738721bb38e697345281ed2f79c2f290f809ba3fbb2f"}, - {file = "pymongo-4.6.2-cp37-cp37m-win32.whl", hash = "sha256:3e9f6e2f3da0a6af854a3e959a6962b5f8b43bbb8113cd0bff0421c5059b3106"}, - {file = "pymongo-4.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3a5280f496297537301e78bde250c96fadf4945e7b2c397d8bb8921861dd236d"}, - {file = "pymongo-4.6.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:5f6bcd2d012d82d25191a911a239fd05a8a72e8c5a7d81d056c0f3520cad14d1"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:4fa30494601a6271a8b416554bd7cde7b2a848230f0ec03e3f08d84565b4bf8c"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bea62f03a50f363265a7a651b4e2a4429b4f138c1864b2d83d4bf6f9851994be"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b2d445f1cf147331947cc35ec10342f898329f29dd1947a3f8aeaf7e0e6878d1"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:5db133d6ec7a4f7fc7e2bd098e4df23d7ad949f7be47b27b515c9fb9301c61e4"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:9eec7140cf7513aa770ea51505d312000c7416626a828de24318fdcc9ac3214c"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:5379ca6fd325387a34cda440aec2bd031b5ef0b0aa2e23b4981945cff1dab84c"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:579508536113dbd4c56e4738955a18847e8a6c41bf3c0b4ab18b51d81a6b7be8"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bae553ca39ed52db099d76acd5e8566096064dc7614c34c9359bb239ec4081"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0257e0eebb50f242ca28a92ef195889a6ad03dcdde5bf1c7ab9f38b7e810801"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbafe3a1df21eeadb003c38fc02c1abf567648b6477ec50c4a3c042dca205371"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaecfafb407feb6f562c7f2f5b91f22bfacba6dd739116b1912788cff7124c4a"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e942945e9112075a84d2e2d6e0d0c98833cdcdfe48eb8952b917f996025c7ffa"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f7b98f8d2cf3eeebde738d080ae9b4276d7250912d9751046a9ac1efc9b1ce2"}, - {file = "pymongo-4.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8110b78fc4b37dced85081d56795ecbee6a7937966e918e05e33a3900e8ea07d"}, - {file = "pymongo-4.6.2-cp38-cp38-win32.whl", hash = "sha256:df813f0c2c02281720ccce225edf39dc37855bf72cdfde6f789a1d1cf32ffb4b"}, - {file = "pymongo-4.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:64ec3e2dcab9af61bdbfcb1dd863c70d1b0c220b8e8ac11df8b57f80ee0402b3"}, - {file = "pymongo-4.6.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bff601fbfcecd2166d9a2b70777c2985cb9689e2befb3278d91f7f93a0456cae"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:f1febca6f79e91feafc572906871805bd9c271b6a2d98a8bb5499b6ace0befed"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d788cb5cc947d78934be26eef1623c78cec3729dc93a30c23f049b361aa6d835"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5c2f258489de12a65b81e1b803a531ee8cf633fa416ae84de65cd5f82d2ceb37"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:fb24abcd50501b25d33a074c1790a1389b6460d2509e4b240d03fd2e5c79f463"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:4d982c6db1da7cf3018183891883660ad085de97f21490d314385373f775915b"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:b2dd8c874927a27995f64a3b44c890e8a944c98dec1ba79eab50e07f1e3f801b"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:4993593de44c741d1e9f230f221fe623179f500765f9855936e4ff6f33571bad"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:658f6c028edaeb02761ebcaca8d44d519c22594b2a51dcbc9bd2432aa93319e3"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68109c13176749fbbbbbdb94dd4a58dcc604db6ea43ee300b2602154aebdd55f"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:707d28a822b918acf941cff590affaddb42a5d640614d71367c8956623a80cbc"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f251db26c239aec2a4d57fbe869e0a27b7f6b5384ec6bf54aeb4a6a5e7408234"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57c05f2e310701fc17ae358caafd99b1830014e316f0242d13ab6c01db0ab1c2"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b575fbe6396bbf21e4d0e5fd2e3cdb656dc90c930b6c5532192e9a89814f72d"}, - {file = "pymongo-4.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ca5877754f3fa6e4fe5aacf5c404575f04c2d9efc8d22ed39576ed9098d555c8"}, - {file = "pymongo-4.6.2-cp39-cp39-win32.whl", hash = "sha256:8caa73fb19070008e851a589b744aaa38edd1366e2487284c61158c77fdf72af"}, - {file = "pymongo-4.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:3e03c732cb64b96849310e1d8688fb70d75e2571385485bf2f1e7ad1d309fa53"}, - {file = "pymongo-4.6.2.tar.gz", hash = "sha256:ab7d01ac832a1663dad592ccbd92bb0f0775bc8f98a1923c5e1a7d7fead495af"}, -] - -[package.dependencies] -dnspython = ">=1.16.0,<3.0.0" - -[package.extras] -aws = ["pymongo-auth-aws (<2.0.0)"] -encryption = ["certifi", "pymongo[aws]", "pymongocrypt (>=1.6.0,<2.0.0)"] -gssapi = ["pykerberos", "winkerberos (>=0.5.0)"] -ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"] -snappy = ["python-snappy"] -test = ["pytest (>=7)"] -zstd = ["zstandard"] - -[[package]] -name = "pymssql" -version = "2.2.11" -description = "DB-API interface to Microsoft SQL Server for Python. (new Cython-based version)" -optional = true -python-versions = "*" -files = [ - {file = "pymssql-2.2.11-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:692ab328ac290bd2031bc4dd6deae32665dfffda1b12aaa92928d3ebc667d5ad"}, - {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:723a4612421027a01b51e42e786678a18c4a27613a3ccecf331c026e0cc41353"}, - {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:34ab2373ca607174ad7244cfe955c07b6bc77a1e21d3c3143dbe934dec82c3a4"}, - {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bc0ba19b4426c57509f065a03748d9ac230f1543ecdac57175e6ebd213a7bc0"}, - {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8d9d42a50f6e8e6b356e4e8b2fa1da725344ec0be6f8a6107b7196e5bd74906"}, - {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aec64022a2419fad9f496f8e310522635e39d092970e1d55375ea0be86725174"}, - {file = "pymssql-2.2.11-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c389c8041c94d4058827faf5735df5f8e4c1c1eebdd051859536dc393925a667"}, - {file = "pymssql-2.2.11-cp310-cp310-win32.whl", hash = "sha256:6452326cecd4dcee359a6f8878b827118a8c8523cd24de5b3a971a7a172e4275"}, - {file = "pymssql-2.2.11-cp310-cp310-win_amd64.whl", hash = "sha256:c1bde266dbc91b100abd0311102a6585df09cc963599421cc12fd6b4cfa8e3d3"}, - {file = "pymssql-2.2.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6ddaf0597138179517bdbf5b5aa3caffee65987316dc906359a5d0801d0847ee"}, - {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c26af25991715431559cb5b37f243b8ff676540f504ed0317774dfc71827af1"}, - {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:410e8c40b7c1b421e750cf80ccf2da8d802ed815575758ac9a78c5f6cd995723"}, - {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1767239ed45e1fa91d82fc0c63305750530787cd64089cabbe183eb538a35b"}, - {file = "pymssql-2.2.11-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:9a644e4158fed30ae9f3846f2f1c74d36fa1610eb552de35b7f611d063fa3c85"}, - {file = "pymssql-2.2.11-cp311-cp311-win32.whl", hash = "sha256:1956c111debe67f69a9c839b33ce420f0e8def1ef5ff9831c03d8ac840f82376"}, - {file = "pymssql-2.2.11-cp311-cp311-win_amd64.whl", hash = "sha256:0bdd1fb49b0e331e47e83f39d4af784c857e230bfc73519654bab29285c51c63"}, - {file = "pymssql-2.2.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2609bbd3b715822bb4fa6d457b2985d32ad6ab9580fdb61ae6e0eee251791d24"}, - {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c382aea9adaaee189f352d7a493e3f76c13f9337ec2b6aa40e76b114fa13ebac"}, - {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5928324a09de7466368c15ece1de4ab5ea968d24943ceade758836f9fc7149f5"}, - {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee8b10f797d0bfec626b803891cf9e98480ee11f2e8459a7616cdb7e4e4bf2de"}, - {file = "pymssql-2.2.11-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1d5aa1a090b17f4ba75ffac3bb371f6c8c869692b653689396f9b470fde06981"}, - {file = "pymssql-2.2.11-cp312-cp312-win32.whl", hash = "sha256:1f7ba71cf81af65c005173f279928bf86700d295f97e4965e169b5764bc6c4f2"}, - {file = "pymssql-2.2.11-cp312-cp312-win_amd64.whl", hash = "sha256:a0ebb0e40c93f8f1e40aad80f512ae4aa89cb1ec8a96964b9afedcff1d5813fd"}, - {file = "pymssql-2.2.11-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e0ed115902956efaca9d9a20fa9b2b604e3e11d640416ca74900d215cdcbf3ab"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1a75afa17746972bb61120fb6ea907657fc1ab68250bbbd8b21a00d0720ed0f4"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2ae69d8e46637a203cfb48e05439fc9e2ff7646fa1f5396aa3577ce52810031"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13710240457ace5b8c9cca7f4971504656f5703b702895a86386e87c7103801"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7234b0f61dd9ccb2304171b5fd7ed9db133b4ea7c835c9942c9dc5bfc00c1cb"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcd76a8cc757c7cfe2d235f232a20d74ac8cebf9feabcdcbda5ef33157d14b1"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:84aff3235ad1289c4079c548cfcdf7eaaf2475b9f81557351deb42e8f45a9c2d"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b081aa7b02911e3f299f7d1f68ce8ca585a5119d44601bf4483da0aae8c2181"}, - {file = "pymssql-2.2.11-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d315f08c106c884d6b42f70c9518e765a5bc23f6d3a587346bc4e6f198768c7a"}, - {file = "pymssql-2.2.11-cp36-cp36m-win32.whl", hash = "sha256:c8b35b3d5e326729e5edb73d593103d2dbfb474bd36ee95b4e85e1f8271ba98a"}, - {file = "pymssql-2.2.11-cp36-cp36m-win_amd64.whl", hash = "sha256:139c5032e0a2765764987803f1266132fcc5da572848ccc4d29cebba794a4260"}, - {file = "pymssql-2.2.11-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:7bac28aed1d625a002e0289e0c18d1808cecbdc12e2a1a3927dbbaff66e5fff3"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4eeaacc1dbbc678f4e80c6fd6fc279468021fdf2e486adc8631ec0de6b6c0e62"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:428e32e53c554798bc2d0682a169fcb681df6b68544c4aedd1186018ea7e0447"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b621c5e32136dabc2fea25696beab0647ec336d25c04ab6d8eb8c8ee92f0e52"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:658c85474ea01ca3a30de769df06f46681e882524b05c6994cd6fd985c485f27"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070181361ab94bdaeb14b591a35d853f327bc90c660b04047d474274fbb80357"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:492e49616b58b2d6caf4a2598cb344572870171a7b65ba1ac61a5e248b6a8e1c"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:803122aec31fbd52f5d65ef3b30b3bd2dc7b2a9e3a8223d16078a25805155c45"}, - {file = "pymssql-2.2.11-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:09075e129655ab1178d2d60efb9b3fbf5cdb6da2338ecdb3a92c53a4ad7efa0c"}, - {file = "pymssql-2.2.11-cp37-cp37m-win32.whl", hash = "sha256:b4a8377527702d746c490c2ce67d17f1c351d182b49b82fae6e67ae206bf9663"}, - {file = "pymssql-2.2.11-cp37-cp37m-win_amd64.whl", hash = "sha256:167313d91606dc7a3c05b2ad60491a138b7408a8779599ab6430a48a67f133f0"}, - {file = "pymssql-2.2.11-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:8d418f4dca245421242ed9df59d3bcda0cd081650df6deb1bef7f157b6a6f9dd"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f0c44169df8d23c7ce172bd90ef5deb44caf19f15990e4db266e3193071988a4"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b78032e45ea33c55d430b93e55370b900479ea324fae5d5d32486cc0fdc0fedd"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:984d99ee6a2579f86c536b1b0354ad3dc9701e98a4b3953f1301b4695477cd2f"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:287c8f79a7eca0c6787405797bac0f7c502d9be151f3f823aae12042235f8426"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ea4ea296afcae34bc61e4e0ef2f503270fd4bb097b308a07a9194f1f063aa1"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a114633fa02b7eb5bc63520bf07954106c0ed0ce032449c871abb8b8c435a872"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7332db36a537cbc16640a0c3473a2e419aa5bc1f9953cada3212e7b2587de658"}, - {file = "pymssql-2.2.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cd7292d872948c1f67c8cc12158f2c8ed9873d54368139ce1f67b2262ac34029"}, - {file = "pymssql-2.2.11-cp38-cp38-win32.whl", hash = "sha256:fbca115e11685b5891755cc22b3db4348071b8d100a41e1ce93526d9c3dbf2d5"}, - {file = "pymssql-2.2.11-cp38-cp38-win_amd64.whl", hash = "sha256:452b88a4ceca7efb934b5babb365851a3c52e723642092ebc92777397c2cacdb"}, - {file = "pymssql-2.2.11-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:001242cedc73587cbb10aec4069de50febbff3c4c50f9908a215476496b3beab"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:da492482b923b9cc9ad37f0f5592c776279299db2a89c0b7fc931aaefec652d4"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:139a833e6e72a624e4f2cde803a34a616d5661dd9a5b2ae0402d9d8a597b2f1f"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e57fbfad252434d64bdf4b6a935e4241616a4cf8df7af58b9772cd91fce9309a"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5308507c2c4e94ede7e5b164870c1ba2be55abab6daf795b5529e2da4e838b6"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdca43c42d5f370358535b2107140ed550d74f9ef0fc95d2d7fa8c4e40ee48c2"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:fe0cc975aac87b364fdb55cb89642435c3e859dcd99d7260f48af94111ba2673"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4551f50c8a3b6ffbd71f794ee1c0c0134134c5d6414302c2fa28b67fe4470d07"}, - {file = "pymssql-2.2.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ae9818df40588d5a49e7476f05e31cc83dea630d607178d66762ca8cf32e9f77"}, - {file = "pymssql-2.2.11-cp39-cp39-win32.whl", hash = "sha256:15257c7bd89c0283f70d6eaafd9b872201818572b8ba1e8576408ae23ef50c7c"}, - {file = "pymssql-2.2.11-cp39-cp39-win_amd64.whl", hash = "sha256:65bb674c0ba35379bf93d1b2cf06fdc5e7ec56e1d0e9de525bdcf977190b2865"}, - {file = "pymssql-2.2.11.tar.gz", hash = "sha256:15815bf1ff9edb475ec4ef567f23e23c4e828ce119ff5bf98a072b66b8d0ac1b"}, -] - -[[package]] -name = "pymysql" -version = "1.1.0" -description = "Pure Python MySQL Driver" -optional = true -python-versions = ">=3.7" -files = [ - {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, - {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, -] - -[package.dependencies] -cryptography = {version = "*", optional = true, markers = "extra == \"rsa\""} - -[package.extras] -ed25519 = ["PyNaCl (>=1.4.0)"] -rsa = ["cryptography"] - -[[package]] -name = "pynacl" -version = "1.5.0" -description = "Python binding to the Networking and Cryptography (NaCl) library" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, - {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, - {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, - {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, - {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, -] - -[package.dependencies] -cffi = ">=1.4.1" - -[package.extras] -docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] -tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] - -[[package]] -name = "pysocks" -version = "1.7.1" -description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, - {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, - {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, -] - -[[package]] -name = "pytest" -version = "7.4.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.5" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"}, - {file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "python-arango" -version = "7.9.1" -description = "Python Driver for ArangoDB" -optional = true -python-versions = ">=3.8" -files = [ - {file = "python-arango-7.9.1.tar.gz", hash = "sha256:18f7d365fb6cf45778fa73b559e3865d0a1c00081de65ef00ba238db52e374ab"}, - {file = "python_arango-7.9.1-py3-none-any.whl", hash = "sha256:23ec7b3aad774db5f99df20f6a1036385c85eb5c9864e47628bc622ea812f2f8"}, -] - -[package.dependencies] -importlib-metadata = ">=4.7.1" -packaging = ">=23.1" -PyJWT = "*" -requests = "*" -requests-toolbelt = "*" -setuptools = ">=42" -urllib3 = ">=1.26.0" - -[package.extras] -dev = ["black (>=22.3.0)", "flake8 (>=4.0.1)", "isort (>=5.10.1)", "mock", "mypy (>=0.942)", "pre-commit (>=2.17.0)", "pytest (>=7.1.1)", "pytest-cov (>=3.0.0)", "sphinx", "sphinx-rtd-theme", "types-pkg-resources", "types-requests", "types-setuptools"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-keycloak" -version = "3.9.1" -description = "python-keycloak is a Python package providing access to the Keycloak API." -optional = true -python-versions = ">=3.8,<4.0" -files = [ - {file = "python_keycloak-3.9.1-py3-none-any.whl", hash = "sha256:898d1fc73560171d3870251f981e069f854cc67bc0a51b96703355512d8d3cf3"}, - {file = "python_keycloak-3.9.1.tar.gz", hash = "sha256:50c8073172ca0630f3569c6b631134216b60f4e347cc5bb669a57e6ffba50f7e"}, -] - -[package.dependencies] -deprecation = ">=2.1.0" -jwcrypto = ">=1.5.4,<2.0.0" -requests = ">=2.20.0" -requests-toolbelt = ">=0.6.0" - -[package.extras] -docs = ["Sphinx (>=6.1.0,<7.0.0)", "alabaster (>=0.7.12,<0.8.0)", "commonmark (>=0.9.1,<0.10.0)", "m2r2 (>=0.3.2,<0.4.0)", "mock (>=4.0.3,<5.0.0)", "readthedocs-sphinx-ext (>=2.1.9,<3.0.0)", "recommonmark (>=0.7.1,<0.8.0)", "sphinx-autoapi (>=3.0.0,<4.0.0)", "sphinx-rtd-theme (>=1.0.0,<2.0.0)"] - -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, -] - -[[package]] -name = "pywin32" -version = "306" -description = "Python for Window Extensions" -optional = false -python-versions = "*" -files = [ - {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, - {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, - {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, - {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, - {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, - {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, - {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, - {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, - {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, - {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, - {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, - {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, - {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, - {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, -] - -[[package]] -name = "pywin32-ctypes" -version = "0.2.2" -description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, - {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "qdrant-client" -version = "1.8.2" -description = "Client library for the Qdrant vector search engine" -optional = true -python-versions = ">=3.8" -files = [ - {file = "qdrant_client-1.8.2-py3-none-any.whl", hash = "sha256:ee5341c0486d09e4346b0f5ef7781436e6d8cdbf1d5ecddfde7adb3647d353a8"}, - {file = "qdrant_client-1.8.2.tar.gz", hash = "sha256:65078d5328bc0393f42a46a31cd319a989b8285bf3958360acf1dffffdf4cc4e"}, -] - -[package.dependencies] -grpcio = ">=1.41.0" -grpcio-tools = ">=1.41.0" -httpx = {version = ">=0.20.0", extras = ["http2"]} -numpy = [ - {version = ">=1.21", markers = "python_version >= \"3.8\" and python_version < \"3.12\""}, - {version = ">=1.26", markers = "python_version >= \"3.12\""}, -] -portalocker = ">=2.7.0,<3.0.0" -pydantic = ">=1.10.8" -urllib3 = ">=1.26.14,<3" - -[package.extras] -fastembed = ["fastembed (==0.2.5)"] - -[[package]] -name = "reactivex" -version = "4.0.4" -description = "ReactiveX (Rx) for Python" -optional = true -python-versions = ">=3.7,<4.0" -files = [ - {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"}, - {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"}, -] - -[package.dependencies] -typing-extensions = ">=4.1.1,<5.0.0" - -[[package]] -name = "readme-renderer" -version = "43.0" -description = "readme_renderer is a library for rendering readme descriptions for Warehouse" -optional = false -python-versions = ">=3.8" -files = [ - {file = "readme_renderer-43.0-py3-none-any.whl", hash = "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9"}, - {file = "readme_renderer-43.0.tar.gz", hash = "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311"}, -] - -[package.dependencies] -docutils = ">=0.13.1" -nh3 = ">=0.2.14" -Pygments = ">=2.5.1" - -[package.extras] -md = ["cmarkgfm (>=0.8.0)"] - -[[package]] -name = "redis" -version = "5.0.3" -description = "Python client for Redis database and key-value store" -optional = true -python-versions = ">=3.7" -files = [ - {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, - {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, -] - -[package.dependencies] -async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""} - -[package.extras] -hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[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-oauthlib" -version = "2.0.0" -description = "OAuthlib authentication support for Requests." -optional = true -python-versions = ">=3.4" -files = [ - {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, - {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, -] - -[package.dependencies] -oauthlib = ">=3.0.0" -requests = ">=2.0.0" - -[package.extras] -rsa = ["oauthlib[signedtoken] (>=3.0.0)"] - -[[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.*" -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" -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.7.1" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, -] - -[package.dependencies] -markdown-it-py = ">=2.2.0" -pygments = ">=2.13.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "rsa" -version = "4.9" -description = "Pure-Python RSA implementation" -optional = true -python-versions = ">=3.6,<4" -files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, -] - -[package.dependencies] -pyasn1 = ">=0.1.3" - -[[package]] -name = "s3transfer" -version = "0.10.0" -description = "An Amazon S3 Transfer Manager" -optional = true -python-versions = ">= 3.8" -files = [ - {file = "s3transfer-0.10.0-py3-none-any.whl", hash = "sha256:3cdb40f5cfa6966e812209d0994f2a4709b561c88e90cf00c2696d2df4e56b2e"}, - {file = "s3transfer-0.10.0.tar.gz", hash = "sha256:d0c8bbf672d5eebbe4e57945e23b972d963f07d82f661cabf678a5c88831595b"}, -] - -[package.dependencies] -botocore = ">=1.33.2,<2.0a.0" - -[package.extras] -crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] - -[[package]] -name = "scramp" -version = "1.4.4" -description = "An implementation of the SCRAM protocol." -optional = false -python-versions = ">=3.7" -files = [ - {file = "scramp-1.4.4-py3-none-any.whl", hash = "sha256:b142312df7c2977241d951318b7ee923d6b7a4f75ba0f05b621ece1ed616faa3"}, - {file = "scramp-1.4.4.tar.gz", hash = "sha256:b7022a140040f33cf863ab2657917ed05287a807b917950489b89b9f685d59bc"}, -] - -[package.dependencies] -asn1crypto = ">=1.5.1" - -[[package]] -name = "secretstorage" -version = "3.3.3" -description = "Python bindings to FreeDesktop.org Secret Service API" -optional = false -python-versions = ">=3.6" -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 = "selenium" -version = "4.18.1" -description = "" -optional = true -python-versions = ">=3.8" -files = [ - {file = "selenium-4.18.1-py3-none-any.whl", hash = "sha256:b24a3cdd2d47c29832e81345bfcde0c12bb608738013e53c781b211b418df241"}, - {file = "selenium-4.18.1.tar.gz", hash = "sha256:a11f67afa8bfac6b77e148c987b33f6b14eb1cae4d352722a75de1f26e3f0ae2"}, -] - -[package.dependencies] -certifi = ">=2021.10.8" -trio = ">=0.17,<1.0" -trio-websocket = ">=0.9,<1.0" -typing_extensions = ">=4.9.0" -urllib3 = {version = ">=1.26,<3", extras = ["socks"]} - -[[package]] -name = "setuptools" -version = "69.1.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -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 = "*" -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 = "sortedcontainers" -version = "2.4.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -optional = true -python-versions = "*" -files = [ - {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, - {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, -] - -[[package]] -name = "sphinx" -version = "7.2.6" -description = "Python documentation generator" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, - {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.14" -requests = ">=2.25.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.9" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.8" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, - {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.6" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, - {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.5" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, - {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -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" -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 = "1.0.7" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, - {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.10" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, - {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sqlalchemy" -version = "2.0.28" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0b148ab0438f72ad21cb004ce3bdaafd28465c4276af66df3b9ecd2037bf252"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbda76961eb8f27e6ad3c84d1dc56d5bc61ba8f02bd20fcf3450bd421c2fcc9c"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feea693c452d85ea0015ebe3bb9cd15b6f49acc1a31c28b3c50f4db0f8fb1e71"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da98815f82dce0cb31fd1e873a0cb30934971d15b74e0d78cf21f9e1b05953f"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5adf383c73f2d49ad15ff363a8748319ff84c371eed59ffd0127355d6ea1da"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56856b871146bfead25fbcaed098269d90b744eea5cb32a952df00d542cdd368"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win32.whl", hash = "sha256:943aa74a11f5806ab68278284a4ddd282d3fb348a0e96db9b42cb81bf731acdc"}, - {file = "SQLAlchemy-2.0.28-cp310-cp310-win_amd64.whl", hash = "sha256:c6c4da4843e0dabde41b8f2e8147438330924114f541949e6318358a56d1875a"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61e2e41656a673b777e2f0cbbe545323dbe0d32312f590b1bc09da1de6c2a02"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:af8ce2d31679006e7b747d30a89cd3ac1ec304c3d4c20973f0f4ad58e2d1c4c9"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win32.whl", hash = "sha256:1ee8bd6d68578e517943f5ebff3afbd93fc65f7ef8f23becab9fa8fb315afb1d"}, - {file = "SQLAlchemy-2.0.28-cp311-cp311-win_amd64.whl", hash = "sha256:ad7acbe95bac70e4e687a4dc9ae3f7a2f467aa6597049eeb6d4a662ecd990bb6"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea30da1e76cb1acc5b72e204a920a3a7678d9d52f688f087dc08e54e2754c67"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e23b88c69497a6322b5796c0781400692eca1ae5532821b39ce81a48c395aae9"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win32.whl", hash = "sha256:a921002be69ac3ab2cf0c3017c4e6a3377f800f1fca7f254c13b5f1a2f10022c"}, - {file = "SQLAlchemy-2.0.28-cp312-cp312-win_amd64.whl", hash = "sha256:b4a2cf92995635b64876dc141af0ef089c6eea7e05898d8d8865e71a326c0385"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c7b78dfc7278329f27be02c44abc0d69fe235495bb8e16ec7ef1b1a17952db"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eba73ef2c30695cb7eabcdb33bb3d0b878595737479e152468f3ba97a9c22a4"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5df5d1dafb8eee89384fb7a1f79128118bc0ba50ce0db27a40750f6f91aa99d5"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2858bbab1681ee5406650202950dc8f00e83b06a198741b7c656e63818633526"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win32.whl", hash = "sha256:9461802f2e965de5cff80c5a13bc945abea7edaa1d29360b485c3d2b56cdb075"}, - {file = "SQLAlchemy-2.0.28-cp37-cp37m-win_amd64.whl", hash = "sha256:a6bec1c010a6d65b3ed88c863d56b9ea5eeefdf62b5e39cafd08c65f5ce5198b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7e4baf9161d076b9a7e432fce06217b9bd90cfb8f1d543d6e8c4595627edb9"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0a5354cb4de9b64bccb6ea33162cb83e03dbefa0d892db88a672f5aad638a75"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fffcc8edc508801ed2e6a4e7b0d150a62196fd28b4e16ab9f65192e8186102b6"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aca7b6d99a4541b2ebab4494f6c8c2f947e0df4ac859ced575238e1d6ca5716b"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win32.whl", hash = "sha256:8c7f10720fc34d14abad5b647bc8202202f4948498927d9f1b4df0fb1cf391b7"}, - {file = "SQLAlchemy-2.0.28-cp38-cp38-win_amd64.whl", hash = "sha256:243feb6882b06a2af68ecf4bec8813d99452a1b62ba2be917ce6283852cf701b"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc4974d3684f28b61b9a90fcb4c41fb340fd4b6a50c04365704a4da5a9603b05"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87724e7ed2a936fdda2c05dbd99d395c91ea3c96f029a033a4a20e008dd876bf"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68722e6a550f5de2e3cfe9da6afb9a7dd15ef7032afa5651b0f0c6b3adb8815d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328529f7c7f90adcd65aed06a161851f83f475c2f664a898af574893f55d9e53"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:df40c16a7e8be7413b885c9bf900d402918cc848be08a59b022478804ea076b8"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:426f2fa71331a64f5132369ede5171c52fd1df1bd9727ce621f38b5b24f48750"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win32.whl", hash = "sha256:33157920b233bc542ce497a81a2e1452e685a11834c5763933b440fedd1d8e2d"}, - {file = "SQLAlchemy-2.0.28-cp39-cp39-win_amd64.whl", hash = "sha256:2f60843068e432311c886c5f03c4664acaef507cf716f6c60d5fde7265be9d7b"}, - {file = "SQLAlchemy-2.0.28-py3-none-any.whl", hash = "sha256:78bb7e8da0183a8301352d569900d9d3594c48ac21dc1c2ec6b3121ed8b6c986"}, - {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "sqlalchemy-cockroachdb" -version = "2.0.2" -description = "CockroachDB dialect for SQLAlchemy" -optional = false -python-versions = "*" -files = [ - {file = "sqlalchemy-cockroachdb-2.0.2.tar.gz", hash = "sha256:119756eb905855d6a11345b99cfe853031a3fe598a9c4bf35a8ddac9f89fe8cc"}, - {file = "sqlalchemy_cockroachdb-2.0.2-py3-none-any.whl", hash = "sha256:0d5d50e805b024cb2ccd85423a5c1a367d1a56a5cd0ea47765233fd47665070d"}, -] - -[package.dependencies] -SQLAlchemy = "*" - -[[package]] -name = "tenacity" -version = "8.2.3" -description = "Retry code until it succeeds" -optional = true -python-versions = ">=3.7" -files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, -] - -[package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "trino" -version = "0.329.0" -description = "Client for the Trino distributed SQL Engine" -optional = true -python-versions = ">=3.8" -files = [ - {file = "trino-0.329.0-py3-none-any.whl", hash = "sha256:74b82a38f16193ad869e63fb837d651e66c044f19a817232787e27c5d44b671f"}, - {file = "trino-0.329.0.tar.gz", hash = "sha256:1d976467726ec3d0fa120a64e61fdb8caf13295db207051e2cc267a952af989b"}, -] - -[package.dependencies] -python-dateutil = "*" -pytz = "*" -requests = ">=2.31.0" -tzlocal = "*" - -[package.extras] -all = ["requests-kerberos", "sqlalchemy (>=1.3)"] -external-authentication-token-cache = ["keyring"] -gssapi = ["requests-gssapi"] -kerberos = ["requests-kerberos"] -sqlalchemy = ["sqlalchemy (>=1.3)"] -tests = ["black", "httpretty (<1.1)", "isort", "pre-commit", "pytest", "pytest-runner", "requests-gssapi", "requests-kerberos", "sqlalchemy (>=1.3)"] - -[[package]] -name = "trio" -version = "0.24.0" -description = "A friendly Python library for async concurrency and I/O" -optional = true -python-versions = ">=3.8" -files = [ - {file = "trio-0.24.0-py3-none-any.whl", hash = "sha256:c3bd3a4e3e3025cd9a2241eae75637c43fe0b9e88b4c97b9161a55b9e54cd72c"}, - {file = "trio-0.24.0.tar.gz", hash = "sha256:ffa09a74a6bf81b84f8613909fb0beaee84757450183a7a2e0b47b455c0cac5d"}, -] - -[package.dependencies] -attrs = ">=20.1.0" -cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -idna = "*" -outcome = "*" -sniffio = ">=1.3.0" -sortedcontainers = "*" - -[[package]] -name = "trio-websocket" -version = "0.11.1" -description = "WebSocket library for Trio" -optional = true -python-versions = ">=3.7" -files = [ - {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, - {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, -] - -[package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -trio = ">=0.11" -wsproto = ">=0.14" - -[[package]] -name = "twine" -version = "4.0.2" -description = "Collection of utilities for publishing packages on PyPI" -optional = false -python-versions = ">=3.7" -files = [ - {file = "twine-4.0.2-py3-none-any.whl", hash = "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8"}, - {file = "twine-4.0.2.tar.gz", hash = "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8"}, -] - -[package.dependencies] -importlib-metadata = ">=3.6" -keyring = ">=15.1" -pkginfo = ">=1.8.1" -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]] -name = "types-paramiko" -version = "3.4.0.20240423" -description = "Typing stubs for paramiko" -optional = false -python-versions = ">=3.8" -files = [ - {file = "types-paramiko-3.4.0.20240423.tar.gz", hash = "sha256:aaa98dda232c47886563d66743d3a8b66c432790c596bc3bdd3f17f91be2a8c1"}, - {file = "types_paramiko-3.4.0.20240423-py3-none-any.whl", hash = "sha256:c56e0d43399a1b909901b1e0375e0ff6ee62e16cd6e00695024abc2e9fe02035"}, -] - -[package.dependencies] -cryptography = ">=37.0.0" - -[[package]] -name = "typing-extensions" -version = "4.11.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, -] - -[[package]] -name = "tzdata" -version = "2024.1" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, -] - -[[package]] -name = "tzlocal" -version = "5.2" -description = "tzinfo object for the local timezone" -optional = true -python-versions = ">=3.8" -files = [ - {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, - {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, -] - -[package.dependencies] -tzdata = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] - -[[package]] -name = "ujson" -version = "5.10.0" -description = "Ultra fast JSON encoder and decoder for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"}, - {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"}, - {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"}, - {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"}, - {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"}, - {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"}, - {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"}, - {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"}, - {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"}, - {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"}, - {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"}, - {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"}, - {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"}, - {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"}, - {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"}, - {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"}, - {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"}, - {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"}, - {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"}, - {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"}, - {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"}, - {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"}, - {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"}, - {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"}, - {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"}, - {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"}, - {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"}, - {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"}, - {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"}, - {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"}, - {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"}, - {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"}, - {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"}, - {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"}, - {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"}, - {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"}, - {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"}, - {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"}, - {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"}, - {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"}, -] - -[[package]] -name = "urllib3" -version = "1.26.18" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, -] - -[package.dependencies] -PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} - -[package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "urllib3" -version = "2.0.7" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.7" -files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, -] - -[package.dependencies] -pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "validators" -version = "0.22.0" -description = "Python Data Validation for Humans™" -optional = true -python-versions = ">=3.8" -files = [ - {file = "validators-0.22.0-py3-none-any.whl", hash = "sha256:61cf7d4a62bbae559f2e54aed3b000cea9ff3e2fdbe463f51179b92c58c9585a"}, - {file = "validators-0.22.0.tar.gz", hash = "sha256:77b2689b172eeeb600d9605ab86194641670cdb73b60afd577142a9397873370"}, -] - -[package.extras] -docs-offline = ["myst-parser (>=2.0.0)", "pypandoc-binary (>=1.11)", "sphinx (>=7.1.1)"] -docs-online = ["mkdocs (>=1.5.2)", "mkdocs-git-revision-date-localized-plugin (>=1.2.0)", "mkdocs-material (>=9.2.6)", "mkdocstrings[python] (>=0.22.0)", "pyaml (>=23.7.0)"] -hooks = ["pre-commit (>=3.3.3)"] -package = ["build (>=1.0.0)", "twine (>=4.0.2)"] -runner = ["tox (>=4.11.1)"] -sast = ["bandit[toml] (>=1.7.5)"] -testing = ["pytest (>=7.4.0)"] -tooling = ["black (>=23.7.0)", "pyright (>=1.1.325)", "ruff (>=0.0.287)"] -tooling-extras = ["pyaml (>=23.7.0)", "pypandoc-binary (>=1.11)", "pytest (>=7.4.0)"] - -[[package]] -name = "virtualenv" -version = "20.25.1" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "weaviate-client" -version = "4.5.4" -description = "A python native Weaviate client" -optional = true -python-versions = ">=3.8" -files = [ - {file = "weaviate-client-4.5.4.tar.gz", hash = "sha256:fc53dc73cd53df453c5e6dc758e49a6a1549212d6670ddd013392107120692f8"}, - {file = "weaviate_client-4.5.4-py3-none-any.whl", hash = "sha256:f6d3a6b759e5aa0d3350067490526ea38b9274ae4043b4a3ae0064c28d56883f"}, -] - -[package.dependencies] -authlib = ">=1.2.1,<2.0.0" -grpcio = ">=1.57.0,<2.0.0" -grpcio-health-checking = ">=1.57.0,<2.0.0" -grpcio-tools = ">=1.57.0,<2.0.0" -httpx = "0.27.0" -pydantic = ">=2.5.0,<3.0.0" -requests = ">=2.30.0,<3.0.0" -validators = "0.22.0" - -[[package]] -name = "websocket-client" -version = "1.7.0" -description = "WebSocket client for Python with low level API options" -optional = true -python-versions = ">=3.8" -files = [ - {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, - {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, -] - -[package.extras] -docs = ["Sphinx (>=6.0)", "sphinx-rtd-theme (>=1.1.0)"] -optional = ["python-socks", "wsaccel"] -test = ["websockets"] - -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - -[[package]] -name = "wsproto" -version = "1.2.0" -description = "WebSockets state-machine based protocol implementation" -optional = true -python-versions = ">=3.7.0" -files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, -] - -[package.dependencies] -h11 = ">=0.9.0,<1" - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[extras] -arangodb = ["python-arango"] -aws = ["boto3", "httpx"] -azurite = ["azure-storage-blob"] -cassandra = [] -chroma = ["chromadb-client"] -clickhouse = ["clickhouse-driver"] -cockroachdb = [] -cosmosdb = ["azure-cosmos"] -db2 = ["ibm_db_sa", "sqlalchemy"] -elasticsearch = [] -generic = ["httpx", "redis"] -google = ["google-cloud-datastore", "google-cloud-pubsub"] -influxdb = ["influxdb", "influxdb-client"] -k3s = ["kubernetes", "pyyaml"] -kafka = [] -keycloak = ["python-keycloak"] -localstack = ["boto3"] -mailpit = ["cryptography"] -memcached = [] -milvus = [] -minio = ["minio"] -mongodb = ["pymongo"] -mqtt = [] -mssql = ["pymssql", "sqlalchemy"] -mysql = ["pymysql", "sqlalchemy"] -nats = ["nats-py"] -neo4j = ["neo4j"] -nginx = [] -ollama = [] -opensearch = ["opensearch-py"] -oracle = ["oracledb", "sqlalchemy"] -oracle-free = ["oracledb", "sqlalchemy"] -postgres = [] -qdrant = ["qdrant-client"] -rabbitmq = ["pika"] -redis = ["redis"] -registry = ["bcrypt"] -scylla = ["cassandra-driver"] -selenium = ["selenium"] -sftp = ["cryptography"] -test-module-import = ["httpx"] -trino = ["trino"] -vault = [] -weaviate = ["weaviate-client"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.9,<4.0" -content-hash = "5c400cc87dc9708588ee8d7d50646de789235732d868b74ebc43f1cf2a403c88" diff --git a/pyproject.toml b/pyproject.toml index 8bbf82ea8..4ba4209ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,275 +1,288 @@ -[tool.poetry] +[project] name = "testcontainers" -version = "4.9.0" # auto-incremented by release-please +version = "4.15.0" # auto-incremented by release-please description = "Python library for throwaway instances of anything that can run in a Docker container" -authors = ["Sergey Pirogov "] +readme = "README.md" +requires-python = ">=3.10" +license = "Apache-2.0" +license-file = ["LICENSE.txt"] + +authors = [{ name = "Sergey Pirogov", email = "automationremarks@gmail.com" }] + maintainers = [ - "Balint Bartha ", - "David Ankin ", - "Vemund Santi " + { name = "Balint Bartha", email = "totallyzen@users.noreply.github.com" }, + { name = "David Ankin", email = "daveankin@gmail.com" }, + { name = "Vemund Santi", email = "vemund@santi.no" }, ] -readme = "README.md" + keywords = ["testing", "logging", "docker", "test automation"] + classifiers = [ - "License :: OSI Approved :: Apache Software License", - "Intended Audience :: Information Technology", - "Intended Audience :: Developers", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Software Development :: Libraries :: Python Modules", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX", - "Operating System :: Unix", - "Operating System :: MacOS", + "License :: OSI Approved :: Apache Software License", + "Intended Audience :: Information Technology", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", ] -# testcontainers-core is a proper package dependency - only modules needed here -packages = [ - { include = "testcontainers", from = "core" }, - { include = "testcontainers", from = "modules/arangodb" }, - { include = "testcontainers", from = "modules/aws"}, - { include = "testcontainers", from = "modules/azurite" }, - { include = "testcontainers", from = "modules/cassandra" }, - { include = "testcontainers", from = "modules/chroma" }, - { include = "testcontainers", from = "modules/clickhouse" }, - { include = "testcontainers", from = "modules/cockroachdb" }, - { include = "testcontainers", from = "modules/cosmosdb" }, - { include = "testcontainers", from = "modules/db2" }, - { include = "testcontainers", from = "modules/elasticsearch" }, - { include = "testcontainers", from = "modules/generic" }, - { include = "testcontainers", from = "modules/test_module_import"}, - { include = "testcontainers", from = "modules/google" }, - { include = "testcontainers", from = "modules/influxdb" }, - { include = "testcontainers", from = "modules/k3s" }, - { include = "testcontainers", from = "modules/kafka" }, - { include = "testcontainers", from = "modules/keycloak" }, - { include = "testcontainers", from = "modules/localstack" }, - { include = "testcontainers", from = "modules/mailpit" }, - { include = "testcontainers", from = "modules/memcached" }, - { include = "testcontainers", from = "modules/minio" }, - { include = "testcontainers", from = "modules/milvus" }, - { include = "testcontainers", from = "modules/mongodb" }, - { include = "testcontainers", from = "modules/mqtt" }, - { include = "testcontainers", from = "modules/mssql" }, - { include = "testcontainers", from = "modules/mysql" }, - { include = "testcontainers", from = "modules/nats" }, - { include = "testcontainers", from = "modules/neo4j" }, - { include = "testcontainers", from = "modules/nginx" }, - { include = "testcontainers", from = "modules/ollama" }, - { include = "testcontainers", from = "modules/opensearch" }, - { include = "testcontainers", from = "modules/oracle-free" }, - { include = "testcontainers", from = "modules/postgres" }, - { include = "testcontainers", from = "modules/qdrant" }, - { include = "testcontainers", from = "modules/rabbitmq" }, - { include = "testcontainers", from = "modules/redis" }, - { include = "testcontainers", from = "modules/registry" }, - { include = "testcontainers", from = "modules/sftp" }, - { include = "testcontainers", from = "modules/selenium" }, - { include = "testcontainers", from = "modules/scylla" }, - { include = "testcontainers", from = "modules/trino" }, - { include = "testcontainers", from = "modules/vault" }, - { include = "testcontainers", from = "modules/weaviate" }, +dependencies = [ + "docker", + "urllib3", + "wrapt", + "typing-extensions", + "python-dotenv", ] -[tool.poetry.urls] -"GitHub" = "https://github.com/testcontainers/testcontainers-python" +[project.urls] +Homepage = "https://github.com/testcontainers/testcontainers-python" +Repository = "https://github.com/testcontainers/testcontainers-python" "Issue Tracker" = "https://github.com/testcontainers/testcontainers-python/issues" -[tool.poetry.dependencies] -python = ">=3.9,<4.0" -docker = "*" # ">=4.0" -urllib3 = "*" # "<2.0" -wrapt = "*" # "^1.16.0" -typing-extensions = "*" -python-dotenv = "*" - -# community modules -python-arango = { version = "^7.8", optional = true } -azure-storage-blob = { version = "^12.19", optional = true } -cassandra-driver = { version = "3.29.1", optional = true } -clickhouse-driver = { version = "*", optional = true } -google-cloud-pubsub = { version = ">=2", optional = true } -google-cloud-datastore = { version = ">=2", optional = true } -influxdb = { version = "*", optional = true } -influxdb-client = { version = "*", optional = true } -kubernetes = { version = "*", optional = true } -pyyaml = { version = "*", optional = true } -python-keycloak = { version = "*", optional = true } -boto3 = { version = "*", optional = true } -minio = { version = "*", optional = true } -nats-py = { version = "*", optional = true } -pymongo = { version = "*", optional = true } -sqlalchemy = { version = "*", optional = true } -pymssql = { version = "*", optional = true } -pymysql = { version = "*", extras = ["rsa"], optional = true } -neo4j = { version = "*", optional = true } -opensearch-py = { version = "*", optional = true } -oracledb = { version = "*", optional = true } -pika = { version = "*", optional = true } -redis = { version = "*", optional = true } -selenium = { version = "*", optional = true } -weaviate-client = { version = "^4.5.4", optional = true } -chromadb-client = { version = "*", optional = true } -qdrant-client = { version = "*", optional = true } -bcrypt = { version = "*", optional = true } -httpx = { version = "*", optional = true } -azure-cosmos = { version = "*", optional = true } -cryptography = { version = "*", optional = true } -trino = { version = "*", optional = true } -ibm_db_sa = { version = "*", optional = true } - -[tool.poetry.extras] -arangodb = ["python-arango"] -aws = ["boto3", "httpx"] -azurite = ["azure-storage-blob"] +[project.optional-dependencies] +arangodb = ["python-arango>=8"] +aws = ["boto3>=1", "httpx"] +azurite = ["azure-storage-blob>=12"] cassandra = [] clickhouse = ["clickhouse-driver"] -cosmosdb = ["azure-cosmos"] +cosmosdb = ["azure-cosmos>=4"] cockroachdb = [] -db2 = ["sqlalchemy", "ibm_db_sa"] +cratedb = [ + "httpx", + "sqlalchemy-cratedb" +] +db2 = [ + "sqlalchemy", + "ibm_db_sa; platform_machine != 'aarch64' and platform_machine != 'arm64'", +] elasticsearch = [] -generic = ["httpx", "redis"] # The advance doctests for ServerContainer require redis +generic = [ + "httpx", + "redis>=7", + "sqlalchemy", +] test_module_import = ["httpx"] -google = ["google-cloud-pubsub", "google-cloud-datastore"] -influxdb = ["influxdb", "influxdb-client"] -k3s = ["kubernetes", "pyyaml"] +google = [ + "google-cloud-pubsub>=2", + "google-cloud-datastore>=2", +] +influxdb = [ + "influxdb>=5", + "influxdb-client>=1", +] +k3s = ["kubernetes", "pyyaml>=6.0.3"] kafka = [] -keycloak = ["python-keycloak"] -localstack = ["boto3"] +keycloak = ["python-keycloak>=6; python_version < '4.0'"] +localstack = ["boto3>=1"] mailpit = ["cryptography"] memcached = [] -minio = ["minio"] +minio = ["minio>=7"] milvus = [] -mongodb = ["pymongo"] +mongodb = ["pymongo>=4"] mqtt = [] -mssql = ["sqlalchemy", "pymssql"] -mysql = ["sqlalchemy", "pymysql"] -nats = ["nats-py"] -neo4j = ["neo4j"] +mssql = ["sqlalchemy", "pymssql>=2"] +mysql = ["sqlalchemy", "pymysql[rsa]>=1"] +nats = ["nats-py>=2"] +neo4j = ["neo4j>=6"] nginx = [] -opensearch = ["opensearch-py"] +openfga = ["openfga-sdk"] +opensearch = ["opensearch-py>=3; python_version < '4.0'"] ollama = [] -oracle = ["sqlalchemy", "oracledb"] -oracle-free = ["sqlalchemy", "oracledb"] +oracle = ["sqlalchemy", "oracledb>=3"] +oracle-free = ["sqlalchemy", "oracledb>=3"] postgres = [] -qdrant = ["qdrant-client"] -rabbitmq = ["pika"] -redis = ["redis"] -registry = ["bcrypt"] -selenium = ["selenium"] -scylla = ["cassandra-driver"] +qdrant = ["qdrant-client>=1"] +rabbitmq = ["pika>=1"] +redis = ["redis>=7"] +registry = ["bcrypt>=5"] +selenium = ["selenium>=4"] +scylla = ["cassandra-driver>=3; python_version < '3.14'"] sftp = ["cryptography"] +toxiproxy = [] +valkey = [] vault = [] -weaviate = ["weaviate-client"] -chroma = ["chromadb-client"] +weaviate = ["weaviate-client>=4"] +chroma = ["chromadb-client>=1"] trino = ["trino"] -[tool.poetry.group.dev.dependencies] -mypy = "1.7.1" -pre-commit = "^3.6" -pytest = "7.4.3" -pytest-cov = "4.1.0" -sphinx = "7.2.6" -twine = "4.0.2" -anyio = "4.3.0" -# for tests only -psycopg2-binary = "2.9.9" -pg8000 = "1.30.5" -sqlalchemy = "2.0.28" -psycopg = "3.1.18" -cassandra-driver = "3.29.1" -pytest-asyncio = "0.23.5" -kafka-python-ng = "^2.2.0" -hvac = "2.1.0" -pymilvus = "2.4.3" -httpx = "0.27.0" -paho-mqtt = "2.1.0" -sqlalchemy-cockroachdb = "2.0.2" -paramiko = "^3.4.0" -types-paramiko = "^3.4.0.20240423" -pytest-mock = "^3.14.0" +# ---------- Dependency groups (uv-friendly; avoids tool lock-in for dev/test sets) ---------- +# This structure is intentionally PEP-ish (dependency groups). uv supports it. +[dependency-groups] +test = [ + "pytest>=9", + "pytest-asyncio>=1", + "pytest-cov>=7", + "pytest-mock>=3", + # TODO: Check what test requirements are really need and for which modules + # Make a group for each module requiring extra deps in order to test + # Each module with minimal extra depencies + "sqlalchemy", + "psycopg2-binary==2.9.11", + "pg8000==1.31.5", + "psycopg>=3", + "cassandra-driver>=3.30", + "kafka-python-ng>=2", + "hvac>=2; python_version < '4.0'", + "pymilvus>=2", + "paho-mqtt>=2", + "sqlalchemy-cockroachdb>=2", + "sqlalchemy-cratedb", + "paramiko>=4", + "twine>=7.0.0", + "anyio>=4", + "tomli>=2.0; python_version < '3.11'", # required for pyproject.toml tests, TODO: remove once we drop py3.10 support + "pytest-xdist>=3.8.0", +] +lint = [ + "mypy>=1", + "types-paramiko>=4", + "ruff", + "pre-commit>=4", + "types-docker>=7.1.0.20260518", + "boto3-stubs-lite[boto3]", +] +docs = [ + "sphinx>=8; python_version < '3.11'", + "sphinx>=9; python_version >= '3.11'", + # TODO: Remove upper bounds of mkdocs and trust the locking/updating instead + "mkdocs>=1.5.3,<2.0.0", + "mkdocs-material>=9.5.0,<10.0.0", + "mkdocs-markdownextradata-plugin>=0.2.6,<0.3.0", + "mkdocs-codeinclude-plugin>=0.2.1,<0.3.0", + "mkdocs-include-markdown-plugin>=7.1.8,<8.0.0", +] +dev = [ + {include-group = "test"}, + {include-group = "lint"}, + {include-group = "docs"}, +] -[[tool.poetry.source]] -name = "PyPI" -priority = "primary" -[tool.black] -line-length = 120 +# ---------- Hatchling build configuration ---------- +[build-system] +# hatchling>=1.32 emits Metadata-Version 2.5, which the pinned twine version +# does not yet recognize as valid (https://github.com/pypa/twine/issues/1327). +requires = ["hatchling>=1.26,<1.32"] +build-backend = "hatchling.build" + +[tool.hatch.build] +include = [ + "src/**", + "README.md", + "LICENSE*", + "pyproject.toml", +] + +[tool.hatch.build.targets.wheel] +packages = [ + "src/testcontainers", +] + +# ---------- Tool configs---------- [tool.pytest.ini_options] -addopts = "--tb=short --strict-markers" +addopts = [ + "--tb=short", + "--strict-markers", + "--import-mode=importlib", # https://docs.pytest.org/en/stable/explanation/goodpractices.html#choosing-a-test-layout +] log_cli = true log_cli_level = "INFO" markers = [ - "inside_docker_check: mark test to be used to validate DinD/DooD is working as expected" + "inside_docker_check: mark test to be used to validate DinD/DooD is working as expected", + "long_running: mark test as very long running", +] +filterwarnings = [ + "ignore:The @wait_container_is_ready decorator is deprecated.*:DeprecationWarning", + "ignore:The wait_for function is deprecated and will be removed in a future version.*:DeprecationWarning", + "ignore:The wait_for_logs function with string or callable predicates is deprecated.*:DeprecationWarning", ] [tool.coverage.run] +relative_files = true +source = ["testcontainers.core"] # we only check coverage for core atm branch = true -omit = [ - "oracle.py" -] +omit = ["oracle.py"] [tool.coverage.report] -exclude_lines = [ - "pass", - "raise NotImplementedError" # TODO: used in core/generic.py, not sure we need DbContainer +exclude_also = [ + "pass", + "raise NotImplementedError", + "if TYPE_CHECKING:", + "@overload", +] + +[tool.coverage.paths] +# only consider non-community/core packages for the coverage. +# Community packages are run conditionally anyway. +core = [ + "src/testcontainers/core", + "*/site-packages/testcontainers/core", + "*/dist-packages/testcontainers/core", +] +compose = [ + "src/testcontainers/compose", + "*/site-packages/testcontainers/compose", + "*/dist-packages/testcontainers/compose", +] +concat = [ + "src/testcontainers/cocat", + "*/site-packages/testcontainer/cocat", + "*/dist-packages/testcontainers/cocat", ] [tool.ruff] target-version = "py39" line-length = 120 fix = true -src = ["core", "modules/*"] [tool.ruff.lint] -fixable = ["I"] -exclude = ["**/tests/**/*.py"] select = [ - # flake8-2020 - "YTT", - # flake8-bugbear - "B", - # flake8-builtins - "A", - # flake8-comprehensions - "C4", - # flake8-debugger - "T10", - # flake8-print - "T20", - # flake8-pytest-style - "PT", - # flake8-simplify - "SIM", - # flake8-tidy-imports - "TID", - # flake8-type-checking - "TCH", - # isort - "I", - # mccabe - "C90", - # pycodestyle - "E", "W", - # pyflakes - "F", - # pygrep-hooks - "PGH", - # pyupgrade - "UP", - # ruff - "RUF", - # TODO: security, enable via line below - # "S", + "YTT", + "B", + "A", + "C4", + "T10", + "T20", + "PT", + "SIM", + "TID", + "TC", + "I", + "C90", + "E", + "W", + "F", + "PGH", + "UP", + "RUF", ] ignore = [ - # line too long (already checked by black) - "E501", - # the must-have __init__.py (we are using package namespaces) - "INP001" + "E501", + "A004", + "PT011",# there are a lof broad exception catches, fix later + "PT012", # multiple statements in pytest.raises are okay + "PT030", # also broad Depecration Warnings are okay + "TC001","TC002","TC003" # performance effect of type-like imports is minimal +] + +[tool.ruff.lint.per-file-ignores] +"**/example_*.py" = ["T201"] +"**/*_example.py" = ["T201"] +"**/examples/*.py" = ["T201"] +"tests/**" = [ + "SIM", # we don't need simple code in tests + "T201", # prints are okay in tests + "T203",# pprint is okay in tests + "B017",# assert Exception is okay in tests + "RUF059", # unused unpacked variables are okay in tests ] [tool.ruff.lint.pyupgrade] @@ -278,69 +291,37 @@ keep-runtime-typing = true [tool.ruff.lint.flake8-type-checking] strict = true +[tool.ruff.lint.flake8-pytest-style] +parametrize-names-type = "csv" + [tool.mypy] -python_version = "3.9" +python_version = "3.10" namespace_packages = true explicit_package_bases = true pretty = true show_error_codes = true +warn_return_any = true strict = true -fast_module_lookup = true -modules = ["testcontainers.core"] -mypy_path = [ - "core", -# "modules/arangodb", -# "modules/azurite", -# "modules/cassandra", -# "modules/clickhouse", -# "modules/elasticsearch", -# "modules/google", -# "modules/k3s", -# "modules/kafka", -# "modules/keycloak", -# "modules/localstack", - "modules/mailpit", -# "modules/minio", -# "modules/mongodb", -# "modules/mssql", -# "modules/mysql", -# "modules/neo4j", -# "modules/nginx", -# "modules/ollama", -# "modules/opensearch", -# "modules/oracle", -# "modules/postgres", -# "modules/rabbitmq", -# "modules/redis", -# "modules/selenium" - "modules/sftp", -# "modules/vault" -# "modules/weaviate" -] -enable_error_code = [ - "ignore-without-code", - "redundant-expr", - "truthy-bool", -] +mypy_path = ["src"] +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] [[tool.mypy.overrides]] -module = ['tests.*'] -# in pytest we allow fixtures to be more relaxed, though we check the untyped functions +module = ["tests.*"] check_untyped_defs = true -disable_error_code = [ - 'no-untyped-def' -] +disable_error_code = ["no-untyped-def"] [[tool.mypy.overrides]] -module = ['docker.*'] -# docker still doesn't have type annotations (not even 7.0) +module = ["pika.*"] ignore_missing_imports = true [[tool.mypy.overrides]] -module = ['wrapt.*'] -# wrapt doesn't have type annotations +module = ["influxdb.*"] ignore_missing_imports = true -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[[tool.mypy.overrides]] +module = ["cassandra.*"] +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = ["testcontainers.registry"] +ignore_missing_imports = true diff --git a/scripts/compute_modules.py b/scripts/compute_modules.py new file mode 100644 index 000000000..5b338ef2d --- /dev/null +++ b/scripts/compute_modules.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +Extract the community module names out of lists of changed files. +""" + +import json +import os +import sys + + +def compute_modules(files: list[str]) -> list[str]: + modules = set() + for f in files: + if f.startswith("src/testcontainers/community/"): + part = f.split("/")[3] + elif f.startswith("tests/community/"): + part = f.split("/")[2] + else: + continue + if not part or part.startswith("__") or part.endswith(".md"): + continue + modules.add(part) + return sorted(modules) + + +if __name__ == "__main__": + files = json.loads(sys.argv[1]) + modules = compute_modules(files) + result = json.dumps(modules) + print(f"computed_modules={result}") # noqa: T201 + with open(os.environ["GITHUB_OUTPUT"], "a") as fh: + fh.write(f"computed_modules={result}\n") diff --git a/modules/generic/tests/samples/fastapi/app/__init__.py b/src/testcontainers/__init__.py similarity index 100% rename from modules/generic/tests/samples/fastapi/app/__init__.py rename to src/testcontainers/__init__.py diff --git a/src/testcontainers/arangodb.py b/src/testcontainers/arangodb.py new file mode 100644 index 000000000..c371146c5 --- /dev/null +++ b/src/testcontainers/arangodb.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.arangodb import ( + ArangoDbContainer, +) + +warnings.warn( + "testcontainers.arangodb is deprecated, use testcontainers.community.arangodb instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ArangoDbContainer", +] diff --git a/src/testcontainers/aws.py b/src/testcontainers/aws.py new file mode 100644 index 000000000..3562101bb --- /dev/null +++ b/src/testcontainers/aws.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.aws import ( + AWSLambdaContainer, +) + +warnings.warn( + "testcontainers.aws is deprecated, use testcontainers.community.aws instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "AWSLambdaContainer", +] diff --git a/src/testcontainers/azurite.py b/src/testcontainers/azurite.py new file mode 100644 index 000000000..dd3b64419 --- /dev/null +++ b/src/testcontainers/azurite.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.azurite import ( + AzuriteContainer, + ConnectionStringType, +) + +warnings.warn( + "testcontainers.azurite is deprecated, use testcontainers.community.azurite instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "AzuriteContainer", + "ConnectionStringType", +] diff --git a/src/testcontainers/cassandra.py b/src/testcontainers/cassandra.py new file mode 100644 index 000000000..a936f95de --- /dev/null +++ b/src/testcontainers/cassandra.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.cassandra import ( + CassandraContainer, +) + +warnings.warn( + "testcontainers.cassandra is deprecated, use testcontainers.community.cassandra instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "CassandraContainer", +] diff --git a/src/testcontainers/chroma.py b/src/testcontainers/chroma.py new file mode 100644 index 000000000..21d9ea7ca --- /dev/null +++ b/src/testcontainers/chroma.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.chroma import ( + ChromaContainer, +) + +warnings.warn( + "testcontainers.chroma is deprecated, use testcontainers.community.chroma instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ChromaContainer", +] diff --git a/src/testcontainers/clickhouse.py b/src/testcontainers/clickhouse.py new file mode 100644 index 000000000..78c460e22 --- /dev/null +++ b/src/testcontainers/clickhouse.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.clickhouse import ( + ClickHouseContainer, +) + +warnings.warn( + "testcontainers.clickhouse is deprecated, use testcontainers.community.clickhouse instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ClickHouseContainer", +] diff --git a/src/testcontainers/cockroachdb.py b/src/testcontainers/cockroachdb.py new file mode 100644 index 000000000..39c8a32aa --- /dev/null +++ b/src/testcontainers/cockroachdb.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.cockroachdb import ( + CockroachDBContainer, +) + +warnings.warn( + "testcontainers.cockroachdb is deprecated, use testcontainers.community.cockroachdb instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "CockroachDBContainer", +] diff --git a/modules/README.md b/src/testcontainers/community/README.md similarity index 100% rename from modules/README.md rename to src/testcontainers/community/README.md diff --git a/modules/influxdb/tests/__init__.py b/src/testcontainers/community/__init__.py similarity index 100% rename from modules/influxdb/tests/__init__.py rename to src/testcontainers/community/__init__.py diff --git a/modules/arangodb/testcontainers/arangodb/__init__.py b/src/testcontainers/community/arangodb/__init__.py similarity index 97% rename from modules/arangodb/testcontainers/arangodb/__init__.py rename to src/testcontainers/community/arangodb/__init__.py index 9ea36f6ea..c81898ca2 100644 --- a/modules/arangodb/testcontainers/arangodb/__init__.py +++ b/src/testcontainers/community/arangodb/__init__.py @@ -23,7 +23,7 @@ class ArangoDbContainer(DbContainer): .. doctest:: - >>> from testcontainers.arangodb import ArangoDbContainer + >>> from testcontainers.community.arangodb import ArangoDbContainer >>> from arango import ArangoClient >>> with ArangoDbContainer("arangodb:3.11.8") as arango: diff --git a/modules/aws/testcontainers/aws/__init__.py b/src/testcontainers/community/aws/__init__.py similarity index 100% rename from modules/aws/testcontainers/aws/__init__.py rename to src/testcontainers/community/aws/__init__.py diff --git a/modules/aws/testcontainers/aws/aws_lambda.py b/src/testcontainers/community/aws/aws_lambda.py similarity index 86% rename from modules/aws/testcontainers/aws/aws_lambda.py rename to src/testcontainers/community/aws/aws_lambda.py index 30a1f0af9..d2c53b5b7 100644 --- a/modules/aws/testcontainers/aws/aws_lambda.py +++ b/src/testcontainers/community/aws/aws_lambda.py @@ -3,12 +3,14 @@ import httpx +from testcontainers.community.generic.server import ServerContainer from testcontainers.core.image import DockerImage -from testcontainers.generic.server import ServerContainer RIE_PATH = "/2015-03-31/functions/function/invocations" # AWS OS-only base images contain an Amazon Linux distribution and the runtime interface emulator (RIE) for Lambda. +# This comment can be removed (Used for testing) + class AWSLambdaContainer(ServerContainer): """ @@ -18,11 +20,11 @@ class AWSLambdaContainer(ServerContainer): .. doctest:: - >>> from testcontainers.aws import AWSLambdaContainer + >>> from testcontainers.community.aws import AWSLambdaContainer >>> from testcontainers.core.waiting_utils import wait_for_logs >>> from testcontainers.core.image import DockerImage - >>> with DockerImage(path="./modules/aws/tests/lambda_sample", tag="test-lambda:latest") as image: + >>> with DockerImage(path=f"{TEST_DIR}/community/aws/lambda_sample", tag="test-lambda:latest") as image: ... with AWSLambdaContainer(image=image, port=8080) as func: ... response = func.send_request(data={'payload': 'some data'}) ... assert response.status_code == 200 diff --git a/src/testcontainers/community/azurite/__init__.py b/src/testcontainers/community/azurite/__init__.py new file mode 100644 index 000000000..35700dc27 --- /dev/null +++ b/src/testcontainers/community/azurite/__init__.py @@ -0,0 +1,229 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import enum +import os +from typing import Optional + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.wait_strategies import PortWaitStrategy + + +class ConnectionStringType(enum.Enum): + """ + Enumeration for specifying the type of connection string to generate for Azurite. + + :cvar LOCALHOST: Represents a connection string for access from the host machine + where the tests are running. + :cvar NETWORK: Represents a connection string for access from another container + within the same Docker network as the Azurite container. + """ + + LOCALHOST = "localhost" + NETWORK = "network" + + +class AzuriteContainer(DockerContainer): + """ + The example below spins up an Azurite container and + shows an example to create a Blob service client with the container. The method + :code:`get_connection_string` can be used to create a client for Blob service, Queue service + and Table service. + + Example: + + .. doctest:: + + >>> from testcontainers.community.azurite import AzuriteContainer + >>> from azure.storage.blob import BlobServiceClient + + >>> with AzuriteContainer() as azurite_container: + ... connection_string = azurite_container.get_connection_string() + ... client = BlobServiceClient.from_connection_string( + ... connection_string, + ... api_version="2019-12-12" + ... ) + """ + + def __init__( + self, + image: str = "mcr.microsoft.com/azure-storage/azurite:latest", + *, + blob_service_port: int = 10_000, + queue_service_port: int = 10_001, + table_service_port: int = 10_002, + account_name: Optional[str] = None, + account_key: Optional[str] = None, + **kwargs, + ) -> None: + """Constructs an AzuriteContainer. + + Args: + image: Expects an image with tag. + **kwargs: Keyword arguments passed to super class. + """ + super().__init__(image=image, **kwargs) + self.account_name = account_name or os.environ.get("AZURITE_ACCOUNT_NAME", "devstoreaccount1") + self.account_key = account_key or os.environ.get( + "AZURITE_ACCOUNT_KEY", + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==", + ) + + raise_for_deprecated_parameter(kwargs, "ports_to_expose", "container.with_exposed_ports") + self.blob_service_port = blob_service_port + self.queue_service_port = queue_service_port + self.table_service_port = table_service_port + + self.with_exposed_ports(blob_service_port, queue_service_port, table_service_port) + self.with_env("AZURITE_ACCOUNTS", f"{self.account_name}:{self.account_key}") + + def get_connection_string( + self, connection_string_type: ConnectionStringType = ConnectionStringType.LOCALHOST + ) -> str: + """Retrieves the appropriate connection string for the Azurite container based on the specified access type. + + This method acts as a dispatcher, returning a connection string optimized + either for access from the host machine or for inter-container communication within the same Docker network. + + :param connection_string_type: The type of connection string to generate. + Use :attr:`ConnectionStringType.LOCALHOST` for connections + from the machine running the tests (default), or + :attr:`ConnectionStringType.NETWORK` for connections + from other containers within the same Docker network. + :type connection_string_type: ConnectionStringType + :return: The generated Azurite connection string. + :rtype: str + :raises ValueError: If an unrecognized `connection_string_type` is provided. + """ + if connection_string_type == ConnectionStringType.LOCALHOST: + return self.__get_local_connection_string() + elif connection_string_type == ConnectionStringType.NETWORK: + return self.__get_external_connection_string() + else: + raise ValueError( + f"unrecognized connection string type {connection_string_type}, " + f"Supported values are ConnectionStringType.LOCALHOST or ConnectionStringType.NETWORK " + ) + + def __get_local_connection_string(self) -> str: + """Generates a connection string for Azurite accessible from the local host machine. + + This connection string uses the Docker host IP address (obtained via + :meth:`testcontainers.core.container.DockerContainer.get_container_host_ip`) + and the dynamically exposed ports of the Azurite container. This ensures that + clients running on the host can connect successfully to the Azurite services. + + :return: The Azurite connection string for local host access. + :rtype: str + """ + host_ip = self.get_container_host_ip() + connection_string = ( + f"DefaultEndpointsProtocol=http;AccountName={self.account_name};AccountKey={self.account_key};" + ) + + if str(self.blob_service_port) in self.ports: + connection_string += ( + f"BlobEndpoint=http://{host_ip}:{self.get_exposed_port(self.blob_service_port)}/{self.account_name};" + ) + + if str(self.queue_service_port) in self.ports: + connection_string += ( + f"QueueEndpoint=http://{host_ip}:{self.get_exposed_port(self.queue_service_port)}/{self.account_name};" + ) + + if str(self.table_service_port) in self.ports: + connection_string += ( + f"TableEndpoint=http://{host_ip}:{self.get_exposed_port(self.table_service_port)}/{self.account_name};" + ) + + return connection_string + + def __get_external_connection_string(self) -> str: + """Generates a connection string for Azurite, primarily optimized for + inter-container communication within a custom Docker network. + + This method attempts to provide the most suitable connection string + based on the container's network configuration: + + - **For Inter-Container Communication (Recommended):** If the Azurite container is + part of a custom Docker network and has network aliases configured, + the connection string will use the first network alias as the hostname + and the internal container ports (e.g., #$#`http://:/`#$#). + This is the most efficient and robust way for other containers + in the same network to connect to Azurite, leveraging Docker's internal DNS. + + - **Fallback for Non-Networked/Aliased Scenarios:** If the container is + not on a custom network with aliases (e.g., running on the default + bridge network without explicit aliases), the method falls back to + using the Docker host IP (obtained via + :meth:`testcontainers.core.container.DockerContainer.get_container_host_ip`) + and the dynamically exposed ports (e.g., #$#`http://:/`#$#). + While this connection string is technically "external" to the container, + it primarily facilitates connections *from the host machine*. + + :return: The generated Azurite connection string. + :rtype: str + """ + # Check if we're on a custom network and have network aliases + if hasattr(self, "_network") and self._network and hasattr(self, "_network_aliases") and self._network_aliases: + # Use the first network alias for inter-container communication + host_ip = self._network_aliases[0] + # When using network aliases, use the internal container ports + blob_port = self.blob_service_port + queue_port = self.queue_service_port + table_port = self.table_service_port + else: + # Use the Docker host IP for external connections + host_ip = self.get_container_host_ip() + # When using host IP, use the exposed ports + blob_port = ( + self.get_exposed_port(self.blob_service_port) + if self.blob_service_port in self.ports + else self.blob_service_port + ) + queue_port = ( + self.get_exposed_port(self.queue_service_port) + if self.queue_service_port in self.ports + else self.queue_service_port + ) + table_port = ( + self.get_exposed_port(self.table_service_port) + if self.table_service_port in self.ports + else self.table_service_port + ) + + connection_string = ( + f"DefaultEndpointsProtocol=http;AccountName={self.account_name};AccountKey={self.account_key};" + ) + + if str(self.blob_service_port) in self.ports: + connection_string += f"BlobEndpoint=http://{host_ip}:{blob_port}/{self.account_name};" + + if str(self.queue_service_port) in self.ports: + connection_string += f"QueueEndpoint=http://{host_ip}:{queue_port}/{self.account_name};" + + if str(self.table_service_port) in self.ports: + connection_string += f"TableEndpoint=http://{host_ip}:{table_port}/{self.account_name};" + + return connection_string + + def start(self) -> Self: + super().start() + self._connect() + return self + + def _connect(self) -> None: + strategy = PortWaitStrategy(int(next(iter(self.ports)))) + strategy.wait_until_ready(self) diff --git a/modules/cassandra/testcontainers/cassandra/__init__.py b/src/testcontainers/community/cassandra/__init__.py similarity index 82% rename from modules/cassandra/testcontainers/cassandra/__init__.py rename to src/testcontainers/community/cassandra/__init__.py index 4e6618b7b..08a801354 100644 --- a/modules/cassandra/testcontainers/cassandra/__init__.py +++ b/src/testcontainers/community/cassandra/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.wait_strategies import LogMessageWaitStrategy class CassandraContainer(DockerContainer): @@ -20,14 +20,15 @@ class CassandraContainer(DockerContainer): Example: - .. doctest:: + .. doctest:: cassandra_container - >>> from testcontainers.cassandra import CassandraContainer + >>> from testcontainers.community.cassandra import CassandraContainer >>> from cassandra.cluster import Cluster, DCAwareRoundRobinPolicy >>> with CassandraContainer("cassandra:4.1.4") as cassandra, Cluster( ... cassandra.get_contact_points(), ... load_balancing_policy=DCAwareRoundRobinPolicy(cassandra.get_local_datacenter()), + ... protocol_version=5, ... ) as cluster: ... session = cluster.connect() ... result = session.execute("SELECT release_version FROM system.local;") @@ -38,7 +39,9 @@ class CassandraContainer(DockerContainer): CQL_PORT = 9042 DEFAULT_LOCAL_DATACENTER = "datacenter1" - def __init__(self, image: str = "cassandra:latest", **kwargs) -> None: + def __init__( + self, image: str = "cassandra:latest", wait_strategy_check_string: str = "Startup complete", **kwargs + ) -> None: super().__init__(image=image, **kwargs) self.with_exposed_ports(self.CQL_PORT) self.with_env("JVM_OPTS", "-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0") @@ -46,14 +49,7 @@ def __init__(self, image: str = "cassandra:latest", **kwargs) -> None: self.with_env("MAX_HEAP_SIZE", "1024M") self.with_env("CASSANDRA_ENDPOINT_SNITCH", "GossipingPropertyFileSnitch") self.with_env("CASSANDRA_DC", self.DEFAULT_LOCAL_DATACENTER) - - def _connect(self): - wait_for_logs(self, "Startup complete") - - def start(self) -> "CassandraContainer": - super().start() - self._connect() - return self + self.waiting_for(LogMessageWaitStrategy(wait_strategy_check_string)) def get_contact_points(self) -> list[tuple[str, int]]: return [(self.get_container_host_ip(), int(self.get_exposed_port(self.CQL_PORT)))] diff --git a/modules/chroma/testcontainers/chroma/__init__.py b/src/testcontainers/community/chroma/__init__.py similarity index 86% rename from modules/chroma/testcontainers/chroma/__init__.py rename to src/testcontainers/community/chroma/__init__.py index 9e5744099..41f71bc64 100644 --- a/modules/chroma/testcontainers/chroma/__init__.py +++ b/src/testcontainers/community/chroma/__init__.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING from requests import ConnectionError, get +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter @@ -20,7 +21,7 @@ class ChromaContainer(DockerContainer): .. doctest:: >>> import chromadb - >>> from testcontainers.chroma import ChromaContainer + >>> from testcontainers.community.chroma import ChromaContainer >>> with ChromaContainer() as chroma: ... config = chroma.get_config() @@ -32,13 +33,13 @@ class ChromaContainer(DockerContainer): def __init__( self, - image: str = "chromadb/chroma:latest", + image: str = "chromadb/chroma:1.0.0", port: int = 8000, **kwargs, ) -> None: """ Args: - image: Docker image to use for the MinIO container. + image: Docker image to use for the ChromaDB container. port: Port to expose on the container. access_key: Access key for client connections. secret_key: Secret key for client connections. @@ -55,7 +56,7 @@ def get_config(self) -> dict: including the endpoint. Returns: - dict: {`endpoint`: str} + dict: {`endpoint`: str, `host`: str, `port`: int} """ host_ip = self.get_container_host_ip() exposed_port = self.get_exposed_port(self.port) @@ -69,11 +70,11 @@ def get_config(self) -> dict: def _healthcheck(self) -> None: """This is an internal method used to check if the Chroma container is healthy and ready to receive requests.""" - url = f"http://{self.get_config()['endpoint']}/api/v1/heartbeat" + url = f"http://{self.get_config()['endpoint']}/api/v2/heartbeat" response: Response = get(url) response.raise_for_status() - def start(self) -> "ChromaContainer": + def start(self) -> Self: """This method starts the Chroma container and runs the healthcheck to verify that the container is ready to use.""" super().start() diff --git a/modules/clickhouse/testcontainers/clickhouse/__init__.py b/src/testcontainers/community/clickhouse/__init__.py similarity index 84% rename from modules/clickhouse/testcontainers/clickhouse/__init__.py rename to src/testcontainers/community/clickhouse/__init__.py index fbc8fab65..c144e2a8a 100644 --- a/modules/clickhouse/testcontainers/clickhouse/__init__.py +++ b/src/testcontainers/community/clickhouse/__init__.py @@ -12,12 +12,10 @@ # under the License. import os from typing import Optional -from urllib.error import HTTPError, URLError -from urllib.request import urlopen from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.wait_strategies import HttpWaitStrategy class ClickHouseContainer(DbContainer): @@ -32,7 +30,7 @@ class ClickHouseContainer(DbContainer): .. doctest:: >>> import clickhouse_driver - >>> from testcontainers.clickhouse import ClickHouseContainer + >>> from testcontainers.community.clickhouse import ClickHouseContainer >>> with ClickHouseContainer("clickhouse/clickhouse-server:21.8") as clickhouse: ... client = clickhouse_driver.Client.from_url(clickhouse.get_connection_url()) @@ -58,12 +56,9 @@ def __init__( self.with_exposed_ports(self.port) self.with_exposed_ports(8123) - @wait_container_is_ready(HTTPError, URLError) def _connect(self) -> None: - # noinspection HttpUrlsUsage - url = f"http://{self.get_container_host_ip()}:{self.get_exposed_port(8123)}" - with urlopen(url) as r: - assert b"Ok" in r.read() + strategy = HttpWaitStrategy(8123).for_response_predicate(lambda response: "Ok" in response) + strategy.wait_until_ready(self) def _configure(self) -> None: self.with_env("CLICKHOUSE_USER", self.username) diff --git a/modules/cockroachdb/testcontainers/cockroachdb/__init__.py b/src/testcontainers/community/cockroachdb/__init__.py similarity index 97% rename from modules/cockroachdb/testcontainers/cockroachdb/__init__.py rename to src/testcontainers/community/cockroachdb/__init__.py index 13a17ed5c..51bfdabd0 100644 --- a/modules/cockroachdb/testcontainers/cockroachdb/__init__.py +++ b/src/testcontainers/community/cockroachdb/__init__.py @@ -33,7 +33,7 @@ class CockroachDBContainer(DbContainer): .. doctest:: >>> import sqlalchemy - >>> from testcontainers.cockroachdb import CockroachDBContainer + >>> from testcontainers.community.cockroachdb import CockroachDBContainer >>> with CockroachDBContainer('cockroachdb/cockroach:v24.1.1') as crdb: ... engine = sqlalchemy.create_engine(crdb.get_connection_url()) diff --git a/modules/cosmosdb/testcontainers/cosmosdb/__init__.py b/src/testcontainers/community/cosmosdb/__init__.py similarity index 100% rename from modules/cosmosdb/testcontainers/cosmosdb/__init__.py rename to src/testcontainers/community/cosmosdb/__init__.py diff --git a/modules/cosmosdb/testcontainers/cosmosdb/_emulator.py b/src/testcontainers/community/cosmosdb/_emulator.py similarity index 91% rename from modules/cosmosdb/testcontainers/cosmosdb/_emulator.py rename to src/testcontainers/community/cosmosdb/_emulator.py index 161a01c29..aed3fda37 100644 --- a/modules/cosmosdb/testcontainers/cosmosdb/_emulator.py +++ b/src/testcontainers/community/cosmosdb/_emulator.py @@ -2,7 +2,6 @@ import socket import ssl from collections.abc import Iterable -from distutils.util import strtobool from urllib.error import HTTPError, URLError from urllib.request import urlopen @@ -16,6 +15,7 @@ __all__ = ["CosmosDBEmulatorContainer"] EMULATOR_PORT = 8081 +_ENV_TRUTHY = {"true", "1", "yes"} class CosmosDBEmulatorContainer(DockerContainer): @@ -32,15 +32,17 @@ def __init__( "AZURE_COSMOS_EMULATOR_IMAGE", "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest" ), partition_count: int = os.getenv("AZURE_COSMOS_EMULATOR_PARTITION_COUNT", None), - enable_data_persistence: bool = strtobool(os.getenv("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false")), + enable_data_persistence: bool = ( + os.getenv("AZURE_COSMOS_EMULATOR_ENABLE_DATA_PERSISTENCE", "false").strip().lower() in _ENV_TRUTHY + ), key: str = os.getenv( "AZURE_COSMOS_EMULATOR_KEY", "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", ), - bind_ports: bool = strtobool(os.getenv("AZURE_COSMOS_EMULATOR_BIND_PORTS", "true")), + bind_ports: bool = os.getenv("AZURE_COSMOS_EMULATOR_BIND_PORTS", "true").strip().lower() in _ENV_TRUTHY, endpoint_ports: Iterable[int] = [], **other_kwargs, - ): + ) -> None: super().__init__(image=image, **other_kwargs) self.endpoint_ports = endpoint_ports self.partition_count = partition_count diff --git a/modules/cosmosdb/testcontainers/cosmosdb/_grab.py b/src/testcontainers/community/cosmosdb/_grab.py similarity index 100% rename from modules/cosmosdb/testcontainers/cosmosdb/_grab.py rename to src/testcontainers/community/cosmosdb/_grab.py diff --git a/modules/cosmosdb/testcontainers/cosmosdb/mongodb.py b/src/testcontainers/community/cosmosdb/mongodb.py similarity index 90% rename from modules/cosmosdb/testcontainers/cosmosdb/mongodb.py rename to src/testcontainers/community/cosmosdb/mongodb.py index 82e8c096b..c46d472d5 100644 --- a/modules/cosmosdb/testcontainers/cosmosdb/mongodb.py +++ b/src/testcontainers/community/cosmosdb/mongodb.py @@ -15,7 +15,7 @@ class CosmosDBMongoEndpointContainer(CosmosDBEmulatorContainer): .. code-block:: python - >>> from testcontainers.cosmosdb import CosmosDBMongoEndpointContainer + >>> from testcontainers.community.cosmosdb import CosmosDBMongoEndpointContainer >>> with CosmosDBMongoEndpointContainer(mongodb_version="4.0") as emulator: ... print(f"Point your MongoDB client at {emulator.host}:{emulator.port} using key {emulator.key}") @@ -27,10 +27,10 @@ def __init__( self, mongodb_version: str, image: str = os.getenv( - "AZURE_COSMOS_EMULATOR_IMAGE", "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:mongodb" + "AZURE_COSMOS_EMULATOR_IMAGE", "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest" ), **other_kwargs, - ): + ) -> None: super().__init__(image=image, endpoint_ports=[ENDPOINT_PORT], **other_kwargs) assert mongodb_version is not None, "A MongoDB version is required to use the MongoDB Endpoint" self.mongodb_version = mongodb_version diff --git a/modules/cosmosdb/testcontainers/cosmosdb/nosql.py b/src/testcontainers/community/cosmosdb/nosql.py similarity index 89% rename from modules/cosmosdb/testcontainers/cosmosdb/nosql.py rename to src/testcontainers/community/cosmosdb/nosql.py index f78469674..864dccbfd 100644 --- a/modules/cosmosdb/testcontainers/cosmosdb/nosql.py +++ b/src/testcontainers/community/cosmosdb/nosql.py @@ -19,13 +19,13 @@ class CosmosDBNoSQLEndpointContainer(CosmosDBEmulatorContainer): .. code-block:: python - >>> from testcontainers.cosmosdb import CosmosDBNoSQLEndpointContainer + >>> from testcontainers.community.cosmosdb import CosmosDBNoSQLEndpointContainer >>> with CosmosDBNoSQLEndpointContainer() as emulator: ... db = emulator.insecure_sync_client().create_database_if_not_exists("test") .. code-block:: python - >>> from testcontainers.cosmosdb import CosmosDBNoSQLEndpointContainer + >>> from testcontainers.community.cosmosdb import CosmosDBNoSQLEndpointContainer >>> from azure.cosmos import CosmosClient >>> with CosmosDBNoSQLEndpointContainer() as emulator: @@ -34,7 +34,7 @@ class CosmosDBNoSQLEndpointContainer(CosmosDBEmulatorContainer): """ - def __init__(self, **kwargs): + def __init__(self, **kwargs: object) -> None: super().__init__(endpoint_ports=[NOSQL_PORT], **kwargs) @property diff --git a/src/testcontainers/community/cratedb/__init__.py b/src/testcontainers/community/cratedb/__init__.py new file mode 100644 index 000000000..021f97075 --- /dev/null +++ b/src/testcontainers/community/cratedb/__init__.py @@ -0,0 +1,112 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import os +from typing import ClassVar, Optional + +from testcontainers.community.generic.sql import SqlContainer +from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.wait_strategies import HttpWaitStrategy + +# CrateDB's HTTP interface. The SQLAlchemy `crate://` dialect talks to CrateDB +# over HTTP, so this is the port used for connection URLs and readiness checks. +HTTP_PORT = 4200 +# CrateDB's PostgreSQL wire-protocol interface, exposed for convenience. +PSQL_PORT = 5432 + + +class CrateDBContainer(SqlContainer): + """ + CrateDB database container. + + Example: + + The example spins up a CrateDB database and connects to it using + SQLAlchemy and the ``sqlalchemy-cratedb`` dialect, which talks to + CrateDB over its HTTP interface (port 4200). + + .. doctest:: + + >>> from testcontainers.community.cratedb import CrateDBContainer + >>> import sqlalchemy + + >>> with CrateDBContainer("crate:5.10") as cratedb: + ... engine = sqlalchemy.create_engine(cratedb.get_connection_url()) + ... with engine.begin() as connection: + ... result = connection.execute(sqlalchemy.text("select name from sys.cluster")) + ... cluster_name, = result.fetchone() + """ + + # Default command-line options. CrateDB needs single-node discovery to run + # as a one-node cluster suitable for testing. + CMD_OPTS: ClassVar[list[tuple[str, str]]] = [("discovery.type", "single-node")] + + def __init__( + self, + image: str = "crate/crate:latest", + port: int = HTTP_PORT, + username: Optional[str] = None, + password: Optional[str] = None, + dialect: str = "crate", + cmd_opts: Optional[list[tuple[str, str]]] = None, + **kwargs, + ) -> None: + """ + :param image: Docker image name (with optional tag). + :param port: container port used to build the connection URL; defaults + to the HTTP port (4200) used by the ``crate://`` dialect. + :param username: username for the DB; falls back to the ``CRATEDB_USER`` + environment variable, then ``crate``. + :param password: password for the DB; falls back to the + ``CRATEDB_PASSWORD`` environment variable, then ``crate``. + :param dialect: SQLAlchemy dialect used in the connection URL. + :param cmd_opts: extra ``-C=`` options passed to CrateDB, + merged over (and able to override) the defaults. + """ + raise_for_deprecated_parameter(kwargs, "user", "username") + # Readiness is signaled by CrateDB's HTTP interface returning 200; this + # keeps startup free of any database client library. + super().__init__(image, wait_strategy=HttpWaitStrategy(HTTP_PORT).for_status_code(200), **kwargs) + + cmd_opts = cmd_opts or [] + default_cmd_opts = [s for s in self.CMD_OPTS if s[0] not in {k[0] for k in cmd_opts}] + self._command = self._build_cmd([*default_cmd_opts, *cmd_opts]) + + self.username = username or os.environ.get("CRATEDB_USER", "crate") + self.password = password or os.environ.get("CRATEDB_PASSWORD", "crate") + self.port = port + self.dialect = dialect + + self.with_exposed_ports(HTTP_PORT, PSQL_PORT) + + @staticmethod + def _build_cmd(opts: list[tuple[str, str]]) -> str: + """Render a CrateDB ``-C= ...`` command-line string.""" + cmd = [] + for key, val in opts: + if isinstance(val, bool): + val = str(val).lower() + cmd.append(f"-C{key}={val}") + return " ".join(cmd) + + def _configure(self) -> None: + self.with_env("CRATEDB_USER", self.username) + self.with_env("CRATEDB_PASSWORD", self.password) + + def get_connection_url(self, host: Optional[str] = None) -> str: + return self._create_connection_url( + dialect=self.dialect, + username=self.username, + password=self.password, + host=host, + port=self.port, + ) diff --git a/modules/db2/testcontainers/db2/__init__.py b/src/testcontainers/community/db2/__init__.py similarity index 96% rename from modules/db2/testcontainers/db2/__init__.py rename to src/testcontainers/community/db2/__init__.py index b17c0efef..767e49f7f 100644 --- a/modules/db2/testcontainers/db2/__init__.py +++ b/src/testcontainers/community/db2/__init__.py @@ -14,7 +14,7 @@ class Db2Container(DbContainer): .. doctest:: >>> import sqlalchemy - >>> from testcontainers.db2 import Db2Container + >>> from testcontainers.community.db2 import Db2Container >>> with Db2Container("icr.io/db2_community/db2:latest") as db2: ... engine = sqlalchemy.create_engine(db2.get_connection_url()) diff --git a/modules/elasticsearch/testcontainers/elasticsearch/__init__.py b/src/testcontainers/community/elasticsearch/__init__.py similarity index 79% rename from modules/elasticsearch/testcontainers/elasticsearch/__init__.py rename to src/testcontainers/community/elasticsearch/__init__.py index 1b943916a..816e441cd 100644 --- a/modules/elasticsearch/testcontainers/elasticsearch/__init__.py +++ b/src/testcontainers/community/elasticsearch/__init__.py @@ -12,12 +12,10 @@ # under the License. import logging import re -import urllib -from urllib.error import URLError from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.wait_strategies import HttpWaitStrategy _FALLBACK_VERSION = 8 """This version is used when no version could be detected from the image name.""" @@ -47,7 +45,7 @@ def _environment_by_version(version: int) -> dict[str, str]: return {"discovery.zen.minimum_master_nodes": "1"} elif version == 7: return {} - elif version == 8: + elif version in {8, 9}: # Elasticsearch uses https now by default. However, our readiness # check uses http, which does not work. Hence we disable security # which should not be an issue for our context @@ -66,17 +64,17 @@ class ElasticSearchContainer(DockerContainer): >>> import json >>> import urllib - >>> from testcontainers.elasticsearch import ElasticSearchContainer + >>> from testcontainers.community.elasticsearch import ElasticSearchContainer >>> with ElasticSearchContainer(f'elasticsearch:8.3.3', mem_limit='3G') as es: - ... resp = urllib.request.urlopen(es.get_url()) + ... resp = urllib.request.urlopen(f'http://{es.get_container_host_ip()}:{es.get_exposed_port(es.port)}') ... json.loads(resp.read().decode())['version']['number'] '8.3.3' """ - def __init__(self, image: str = "elasticsearch", port: int = 9200, **kwargs) -> None: + def __init__(self, image: str = "elasticsearch", port: int = 9200, **kwargs: object) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super().__init__(image, **kwargs) + super().__init__(image, _wait_strategy=HttpWaitStrategy(port), **kwargs) self.port = port self.with_exposed_ports(self.port) self.with_env("transport.host", "127.0.0.1") @@ -85,19 +83,3 @@ def __init__(self, image: str = "elasticsearch", port: int = 9200, **kwargs) -> major_version = _major_version_from_image_name(image) for key, value in _environment_by_version(major_version).items(): self.with_env(key, value) - - @wait_container_is_ready(URLError) - def _connect(self) -> None: - res = urllib.request.urlopen(self.get_url()) - if res.status != 200: - raise Exception() - - def get_url(self) -> str: - host = self.get_container_host_ip() - port = self.get_exposed_port(self.port) - return f"http://{host}:{port}" - - def start(self) -> "ElasticSearchContainer": - super().start() - self._connect() - return self diff --git a/modules/generic/testcontainers/generic/__init__.py b/src/testcontainers/community/generic/__init__.py similarity index 53% rename from modules/generic/testcontainers/generic/__init__.py rename to src/testcontainers/community/generic/__init__.py index f239a80c6..ce6610a3c 100644 --- a/modules/generic/testcontainers/generic/__init__.py +++ b/src/testcontainers/community/generic/__init__.py @@ -1 +1,2 @@ from .server import ServerContainer # noqa: F401 +from .sql import SqlContainer # noqa: F401 diff --git a/src/testcontainers/community/generic/providers/__init__.py b/src/testcontainers/community/generic/providers/__init__.py new file mode 100644 index 000000000..5b5eb95a2 --- /dev/null +++ b/src/testcontainers/community/generic/providers/__init__.py @@ -0,0 +1 @@ +from .sql_connection_wait_strategy import SqlAlchemyConnectWaitStrategy # noqa: F401 diff --git a/src/testcontainers/community/generic/providers/sql_connection_wait_strategy.py b/src/testcontainers/community/generic/providers/sql_connection_wait_strategy.py new file mode 100644 index 000000000..a1e1c64f2 --- /dev/null +++ b/src/testcontainers/community/generic/providers/sql_connection_wait_strategy.py @@ -0,0 +1,48 @@ +# This module provides a wait strategy for SQL database connectivity testing using SQLAlchemy. +# It includes handling for transient exceptions and connection retries. + +import logging + +from testcontainers.core.waiting_utils import WaitStrategy, WaitStrategyTarget + +logger = logging.getLogger(__name__) + +ADDITIONAL_TRANSIENT_ERRORS = [] +try: + from sqlalchemy.exc import DBAPIError + + ADDITIONAL_TRANSIENT_ERRORS.append(DBAPIError) +except ImportError: + logger.debug("SQLAlchemy not available, skipping DBAPIError handling") + + +class SqlAlchemyConnectWaitStrategy(WaitStrategy): + """Wait strategy for database connectivity testing using SQLAlchemy.""" + + def __init__(self) -> None: + super().__init__() + self.with_transient_exceptions(TimeoutError, ConnectionError, *ADDITIONAL_TRANSIENT_ERRORS) + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """Test database connectivity with retry logic until success or timeout.""" + if not hasattr(container, "get_connection_url"): + raise AttributeError(f"Container {container} must have a get_connection_url method") + + try: + import sqlalchemy + except ImportError as e: + raise ImportError("SQLAlchemy is required for database containers") from e + + def _test_connection() -> bool: + """Test database connection, returning True if successful.""" + engine = sqlalchemy.create_engine(container.get_connection_url()) + try: + with engine.connect(): + logger.info("Database connection successful") + return True + finally: + engine.dispose() + + result = self._poll(_test_connection) + if not result: + raise TimeoutError(f"Database connection failed after {self._startup_timeout}s timeout") diff --git a/modules/generic/testcontainers/generic/server.py b/src/testcontainers/community/generic/server.py similarity index 70% rename from modules/generic/testcontainers/generic/server.py rename to src/testcontainers/community/generic/server.py index fe990f179..206d68982 100644 --- a/modules/generic/testcontainers/generic/server.py +++ b/src/testcontainers/community/generic/server.py @@ -1,13 +1,12 @@ from typing import Union -from urllib.error import HTTPError, URLError -from urllib.request import urlopen import httpx +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.image import DockerImage -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.wait_strategies import HttpWaitStrategy class ServerContainer(DockerContainer): @@ -19,11 +18,11 @@ class ServerContainer(DockerContainer): .. doctest:: >>> import httpx - >>> from testcontainers.generic import ServerContainer + >>> from testcontainers.community.generic import ServerContainer >>> from testcontainers.core.waiting_utils import wait_for_logs >>> from testcontainers.core.image import DockerImage - >>> with DockerImage(path="./modules/generic/tests/samples/python_server", tag="test-srv:latest") as image: + >>> with DockerImage(path=f"{TEST_DIR}/community/generic/samples/python_server", tag="test-srv:latest") as image: ... with ServerContainer(port=9000, image=image) as srv: ... url = srv._create_connection_url() ... response = httpx.get(f"{url}", timeout=5) @@ -40,19 +39,9 @@ def __init__(self, port: int, image: Union[str, DockerImage]) -> None: self.internal_port = port self.with_exposed_ports(self.internal_port) - @wait_container_is_ready(HTTPError, URLError) def _connect(self) -> None: - # noinspection HttpUrlsUsage - url = self._create_connection_url() - try: - with urlopen(url) as r: - assert b"" in r.read() - except HTTPError as e: - # 404 is expected, as the server may not have the specific endpoint we are looking for - if e.code == 404: - pass - else: - raise + strategy = HttpWaitStrategy(self.internal_port).for_status_code(404) + strategy.wait_until_ready(self) def get_api_url(self) -> str: raise NotImplementedError @@ -65,7 +54,7 @@ def _create_connection_url(self) -> str: url = f"http://{host}:{exposed_port}" return url - def start(self) -> "ServerContainer": + def start(self) -> Self: super().start() self._connect() return self diff --git a/src/testcontainers/community/generic/sql.py b/src/testcontainers/community/generic/sql.py new file mode 100644 index 000000000..9fa9abe2d --- /dev/null +++ b/src/testcontainers/community/generic/sql.py @@ -0,0 +1,141 @@ +import logging +from typing import Any, Optional +from urllib.parse import quote, urlencode + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.waiting_utils import WaitStrategy + +logger = logging.getLogger(__name__) + + +class SqlContainer(DockerContainer): + """ + Generic SQL database container providing common functionality. + + This class can serve as a base for database-specific container implementations. + It provides connection management, URL construction, and basic lifecycle methods. + Database connection readiness is automatically handled by the provided wait strategy. + + Note: `SqlAlchemyConnectWaitStrategy` from `sql_connection_wait_strategy` is a provided wait strategy for SQL databases. + """ + + def __init__(self, image: str, wait_strategy: WaitStrategy, **kwargs: object) -> None: + """ + Initialize SqlContainer with optional wait strategy. + + Args: + image: Docker image name + wait_strategy: Wait strategy for SQL database connectivity + **kwargs: Additional arguments passed to DockerContainer + """ + super().__init__(image, **kwargs) + self.wait_strategy = wait_strategy + + def _create_connection_url( + self, + dialect: str, + username: str, + password: str, + host: Optional[str] = None, + port: Optional[int] = None, + dbname: Optional[str] = None, + query_params: Optional[dict[str, str]] = None, + **kwargs: Any, + ) -> str: + """ + Create a database connection URL. + + Args: + dialect: Database dialect (e.g., 'postgresql', 'mysql') + username: Database username + password: Database password + host: Database host (defaults to container host) + port: Database port + dbname: Database name + query_params: Additional query parameters for the URL + **kwargs: Additional parameters (checked for deprecated usage) + + Returns: + str: Formatted database connection URL + + Raises: + ValueError: If unexpected arguments are provided or required parameters are missing + ContainerStartException: If container is not started + """ + + if self._container is None: + raise ContainerStartException("Container has not been started") + + host = host or self.get_container_host_ip() + exposed_port = self.get_exposed_port(port) + quoted_password = quote(password, safe="") + quoted_username = quote(username, safe="") + url = f"{dialect}://{quoted_username}:{quoted_password}@{host}:{exposed_port}" + + if dbname: + quoted_dbname = quote(dbname, safe="") + url = f"{url}/{quoted_dbname}" + + if query_params: + query_string = urlencode(query_params) + url = f"{url}?{query_string}" + + return url + + def start(self) -> Self: + """ + Start the database container and perform initialization. + + Returns: + SqlContainer: Self for method chaining + + Raises: + ContainerStartException: If container fails to start + Exception: If configuration, seed transfer, or connection fails + """ + logger.info(f"Starting database container: {self.image}") + + try: + self._configure() + self.waiting_for(self.wait_strategy) + super().start() + self._transfer_seed() + logger.info("Database container started successfully") + except Exception as e: + logger.error(f"Failed to start database container: {e}") + raise + + return self + + def _configure(self) -> None: + """ + Configure the database container before starting. + + Raises: + NotImplementedError: Must be implemented by subclasses + """ + raise NotImplementedError("Subclasses must implement _configure()") + + def _transfer_seed(self) -> None: + """ + Transfer seed data to the database container. + + This method can be overridden by subclasses to provide + database-specific seeding functionality. + """ + logger.debug("No seed data to transfer") + + def get_connection_url(self) -> str: + """ + Get the database connection URL. + + Returns: + str: Database connection URL + + Raises: + NotImplementedError: Must be implemented by subclasses + """ + raise NotImplementedError("Subclasses must implement get_connection_url()") diff --git a/modules/google/testcontainers/google/__init__.py b/src/testcontainers/community/google/__init__.py similarity index 100% rename from modules/google/testcontainers/google/__init__.py rename to src/testcontainers/community/google/__init__.py diff --git a/modules/google/testcontainers/google/datastore.py b/src/testcontainers/community/google/datastore.py similarity index 94% rename from modules/google/testcontainers/google/datastore.py rename to src/testcontainers/community/google/datastore.py index 24edbdcd7..ff0540999 100644 --- a/modules/google/testcontainers/google/datastore.py +++ b/src/testcontainers/community/google/datastore.py @@ -14,6 +14,7 @@ from unittest.mock import patch from google.cloud import datastore + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -30,7 +31,7 @@ class DatastoreContainer(DockerContainer): .. doctest:: - >>> from testcontainers.google import DatastoreContainer + >>> from testcontainers.community.google import DatastoreContainer >>> config = DatastoreContainer() >>> with config as datastore: @@ -55,7 +56,7 @@ def __init__( def get_datastore_emulator_host(self) -> str: return f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" - def get_datastore_client(self, **kwargs) -> datastore.Client: + def get_datastore_client(self, **kwargs: object) -> datastore.Client: wait_for_logs(self, "Dev App Server is now running.", timeout=30.0) env_vars = { "DATASTORE_DATASET": self.project, diff --git a/modules/google/testcontainers/google/pubsub.py b/src/testcontainers/community/google/pubsub.py similarity index 88% rename from modules/google/testcontainers/google/pubsub.py rename to src/testcontainers/community/google/pubsub.py index 706030599..b701c761d 100644 --- a/modules/google/testcontainers/google/pubsub.py +++ b/src/testcontainers/community/google/pubsub.py @@ -11,11 +11,15 @@ # License for the specific language governing permissions and limitations # under the License. import os +from typing import TypeVar from unittest.mock import patch from google.cloud import pubsub + from testcontainers.core.container import DockerContainer +T = TypeVar("T") + class PubSubContainer(DockerContainer): """ @@ -30,7 +34,7 @@ class PubSubContainer(DockerContainer): .. doctest:: - >>> from testcontainers.google import PubSubContainer + >>> from testcontainers.community.google import PubSubContainer >>> config = PubSubContainer() >>> with config as pubsub: @@ -51,18 +55,18 @@ def __init__( def get_pubsub_emulator_host(self) -> str: return f"{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" - def _get_client(self, cls: type, **kwargs) -> dict: + def _get_client(self, cls: type[T], **kwargs: object) -> T: with patch.dict(os.environ, PUBSUB_EMULATOR_HOST=self.get_pubsub_emulator_host()): return cls(**kwargs) - def get_publisher_client(self, **kwargs) -> pubsub.PublisherClient: + def get_publisher_client(self, **kwargs: object) -> pubsub.PublisherClient: from google.auth import credentials kwargs["client_options"] = {"api_endpoint": self.get_pubsub_emulator_host()} kwargs["credentials"] = credentials.AnonymousCredentials() return self._get_client(pubsub.PublisherClient, **kwargs) - def get_subscriber_client(self, **kwargs) -> pubsub.SubscriberClient: + def get_subscriber_client(self, **kwargs: object) -> pubsub.SubscriberClient: from google.auth import credentials kwargs["client_options"] = {"api_endpoint": self.get_pubsub_emulator_host()} diff --git a/src/testcontainers/community/influxdb/__init__.py b/src/testcontainers/community/influxdb/__init__.py new file mode 100644 index 000000000..7fe141137 --- /dev/null +++ b/src/testcontainers/community/influxdb/__init__.py @@ -0,0 +1,31 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +testcontainers.community.influxdb provides means to spawn an InfluxDB instance within a Docker container. + +- The InfluxDbContainer provides the common mechanism to spawn an InfluxDB container. + You are not likely to use this module directly. +- Import the InfluxDb1Container class to spawn a container for an InfluxDB 1.x instance +- Import the InfluxDb2Container class to spawn a container for an InfluxDB 2.x instance + +The 2 containers are separated in different modules for 2 reasons: +- because the Docker images are not designed to be used in the same way +- because the InfluxDB clients are different for 1.x and 2.x versions, + so you won't have to install dependencies that you do not need +""" + +from .base import InfluxDbContainer +from .version1 import InfluxDb1Container +from .version2 import InfluxDb2Container + +__all__ = ["InfluxDb1Container", "InfluxDb2Container", "InfluxDbContainer"] diff --git a/modules/influxdb/testcontainers/influxdb.py b/src/testcontainers/community/influxdb/base.py similarity index 78% rename from modules/influxdb/testcontainers/influxdb.py rename to src/testcontainers/community/influxdb/base.py index d8956e992..3c08de932 100644 --- a/modules/influxdb/testcontainers/influxdb.py +++ b/src/testcontainers/community/influxdb/base.py @@ -11,26 +11,11 @@ # License for the specific language governing permissions and limitations # under the License. -""" -testcontainers/influxdb provides means to spawn an InfluxDB instance within a Docker container. - -- this influxdb.py module provides the common mechanism to spawn an InfluxDB container. - You are not likely to use this module directly. -- import the InfluxDb1Container class from the influxdb1/__init__.py module to spawn - a container for an InfluxDB 1.x instance -- import the InfluxDb2Container class from the influxdb2/__init__.py module to spawn - a container for an InfluxDB 2.x instance - -The 2 containers are separated in different modules for 2 reasons: -- because the Docker images are not designed to be used in the same way -- because the InfluxDB clients are different for 1.x and 2.x versions, - so you won't have to install dependencies that you do not need -""" - from typing import Optional from requests import get from requests.exceptions import ConnectionError, ReadTimeout +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -54,7 +39,7 @@ def __init__( # specifies the port on the host machine where influxdb is exposed; a random available port otherwise host_port: Optional[int] = None, **docker_client_kw, - ): + ) -> None: super().__init__(image=image, **docker_client_kw) self.container_port = container_port self.host_port = host_port @@ -90,7 +75,7 @@ def get_influxdb_version(self) -> str: return self._health_check().get("version") - def start(self) -> "InfluxDbContainer": + def start(self) -> Self: """ Spawns a container of the InfluxDB Docker image, ready to be used. """ diff --git a/modules/influxdb/testcontainers/influxdb1/__init__.py b/src/testcontainers/community/influxdb/version1.py similarity index 91% rename from modules/influxdb/testcontainers/influxdb1/__init__.py rename to src/testcontainers/community/influxdb/version1.py index 81f21c163..3afce4383 100644 --- a/modules/influxdb/testcontainers/influxdb1/__init__.py +++ b/src/testcontainers/community/influxdb/version1.py @@ -14,8 +14,9 @@ from typing import Optional from influxdb import InfluxDBClient +from typing_extensions import Self -from testcontainers.influxdb import InfluxDbContainer +from .base import InfluxDbContainer class InfluxDb1Container(InfluxDbContainer): @@ -27,7 +28,7 @@ class InfluxDb1Container(InfluxDbContainer): .. doctest:: - >>> from testcontainers.influxdb1 import InfluxDbContainer + >>> from testcontainers.community.influxdb1 import InfluxDbContainer >>> with InfluxDbContainer() as influxdb: ... version = influxdb.get_version() @@ -41,7 +42,7 @@ def __init__( # specifies the port on the host machine where influxdb is exposed; a random available port otherwise host_port: Optional[int] = None, **docker_client_kw, - ): + ) -> None: super().__init__(image, container_port, host_port, **docker_client_kw) def get_client(self, **client_kwargs): @@ -58,7 +59,7 @@ def get_client(self, **client_kwargs): return InfluxDBClient(self.get_container_host_ip(), self.get_exposed_port(self.container_port), **client_kwargs) - def start(self) -> "InfluxDb1Container": + def start(self) -> Self: """ Overridden for better typing reason """ diff --git a/modules/influxdb/testcontainers/influxdb2/__init__.py b/src/testcontainers/community/influxdb/version2.py similarity index 95% rename from modules/influxdb/testcontainers/influxdb2/__init__.py rename to src/testcontainers/community/influxdb/version2.py index bd33d7fc4..c2e57024a 100644 --- a/modules/influxdb/testcontainers/influxdb2/__init__.py +++ b/src/testcontainers/community/influxdb/version2.py @@ -15,8 +15,9 @@ from typing import Optional from influxdb_client import InfluxDBClient, Organization +from typing_extensions import Self -from testcontainers.influxdb import InfluxDbContainer +from .base import InfluxDbContainer class InfluxDb2Container(InfluxDbContainer): @@ -28,7 +29,7 @@ class InfluxDb2Container(InfluxDbContainer): .. doctest:: - >>> from testcontainers.influxdb2 import InfluxDb2Container + >>> from testcontainers.community.influxdb2 import InfluxDb2Container >>> with InfluxDb2Container() as influxdb2: ... version = influxdb2.get_version() @@ -53,7 +54,7 @@ def __init__( bucket: Optional[str] = None, retention: Optional[str] = None, **docker_client_kw, - ): + ) -> None: super().__init__(image, container_port, host_port, **docker_client_kw) configuration = { @@ -70,7 +71,7 @@ def __init__( if env_value: self.with_env(env_key, env_value) - def start(self) -> "InfluxDb2Container": + def start(self) -> Self: """ Overridden for better typing reason """ diff --git a/modules/k3s/testcontainers/k3s/__init__.py b/src/testcontainers/community/k3s/__init__.py similarity index 86% rename from modules/k3s/testcontainers/k3s/__init__.py rename to src/testcontainers/community/k3s/__init__.py index 6e5354175..add986457 100644 --- a/modules/k3s/testcontainers/k3s/__init__.py +++ b/src/testcontainers/community/k3s/__init__.py @@ -13,6 +13,8 @@ import logging +from typing_extensions import Self + from testcontainers.core.config import testcontainers_config from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -27,7 +29,7 @@ class K3SContainer(DockerContainer): .. doctest:: >>> import yaml - >>> from testcontainers.k3s import K3SContainer + >>> from testcontainers.community.k3s import K3SContainer >>> from kubernetes import client, config >>> with K3SContainer() as k3s: @@ -39,12 +41,14 @@ class K3SContainer(DockerContainer): KUBE_SECURE_PORT = 6443 RANCHER_WEBHOOK_PORT = 8443 - def __init__(self, image="rancher/k3s:latest", enable_cgroup_mount=True, **kwargs) -> None: + def __init__(self, image="rancher/k3s:latest", enable_cgroup_mount=True, **kwargs: object) -> None: super().__init__(image, **kwargs) self.with_exposed_ports(self.KUBE_SECURE_PORT, self.RANCHER_WEBHOOK_PORT) self.with_env("K3S_URL", f"https://{self.get_container_host_ip()}:{self.KUBE_SECURE_PORT}") self.with_command("server --disable traefik --tls-san=" + self.get_container_host_ip()) - self.with_kwargs(privileged=True, tmpfs={"/run": "", "/var/run": ""}) + self.with_kwargs(privileged=True) + self.with_tmpfs_mount("/run") + self.with_tmpfs_mount("/var/run") if enable_cgroup_mount: self.with_volume_mapping("/sys/fs/cgroup", "/sys/fs/cgroup", "rw") else: @@ -53,7 +57,7 @@ def __init__(self, image="rancher/k3s:latest", enable_cgroup_mount=True, **kwarg def _connect(self) -> None: wait_for_logs(self, predicate="Node controller sync successful", timeout=testcontainers_config.timeout) - def start(self) -> "K3SContainer": + def start(self) -> Self: super().start() self._connect() return self @@ -65,6 +69,6 @@ def config_yaml(self) -> str: execution = self.get_wrapped_container().exec_run(["cat", "/etc/rancher/k3s/k3s.yaml"]) config_yaml = execution.output.decode("utf-8").replace( f"https://127.0.0.1:{self.KUBE_SECURE_PORT}", - f"https://{self.get_container_host_ip()}:" f"{self.get_exposed_port(self.KUBE_SECURE_PORT)}", + f"https://{self.get_container_host_ip()}:{self.get_exposed_port(self.KUBE_SECURE_PORT)}", ) return config_yaml diff --git a/modules/kafka/testcontainers/kafka/__init__.py b/src/testcontainers/community/kafka/__init__.py similarity index 81% rename from modules/kafka/testcontainers/kafka/__init__.py rename to src/testcontainers/community/kafka/__init__.py index ccd7f5b77..62ed70364 100644 --- a/modules/kafka/testcontainers/kafka/__init__.py +++ b/src/testcontainers/community/kafka/__init__.py @@ -1,3 +1,4 @@ +import re import tarfile import time from dataclasses import dataclass, field @@ -7,11 +8,11 @@ from typing_extensions import Self +from testcontainers.community.kafka._redpanda import RedpandaContainer from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.version import ComparableVersion -from testcontainers.core.waiting_utils import wait_for_logs -from testcontainers.kafka._redpanda import RedpandaContainer +from testcontainers.core.wait_strategies import LogMessageWaitStrategy __all__ = [ "KafkaContainer", @@ -41,7 +42,7 @@ class KafkaContainer(DockerContainer): .. doctest:: - >>> from testcontainers.kafka import KafkaContainer + >>> from testcontainers.community.kafka import KafkaContainer >>> with KafkaContainer() as kafka: ... connection = kafka.get_bootstrap_server() @@ -54,16 +55,26 @@ class KafkaContainer(DockerContainer): TC_START_SCRIPT = "/tc-start.sh" MIN_KRAFT_TAG = "7.0.0" - def __init__(self, image: str = "confluentinc/cp-kafka:7.6.0", port: int = 9093, **kwargs) -> None: + def __init__( + self, + image: str = "confluentinc/cp-kafka:7.6.0", + port: int = 9093, + wait_strategy_check_string: str = r".*\[KafkaServer id=\d+\] started.*", + listener_name: str = "PLAINTEXT", + security_protocol: str = "PLAINTEXT", + **kwargs, + ) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image, **kwargs) self.port = port + self.listener_name = listener_name + self.security_protocol = security_protocol self.kraft_enabled = False - self.wait_for = r".*\[KafkaServer id=\d+\] started.*" + self.wait_for: re.Pattern[str] = re.compile(wait_strategy_check_string) self.boot_command = "" self.cluster_id = "MkU3OEVBNTcwNTJENDM2Qk" - self.listeners = f"PLAINTEXT://0.0.0.0:{self.port},BROKER://0.0.0.0:9092" - self.security_protocol_map = "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT" + self.listeners = f"{listener_name}://0.0.0.0:{self.port},BROKER://0.0.0.0:9092" + self.security_protocol_map = f"BROKER:PLAINTEXT,{listener_name}:{security_protocol}" self.with_exposed_ports(self.port) self.with_env("KAFKA_LISTENERS", self.listeners) @@ -102,7 +113,7 @@ def configure(self): self._configure_zookeeper() def _configure_kraft(self) -> None: - self.wait_for = r".*Kafka Server started.*" + self.wait_for = re.compile(r".*Kafka Server started.*") self.with_env("CLUSTER_ID", self.cluster_id) self.with_env("KAFKA_NODE_ID", 1) @@ -153,9 +164,9 @@ def tc_start(self) -> None: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) if kafka_config.limit_broker_to_first_host: - listeners = f"PLAINTEXT://{host}:{port},BROKER://$(hostname -i | cut -d' ' -f1):9092" + listeners = f"{self.listener_name}://{host}:{port},BROKER://$(hostname -i | cut -d' ' -f1):9092" else: - listeners = f"PLAINTEXT://{host}:{port},BROKER://$(hostname -i):9092" + listeners = f"{self.listener_name}://{host}:{port},BROKER://$(hostname -i):9092" data = ( dedent( f""" @@ -172,14 +183,16 @@ def tc_start(self) -> None: ) self.create_file(data, KafkaContainer.TC_START_SCRIPT) - def start(self, timeout=30) -> "KafkaContainer": + def start(self, timeout: int = 30) -> Self: script = KafkaContainer.TC_START_SCRIPT command = f'sh -c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' self.configure() self.with_command(command) super().start() self.tc_start() - wait_for_logs(self, self.wait_for, timeout=timeout) + wait_strategy = LogMessageWaitStrategy(self.wait_for) + wait_strategy.with_startup_timeout(timeout) + wait_strategy.wait_until_ready(self) return self def create_file(self, content: bytes, path: str) -> None: diff --git a/modules/kafka/testcontainers/kafka/_redpanda.py b/src/testcontainers/community/kafka/_redpanda.py similarity index 76% rename from modules/kafka/testcontainers/kafka/_redpanda.py rename to src/testcontainers/community/kafka/_redpanda.py index 90d02c4cb..b737bcf31 100644 --- a/modules/kafka/testcontainers/kafka/_redpanda.py +++ b/src/testcontainers/community/kafka/_redpanda.py @@ -1,10 +1,14 @@ +import os.path +import re import tarfile import time from io import BytesIO from textwrap import dedent +from typing_extensions import Self + from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.wait_strategies import LogMessageWaitStrategy class RedpandaContainer(DockerContainer): @@ -15,13 +19,13 @@ class RedpandaContainer(DockerContainer): .. doctest:: - >>> from testcontainers.kafka import RedpandaContainer + >>> from testcontainers.community.kafka import RedpandaContainer >>> with RedpandaContainer() as redpanda: ... connection = redpanda.get_bootstrap_server() """ - TC_START_SCRIPT = "/tc-start.sh" + TC_START_SCRIPT = "/var/lib/redpanda/tc-start.sh" def __init__( self, @@ -33,6 +37,7 @@ def __init__( self.redpanda_port = 9092 self.schema_registry_port = 8081 self.with_exposed_ports(self.redpanda_port, self.schema_registry_port) + self.wait_for: re.Pattern[str] = re.compile(r".*Started Kafka API server.*") def get_bootstrap_server(self) -> str: host = self.get_container_host_ip() @@ -63,20 +68,23 @@ def tc_start(self) -> None: self.create_file(data, RedpandaContainer.TC_START_SCRIPT) - def start(self, timeout=10) -> "RedpandaContainer": + def start(self, timeout=10) -> Self: script = RedpandaContainer.TC_START_SCRIPT command = f'-c "while [ ! -f {script} ]; do sleep 0.1; done; sh {script}"' self.with_command(command) super().start() self.tc_start() - wait_for_logs(self, r".*Started Kafka API server.*", timeout=timeout) + wait_strategy = LogMessageWaitStrategy(self.wait_for) + wait_strategy.with_startup_timeout(timeout) + wait_strategy.wait_until_ready(self) return self def create_file(self, content: bytes, path: str) -> None: with BytesIO() as archive, tarfile.TarFile(fileobj=archive, mode="w") as tar: - tarinfo = tarfile.TarInfo(name=path) + dirname, basename = os.path.split(path) + tarinfo = tarfile.TarInfo(name=basename) tarinfo.size = len(content) tarinfo.mtime = time.time() tar.addfile(tarinfo, BytesIO(content)) archive.seek(0) - self.get_wrapped_container().put_archive("/", archive) + self.get_wrapped_container().put_archive(dirname, archive) diff --git a/modules/keycloak/testcontainers/keycloak/__init__.py b/src/testcontainers/community/keycloak/__init__.py similarity index 68% rename from modules/keycloak/testcontainers/keycloak/__init__.py rename to src/testcontainers/community/keycloak/__init__.py index e7a065211..7569ef8c0 100644 --- a/modules/keycloak/testcontainers/keycloak/__init__.py +++ b/src/testcontainers/community/keycloak/__init__.py @@ -14,12 +14,17 @@ from typing import Optional import requests - from keycloak import KeycloakAdmin +from typing_extensions import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs _DEFAULT_DEV_COMMAND = "start-dev" +# Since Keycloak v26.0.0 +# See: https://www.keycloak.org/server/all-config#category-bootstrap_admin +ADMIN_USERNAME_ENVIRONMENT_VARIABLE = "KC_BOOTSTRAP_ADMIN_USERNAME" +ADMIN_PASSWORD_ENVIRONMENT_VARIABLE = "KC_BOOTSTRAP_ADMIN_PASSWORD" class KeycloakContainer(DockerContainer): @@ -32,7 +37,7 @@ class KeycloakContainer(DockerContainer): .. doctest:: - >>> from testcontainers.keycloak import KeycloakContainer + >>> from testcontainers.community.keycloak import KeycloakContainer >>> with KeycloakContainer(f"quay.io/keycloak/keycloak:25.0.4") as keycloak: ... keycloak.get_client().users_count() @@ -57,6 +62,9 @@ def __init__( self.cmd = cmd def _configure(self) -> None: + self.with_env(ADMIN_USERNAME_ENVIRONMENT_VARIABLE, self.username) + self.with_env(ADMIN_PASSWORD_ENVIRONMENT_VARIABLE, self.password) + # legacy env vars (<= 26.0.0) self.with_env("KEYCLOAK_ADMIN", self.username) self.with_env("KEYCLOAK_ADMIN_PASSWORD", self.password) # Enable health checks @@ -71,12 +79,22 @@ def _configure(self) -> None: def get_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.port) - return f"http://{host}:{port}" + + if "KC_HTTP_RELATIVE_PATH" in self.env: + path = self.env.get("KC_HTTP_RELATIVE_PATH", "").strip("/") + return f"http://{host}:{port}/{path}/" + else: + return f"http://{host}:{port}" def get_management_url(self) -> str: host = self.get_container_host_ip() port = self.get_exposed_port(self.management_port) - return f"http://{host}:{port}" + + if "KC_HTTP_MANAGEMENT_RELATIVE_PATH" in self.env: + path = self.env.get("KC_HTTP_MANAGEMENT_RELATIVE_PATH", "").strip("/") + return f"http://{host}:{port}/{path}/" + else: + return f"http://{host}:{port}" @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) def _readiness_probe(self) -> None: @@ -89,14 +107,38 @@ def _readiness_probe(self) -> None: response = requests.get(f"{self.get_url()}/health/ready", timeout=1) response.raise_for_status() if _DEFAULT_DEV_COMMAND in self._command: - wait_for_logs(self, "Added user .* to realm .*") + wait_for_logs(self, "started in \\d+\\.\\d+s") + wait_for_logs(self, "Created temporary admin user|Added user '") - def start(self) -> "KeycloakContainer": + def start(self) -> Self: super().start() self._readiness_probe() + self._disable_ssl_required() return self - def with_realm_import_file(self, realm_import_file: str) -> "KeycloakContainer": + def _disable_ssl_required(self) -> None: + result = self.get_wrapped_container().exec_run( + [ + "/opt/keycloak/bin/kcadm.sh", + "update", + "realms/master", + "-s", + "sslRequired=none", + "--no-config", + "--server", + f"http://localhost:{self.port}", + "--realm", + "master", + "--user", + self.username, + "--password", + self.password, + ] + ) + if result.exit_code != 0: + raise RuntimeError(f"Failed to disable SSL for master realm: {result.output.decode()}") + + def with_realm_import_file(self, realm_import_file: str) -> Self: file = os.path.abspath(realm_import_file) if not os.path.exists(file): raise FileNotFoundError(f"Realm file {file} does not exist") @@ -104,7 +146,7 @@ def with_realm_import_file(self, realm_import_file: str) -> "KeycloakContainer": self.has_realm_imports = True return self - def with_realm_import_folder(self, realm_import_folder: str) -> "KeycloakContainer": + def with_realm_import_folder(self, realm_import_folder: str) -> Self: folder = os.path.abspath(realm_import_folder) if not os.path.exists(folder): raise FileNotFoundError(f"Realm folder {folder} does not exist") @@ -112,12 +154,13 @@ def with_realm_import_folder(self, realm_import_folder: str) -> "KeycloakContain self.has_realm_imports = True return self - def get_client(self, **kwargs) -> KeycloakAdmin: + def get_client(self, **kwargs: object) -> KeycloakAdmin: default_kwargs = { "server_url": self.get_url(), "username": self.username, "password": self.password, "realm_name": "master", + "user_realm_name": "master", "verify": True, } kwargs = {**default_kwargs, **kwargs} diff --git a/modules/localstack/testcontainers/localstack/__init__.py b/src/testcontainers/community/localstack/__init__.py similarity index 91% rename from modules/localstack/testcontainers/localstack/__init__.py rename to src/testcontainers/community/localstack/__init__.py index 15cabeab6..f6cbef66a 100644 --- a/modules/localstack/testcontainers/localstack/__init__.py +++ b/src/testcontainers/community/localstack/__init__.py @@ -15,6 +15,7 @@ from typing import Any, Optional import boto3 +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -28,7 +29,7 @@ class LocalStackContainer(DockerContainer): .. doctest:: - >>> from testcontainers.localstack import LocalStackContainer + >>> from testcontainers.community.localstack import LocalStackContainer >>> with LocalStackContainer(image="localstack/localstack:2.0.1") as localstack: ... dynamo_client = localstack.get_client("dynamodb") @@ -52,7 +53,7 @@ def __init__( self.with_env("AWS_ACCESS_KEY_ID", "testcontainers-localstack") self.with_env("AWS_SECRET_ACCESS_KEY", "testcontainers-localstack") - def with_services(self, *services) -> "LocalStackContainer": + def with_services(self, *services) -> Self: """ Restrict what services to run. By default all localstack services are launched. @@ -75,7 +76,7 @@ def get_url(self) -> str: return f"http://{host}:{port}" @ft.wraps(boto3.client) - def get_client(self, name, **kwargs) -> Any: + def get_client(self, name, **kwargs: object) -> Any: kwargs_ = { "endpoint_url": self.get_url(), "region_name": self.region_name, @@ -85,7 +86,7 @@ def get_client(self, name, **kwargs) -> Any: kwargs_.update(kwargs) return boto3.client(name, **kwargs_) - def start(self, timeout: float = 60) -> "LocalStackContainer": + def start(self, timeout: float = 60) -> Self: super().start() wait_for_logs(self, r"Ready\.\n", timeout=timeout) return self diff --git a/modules/mailpit/testcontainers/mailpit/__init__.py b/src/testcontainers/community/mailpit/__init__.py similarity index 97% rename from modules/mailpit/testcontainers/mailpit/__init__.py rename to src/testcontainers/community/mailpit/__init__.py index 466755949..26b802fad 100644 --- a/modules/mailpit/testcontainers/mailpit/__init__.py +++ b/src/testcontainers/community/mailpit/__init__.py @@ -44,7 +44,7 @@ class MailpitUser(NamedTuple): .. doctest:: - >>> from testcontainers.mailpit import MailpitUser + >>> from testcontainers.community.mailpit import MailpitUser >>> users = [ ... MailpitUser("jane", "secret"), @@ -86,7 +86,7 @@ class MailpitContainer(DockerContainer): >>> import smtplib - >>> from testcontainers.mailpit import MailpitContainer + >>> from testcontainers.community.mailpit import MailpitContainer >>> with MailpitContainer() as mailpit_container: ... host_ip = mailpit_container.get_container_host_ip() @@ -105,7 +105,7 @@ class MailpitContainer(DockerContainer): >>> import smtplib - >>> from testcontainers.mailpit import MailpitContainer, MailpitUser + >>> from testcontainers.community.mailpit import MailpitContainer, MailpitUser >>> users = [MailpitUser("jane", "secret"), MailpitUser("ron", "pass2")] diff --git a/modules/memcached/testcontainers/memcached/__init__.py b/src/testcontainers/community/memcached/__init__.py similarity index 91% rename from modules/memcached/testcontainers/memcached/__init__.py rename to src/testcontainers/community/memcached/__init__.py index 6da409e06..922003f1e 100644 --- a/modules/memcached/testcontainers/memcached/__init__.py +++ b/src/testcontainers/community/memcached/__init__.py @@ -12,6 +12,8 @@ # under the License. import socket +from typing_extensions import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -28,13 +30,13 @@ class MemcachedContainer(DockerContainer): .. doctest:: - >>> from testcontainers.memcached import MemcachedContainer + >>> from testcontainers.community.memcached import MemcachedContainer >>> with MemcachedContainer() as memcached_container: ... host, port = memcached_container.get_host_and_port() """ - def __init__(self, image="memcached:1", port_to_expose=11211, **kwargs): + def __init__(self, image="memcached:1", port_to_expose=11211, **kwargs: object) -> None: super().__init__(image, **kwargs) self.port_to_expose = port_to_expose self.with_exposed_ports(port_to_expose) @@ -50,7 +52,7 @@ def _connect(self): if len(data) == 0: raise MemcachedNotReady("Memcached not ready yet") - def start(self): + def start(self) -> Self: super().start() self._connect() return self diff --git a/src/testcontainers/community/milvus/__init__.py b/src/testcontainers/community/milvus/__init__.py new file mode 100644 index 000000000..62352356d --- /dev/null +++ b/src/testcontainers/community/milvus/__init__.py @@ -0,0 +1,62 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from testcontainers.core.generic import DockerContainer +from testcontainers.core.wait_strategies import CompositeWaitStrategy, HttpWaitStrategy, LogMessageWaitStrategy + + +class MilvusContainer(DockerContainer): + """ + Milvus database container. + + Read mode about Milvus: https://milvus.io/docs + + Example: + + The example spins up a Milvus database and connects to it client using MilvisClient. + + .. doctest:: + + >>> from testcontainers.community.milvus import MilvusContainer + >>> with MilvusContainer("milvusdb/milvus:v2.4.4") as milvus_container: + ... str(milvus_container.get_exposed_port(milvus_container.port)) in milvus_container.get_connection_url() + True + """ + + def __init__( + self, + image: str = "milvusdb/milvus:latest", + port: int = 19530, + **kwargs, + ) -> None: + super().__init__(image=image, **kwargs) + self.port = port + self.healthcheck_port = 9091 + self.with_exposed_ports(self.port, self.healthcheck_port) + self.with_command("milvus run standalone") + + self.with_env("ETCD_USE_EMBED", "true") + self.with_env("ETCD_DATA_DIR", "/var/lib/milvus/etcd") + self.with_env("COMMON_STORAGETYPE", "local") + + self.waiting_for( + CompositeWaitStrategy( + LogMessageWaitStrategy("Welcome to use Milvus!"), + HttpWaitStrategy(self.healthcheck_port, "/healthz"), + ) + ) + + def get_connection_url(self) -> str: + ip = self.get_container_host_ip() + port = self.get_exposed_port(self.port) + return f"http://{ip}:{port}" diff --git a/modules/minio/testcontainers/minio/__init__.py b/src/testcontainers/community/minio/__init__.py similarity index 75% rename from modules/minio/testcontainers/minio/__init__.py rename to src/testcontainers/community/minio/__init__.py index 51a7094e3..5310e3cd6 100644 --- a/modules/minio/testcontainers/minio/__init__.py +++ b/src/testcontainers/community/minio/__init__.py @@ -1,14 +1,8 @@ -from typing import TYPE_CHECKING - -from requests import ConnectionError, get - from minio import Minio + from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready - -if TYPE_CHECKING: - from requests import Response +from testcontainers.core.wait_strategies import HttpWaitStrategy class MinioContainer(DockerContainer): @@ -25,7 +19,7 @@ class MinioContainer(DockerContainer): .. doctest:: >>> import io - >>> from testcontainers.minio import MinioContainer + >>> from testcontainers.community.minio import MinioContainer >>> with MinioContainer() as minio: ... client = minio.get_client() @@ -56,7 +50,11 @@ def __init__( secret_key: Secret key for client connections. """ raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super().__init__(image, **kwargs) + super().__init__( + image, + _wait_strategy=HttpWaitStrategy(port, "/minio/health/live"), + **kwargs, + ) self.port = port self.access_key = access_key self.secret_key = secret_key @@ -66,7 +64,7 @@ def __init__( self.with_env("MINIO_SECRET_KEY", self.secret_key) self.with_command(f"server /data --address :{self.port}") - def get_client(self, **kwargs) -> Minio: + def get_client(self, **kwargs: object) -> Minio: """Returns a Minio client to connect to the container. Returns: @@ -76,7 +74,7 @@ def get_client(self, **kwargs) -> Minio: host_ip = self.get_container_host_ip() exposed_port = self.get_exposed_port(self.port) return Minio( - f"{host_ip}:{exposed_port}", + endpoint=f"{host_ip}:{exposed_port}", access_key=self.access_key, secret_key=self.secret_key, secure=False, @@ -97,18 +95,3 @@ def get_config(self) -> dict: "access_key": self.access_key, "secret_key": self.secret_key, } - - @wait_container_is_ready(ConnectionError) - def _healthcheck(self) -> None: - """This is an internal method used to check if the Minio container - is healthy and ready to receive requests.""" - url = f"http://{self.get_config()['endpoint']}/minio/health/live" - response: Response = get(url) - response.raise_for_status() - - def start(self) -> "MinioContainer": - """This method starts the Minio container and runs the healthcheck - to verify that the container is ready to use.""" - super().start() - self._healthcheck() - return self diff --git a/src/testcontainers/community/mongodb/__init__.py b/src/testcontainers/community/mongodb/__init__.py new file mode 100644 index 000000000..2ede8e8f8 --- /dev/null +++ b/src/testcontainers/community/mongodb/__init__.py @@ -0,0 +1,400 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import base64 +import os +import re +import shlex +import time +from typing import Any, Optional +from urllib.parse import urlencode + +from pymongo import MongoClient +from pymongo.errors import OperationFailure, PyMongoError +from typing_extensions import Self + +from testcontainers.core.config import testcontainers_config +from testcontainers.core.exceptions import ContainerStartException +from testcontainers.core.generic import DbContainer +from testcontainers.core.utils import raise_for_deprecated_parameter +from testcontainers.core.wait_strategies import HealthcheckWaitStrategy, LogMessageWaitStrategy + +_REPLICA_SET_KEYFILE_PATH = "/tmp/testcontainers-mongodb-keyfile" +_REPLICA_SET_ENTRYPOINT_PATH = "/tmp/testcontainers-mongodb-entrypoint.sh" +_REPLICA_SET_ENTRYPOINT = f"""#!/bin/bash +set -Eeuo pipefail +chown mongodb:mongodb {_REPLICA_SET_KEYFILE_PATH} +chmod 400 {_REPLICA_SET_KEYFILE_PATH} +exec /usr/local/bin/docker-entrypoint.sh "$@" +""".encode() + + +def _is_root_container_user(user: Any) -> bool: + if user in (None, "", 0, "0", "root"): + return True + return isinstance(user, str) and user.partition(":")[0] in ("0", "root") + + +class MongoDbContainer(DbContainer): + """ + Mongo document-based database container. + + Example: + + .. doctest:: + + >>> from testcontainers.community.mongodb import MongoDbContainer + + >>> with MongoDbContainer("mongo:7.0.7") as mongo: + ... db = mongo.get_connection_client().test + ... # Insert a database entry + ... result = db.restaurants.insert_one( + ... { + ... "name": "Vella", + ... "cuisine": "Italian", + ... "restaurant_id": "123456" + ... } + ... ) + ... # Find the restaurant document + ... result = db.restaurants.find_one({"name": "Vella"}) + ... result["restaurant_id"] + '123456' + """ + + def __init__( + self, + image: str = "mongo:latest", + port: int = 27017, + username: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + **kwargs, + ) -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") + super().__init__( + image=image, + _wait_strategy=LogMessageWaitStrategy(re.compile(r"waiting for connections", re.IGNORECASE)), + **kwargs, + ) + self.username = username if username else os.environ.get("MONGO_INITDB_ROOT_USERNAME", "test") + self.password = password if password else os.environ.get("MONGO_INITDB_ROOT_PASSWORD", "test") + self.dbname = dbname if dbname else os.environ.get("MONGO_DB", "test") + self.port = port + self.with_exposed_ports(self.port) + + def _configure(self) -> None: + self.with_env("MONGO_INITDB_ROOT_USERNAME", self.username) + self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.password) + self.with_env("MONGO_DB", self.dbname) + + def get_connection_url(self) -> str: + return self._create_connection_url( + dialect="mongodb", + username=self.username, + password=self.password, + port=self.port, + ) + + def _connect(self) -> None: + # LogMessageWaitStrategy handles waiting for container readiness + pass + + def get_connection_client(self) -> MongoClient: + return MongoClient(self.get_connection_url()) + + +class MongoDbReplicaSetContainer(MongoDbContainer): + """MongoDB container configured as a single-node replica set. + + Authentication is enabled by default and uses an ephemeral keyfile for internal + replica-set authentication. Set ``auth_enabled=False`` to run without + authentication or a keyfile. + + Automatic keyfile setup targets the official ``mongo`` image and requires the + container to start as root before the image drops privileges to ``mongodb``. + + Example: + + .. code-block:: python + + from testcontainers.community.mongodb import MongoDbReplicaSetContainer + + with MongoDbReplicaSetContainer("mongo:7.0.7") as mongo: + client = mongo.get_connection_client() + with client.start_session() as session, session.start_transaction(): + client.test.items.insert_one({"name": "example"}, session=session) + + with MongoDbReplicaSetContainer( + "mongo:7.0.7", + auth_enabled=False, + ) as mongo: + client = mongo.get_connection_client() + """ + + def __init__( + self, + image: str = "mongo:latest", + port: int = 27017, + username: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + replica_set: str = "docker-rs", + auth_enabled: bool = True, + **kwargs: Any, + ) -> None: + if not replica_set: + raise ValueError("replica_set must not be empty") + if not auth_enabled and (username is not None or password is not None): + raise ValueError("username and password cannot be set when authentication is disabled") + if auth_enabled: + if "entrypoint" in kwargs: + raise ValueError("entrypoint cannot be overridden for an authenticated replica set") + if "user" in kwargs and not _is_root_container_user(kwargs["user"]): + raise ValueError("authenticated replica sets must start as the root container user") + kwargs["entrypoint"] = _REPLICA_SET_ENTRYPOINT_PATH + + super().__init__( + image=image, + port=port, + username=username, + password=password, + dbname=dbname, + **kwargs, + ) + self.auth_enabled = auth_enabled + self.replica_set = replica_set + if not auth_enabled: + self.username = "" + self.password = "" + else: + super().with_copy_into_container(_REPLICA_SET_ENTRYPOINT, _REPLICA_SET_ENTRYPOINT_PATH, mode=0o755) + keyfile = base64.b64encode(os.urandom(756)) + super().with_copy_into_container(keyfile, _REPLICA_SET_KEYFILE_PATH, mode=0o400) + command = MongoDbReplicaSetContainer._replica_set_command(self, self._command) + super().with_command(command) + + def _replica_set_command(self, command: Optional[str | list[str]]) -> list[str]: + command_parts = shlex.split(command) if isinstance(command, str) else list(command or []) + if command_parts and not (command_parts[0].startswith("-") or os.path.basename(command_parts[0]) == "mongod"): + raise ValueError("replica set commands must contain mongod options or start with mongod") + if any( + part in ("--replSet", "--keyFile") or part.startswith(("--replSet=", "--keyFile=")) + for part in command_parts + ): + raise ValueError("replica set and keyfile options are managed by MongoDbReplicaSetContainer") + + command_parts.extend(["--replSet", self.replica_set]) + if self.auth_enabled: + command_parts.extend(["--keyFile", _REPLICA_SET_KEYFILE_PATH]) + return command_parts + + def with_command(self, command: str | list[str]) -> Self: + return super().with_command(self._replica_set_command(command)) + + def with_kwargs(self, **kwargs: Any) -> Self: + if self.auth_enabled: + if "entrypoint" in kwargs: + raise ValueError("entrypoint cannot be overridden for an authenticated replica set") + if "user" in kwargs and not _is_root_container_user(kwargs["user"]): + raise ValueError("authenticated replica sets must start as the root container user") + kwargs["entrypoint"] = _REPLICA_SET_ENTRYPOINT_PATH + return super().with_kwargs(**kwargs) + + def _configure(self) -> None: + if self.auth_enabled: + assert self.username is not None + assert self.password is not None + self.with_env("MONGO_INITDB_ROOT_USERNAME", self.username) + self.with_env("MONGO_INITDB_ROOT_PASSWORD", self.password) + else: + self.env.pop("MONGO_INITDB_ROOT_USERNAME", None) + self.env.pop("MONGO_INITDB_ROOT_PASSWORD", None) + self.with_env("MONGO_DB", self.dbname) + + def get_connection_url(self) -> str: + if self.auth_enabled: + url = super().get_connection_url() + else: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.port) + url = f"mongodb://{host}:{port}" + + return f"{url}/?{urlencode({'replicaSet': self.replica_set, 'directConnection': 'true'})}" + + def _connect(self) -> None: + direct_url = self.get_connection_url().replace( + urlencode({"replicaSet": self.replica_set, "directConnection": "true"}), + urlencode({"directConnection": "true"}), + ) + client: MongoClient[dict[str, Any]] = MongoClient( + direct_url, + serverSelectionTimeoutMS=1000, + connectTimeoutMS=1000, + socketTimeoutMS=1000, + ) + deadline = time.monotonic() + testcontainers_config.timeout + + try: + self._wait_for_mongodb(client, deadline) + self._wait_for_replica_set_primary(client, deadline) + finally: + client.close() + + def _wait_for_mongodb(self, client: MongoClient[dict[str, Any]], deadline: float) -> None: + last_error: Optional[Exception] = None + while time.monotonic() < deadline: + self._raise_if_replica_set_container_stopped() + try: + client.admin.command("ping") + return + except PyMongoError as error: + last_error = error + time.sleep(testcontainers_config.sleep_time) + + raise ContainerStartException("MongoDB did not become ready") from last_error + + def _wait_for_replica_set_primary(self, client: MongoClient[dict[str, Any]], deadline: float) -> None: + last_error: Optional[Exception] = None + while time.monotonic() < deadline: + self._raise_if_replica_set_container_stopped() + try: + self._initiate_replica_set_if_needed(client) + if client.admin.command("hello").get("isWritablePrimary"): + return + except OperationFailure: + raise + except PyMongoError as error: + last_error = error + time.sleep(testcontainers_config.sleep_time) + + raise ContainerStartException("MongoDB replica set did not elect a primary") from last_error + + def _initiate_replica_set_if_needed(self, client: MongoClient[dict[str, Any]]) -> None: + try: + client.admin.command("replSetGetStatus") + return + except OperationFailure as error: + if error.code != 94: # NotYetInitialized + raise + + try: + client.admin.command( + { + "replSetInitiate": { + "_id": self.replica_set, + "members": [{"_id": 0, "host": f"localhost:{self.port}"}], + } + } + ) + except OperationFailure as error: + if error.code != 23: # AlreadyInitialized + raise + + def _raise_if_replica_set_container_stopped(self) -> None: + self.reload() + if self.status not in ("exited", "dead"): + return + + stdout, stderr = self.get_logs() + logs = (stdout + stderr).decode(errors="replace") + raise ContainerStartException(f"MongoDB stopped while initializing its replica set:\n{logs}") + + +class MongoDBAtlasLocalContainer(DbContainer): + """ + MongoDB Atlas Local document-based database container. + + This is the local version of the Mongo Atlas service. + It includes Mongo DB and Mongo Atlas Search services + Example: + + .. doctest:: + + >>> from testcontainers.community.mongodb import MongoDBAtlasLocalContainer + >>> import time + >>> with MongoDBAtlasLocalContainer("mongodb/mongodb-atlas-local:8.0.13") as mongo: + ... db = mongo.get_connection_client().test + ... # Insert a database entry + ... result = db.restaurants.insert_one( + ... { + ... "name": "Vella", + ... "cuisine": "Italian", + ... "restaurant_id": "123456" + ... } + ... ) + ... # add an index + ... _ = db.restaurants.create_search_index( + ... { + ... "definition": { + ... "mappings": { + ... "dynamic": True + ... } + ... }, + ... "name": "default" + ... } + ... ) + ... # wait for the index to be created + ... time.sleep(1) + ... + ... # Find the restaurant document + ... result = db.restaurants.aggregate([{ + ... "$search": { + ... "index": "default", + ... "text": { + ... "query": "Vella", + ... "path": "name" + ... } + ... } + ... }]).next() + ... result["restaurant_id"] + '123456' + """ + + def __init__( + self, + image: str = "mongodb/mongodb-atlas-local:latest", + port: int = 27017, + username: Optional[str] = None, + password: Optional[str] = None, + dbname: Optional[str] = None, + **kwargs, + ) -> None: + raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") + super().__init__(image=image, **kwargs) + self.username = username if username else os.environ.get("MONGODB_INITDB_ROOT_USERNAME", "test") + self.password = password if password else os.environ.get("MONGODB_INITDB_ROOT_PASSWORD", "test") + self.dbname = dbname if dbname else os.environ.get("MONGODB_INITDB_DATABASE", "test") + self.port = port + self.with_exposed_ports(self.port) + + def _configure(self) -> None: + self.with_env("MONGODB_INITDB_ROOT_USERNAME", self.username) + self.with_env("MONGODB_INITDB_ROOT_PASSWORD", self.password) + self.with_env("MONGODB_INITDB_DATABASE", self.dbname) + + def get_connection_url(self) -> str: + return ( + self._create_connection_url( + dialect="mongodb", + username=self.username, + password=self.password, + port=self.port, + ) + + "?directConnection=true" + ) + + def _connect(self) -> None: + strategy = HealthcheckWaitStrategy() + strategy.wait_until_ready(self) + + def get_connection_client(self) -> MongoClient: + return MongoClient(self.get_connection_url()) diff --git a/modules/mqtt/testcontainers/mqtt/__init__.py b/src/testcontainers/community/mqtt/__init__.py similarity index 92% rename from modules/mqtt/testcontainers/mqtt/__init__.py rename to src/testcontainers/community/mqtt/__init__.py index 1382762ae..fece15bc2 100644 --- a/modules/mqtt/testcontainers/mqtt/__init__.py +++ b/src/testcontainers/community/mqtt/__init__.py @@ -31,7 +31,7 @@ class MosquittoContainer(DockerContainer): .. doctest:: - >>> from testcontainers.mqtt import MosquittoContainer + >>> from testcontainers.community.mqtt import MosquittoContainer >>> with MosquittoContainer() as mosquitto_broker: ... mqtt_client = mosquitto_broker.get_client() @@ -50,7 +50,7 @@ def __init__( super().__init__(image, **kwargs) # self.password = password # reusable client context: - self.client: Optional["Client"] = None + self.client: Optional["Client"] = None # noqa: UP037 @wait_container_is_ready() def get_client(self) -> "Client": @@ -75,7 +75,7 @@ def get_client(self) -> "Client": self.client = client return client - def new_client(self, **kwargs) -> tuple["Client", "MQTTErrorCode"]: + def new_client(self, **kwargs: object) -> tuple["Client", "MQTTErrorCode"]: """ Get a paho.mqtt client connected to this container. Check the returned object is_connected() method before use @@ -121,6 +121,10 @@ def start(self, configfile: Optional[str] = None) -> Self: # default config file configfile = Path(__file__).parent / MosquittoContainer.CONFIG_FILE self.with_volume_mapping(configfile, "/mosquitto/config/mosquitto.conf") + # since version 2.1.1 - 2026-02-04, which fixed a PUID/PGID issue, the container needs to write to the data directory, + # so we mount it as tmpfs for better performance in tests + self.with_tmpfs_mount("/data") + # if self.password: # # TODO: add authentication # pass diff --git a/modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf b/src/testcontainers/community/mqtt/testcontainers-mosquitto-default-configuration.conf similarity index 87% rename from modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf rename to src/testcontainers/community/mqtt/testcontainers-mosquitto-default-configuration.conf index 13728cec0..b3c67048f 100644 --- a/modules/mqtt/testcontainers/mqtt/testcontainers-mosquitto-default-configuration.conf +++ b/src/testcontainers/community/mqtt/testcontainers-mosquitto-default-configuration.conf @@ -1,7 +1,6 @@ # see https://mosquitto.org/man/mosquitto-conf-5.html -protocol mqtt -user root +listener 1883 log_dest stdout allow_anonymous true @@ -14,7 +13,4 @@ log_timestamp_format %Y-%m-%d %H:%M:%S persistence true persistence_location /data/ -listener 1883 -protocol mqtt - sys_interval 1 diff --git a/modules/mssql/testcontainers/mssql/__init__.py b/src/testcontainers/community/mssql/__init__.py similarity index 81% rename from modules/mssql/testcontainers/mssql/__init__.py rename to src/testcontainers/community/mssql/__init__.py index e13f9cf0f..f2b6c527c 100644 --- a/modules/mssql/testcontainers/mssql/__init__.py +++ b/src/testcontainers/community/mssql/__init__.py @@ -3,7 +3,7 @@ from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.wait_strategies import ExecWaitStrategy class SqlServerContainer(DbContainer): @@ -15,7 +15,7 @@ class SqlServerContainer(DbContainer): .. doctest:: >>> import sqlalchemy - >>> from testcontainers.mssql import SqlServerContainer + >>> from testcontainers.community.mssql import SqlServerContainer >>> with SqlServerContainer("mcr.microsoft.com/mssql/server:2022-CU12-ubuntu-22.04") as mssql: ... engine = sqlalchemy.create_engine(mssql.get_connection_url()) @@ -50,12 +50,15 @@ def _configure(self) -> None: self.with_env("SQLSERVER_DBNAME", self.dbname) self.with_env("ACCEPT_EULA", "Y") - @wait_container_is_ready(AssertionError) def _connect(self) -> None: - status, _ = self.exec( - ["bash", "-c", '/opt/mssql-tools*/bin/sqlcmd -U "$SQLSERVER_USER" -P "$SA_PASSWORD" -Q \'SELECT 1\' -C'] + strategy = ExecWaitStrategy( + [ + "bash", + "-c", + '/opt/mssql-tools*/bin/sqlcmd -U "$SQLSERVER_USER" -P "$SA_PASSWORD" -Q \'SELECT 1\' -C', + ] ) - assert status == 0, "Cannot run 'SELECT 1': container is not ready" + strategy.wait_until_ready(self) def get_connection_url(self) -> str: return super()._create_connection_url( diff --git a/modules/mysql/testcontainers/mysql/__init__.py b/src/testcontainers/community/mysql/__init__.py similarity index 85% rename from modules/mysql/testcontainers/mysql/__init__.py rename to src/testcontainers/community/mysql/__init__.py index 4c381ff98..503545b17 100644 --- a/modules/mysql/testcontainers/mysql/__init__.py +++ b/src/testcontainers/community/mysql/__init__.py @@ -19,7 +19,7 @@ from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.wait_strategies import LogMessageWaitStrategy class MySqlContainer(DbContainer): @@ -36,9 +36,9 @@ class MySqlContainer(DbContainer): .. doctest:: >>> import sqlalchemy - >>> from testcontainers.mysql import MySqlContainer + >>> from testcontainers.community.mysql import MySqlContainer - >>> with MySqlContainer("mysql:5.7.17", dialect="pymysql") as mysql: + >>> with MySqlContainer("mysql:8.0", dialect="pymysql") as mysql: ... engine = sqlalchemy.create_engine(mysql.get_connection_url()) ... with engine.begin() as connection: ... result = connection.execute(sqlalchemy.text("select version()")) @@ -50,12 +50,13 @@ class MySqlContainer(DbContainer): automatically. .. doctest:: + >>> import sqlalchemy - >>> from testcontainers.mysql import MySqlContainer - >>> with MySqlContainer(seed="../../tests/seeds/") as mysql: + >>> from testcontainers.community.mysql import MySqlContainer + >>> with MySqlContainer(seed=f"{TEST_DIR}/community/mysql/seeds/", dialect="pymysql") as mysql: ... engine = sqlalchemy.create_engine(mysql.get_connection_url()) ... with engine.begin() as connection: - ... query = "select * from stuff" # Can now rely on schema/data + ... query = "select name from stuff" ... result = connection.execute(sqlalchemy.text(query)) ... first_stuff, = result.fetchone() @@ -71,6 +72,7 @@ def __init__( dbname: Optional[str] = None, port: int = 3306, seed: Optional[str] = None, + wait_strategy_check_string: str = r".*: ready for connections.*: ready for connections.*", **kwargs, ) -> None: if dialect is not None and dialect.startswith("mysql+"): @@ -96,6 +98,7 @@ def __init__( if self.username == "root": self.root_password = self.password self.seed = seed + self.wait_strategy_check_string = wait_strategy_check_string def _configure(self) -> None: self.with_env("MYSQL_ROOT_PASSWORD", self.root_password) @@ -106,10 +109,10 @@ def _configure(self) -> None: self.with_env("MYSQL_PASSWORD", self.password) def _connect(self) -> None: - wait_for_logs( - self, - re.compile(".*: ready for connections.*: ready for connections.*", flags=re.DOTALL | re.MULTILINE).search, + wait_strategy = LogMessageWaitStrategy( + re.compile(self.wait_strategy_check_string, flags=re.DOTALL | re.MULTILINE), ) + wait_strategy.wait_until_ready(self) def get_connection_url(self) -> str: return super()._create_connection_url( diff --git a/modules/nats/testcontainers/nats/__init__.py b/src/testcontainers/community/nats/__init__.py similarity index 92% rename from modules/nats/testcontainers/nats/__init__.py rename to src/testcontainers/community/nats/__init__.py index 8ffeca4da..c0d72a17b 100644 --- a/modules/nats/testcontainers/nats/__init__.py +++ b/src/testcontainers/community/nats/__init__.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -26,7 +27,7 @@ class NatsContainer(DockerContainer): >>> import asyncio >>> from nats import connect as nats_connect - >>> from testcontainers.nats import NatsContainer + >>> from testcontainers.community.nats import NatsContainer >>> async def test_doctest_usage(): ... with NatsContainer() as nats_container: @@ -47,6 +48,7 @@ def __init__( management_port: int = 8222, expected_ready_log: str = "Server is ready", ready_timeout_secs: int = 120, + jetstream: bool = False, **kwargs, ) -> None: super().__init__(image, **kwargs) @@ -55,6 +57,8 @@ def __init__( self._expected_ready_log = expected_ready_log self._ready_timeout_secs = max(ready_timeout_secs, 0) self.with_exposed_ports(self.client_port, self.management_port) + if jetstream: + self.with_command("-js") @wait_container_is_ready() def _healthcheck(self) -> None: @@ -69,7 +73,7 @@ def nats_host_and_port(self) -> tuple[str, int]: def nats_management_uri(self) -> str: return f"nats://{self.get_container_host_ip()}:{self.get_exposed_port(self.management_port)}" - def start(self) -> "NatsContainer": + def start(self) -> Self: super().start() self._healthcheck() return self diff --git a/modules/neo4j/testcontainers/neo4j/__init__.py b/src/testcontainers/community/neo4j/__init__.py similarity index 86% rename from modules/neo4j/testcontainers/neo4j/__init__.py rename to src/testcontainers/community/neo4j/__init__.py index 7939c013f..6ddaba970 100644 --- a/modules/neo4j/testcontainers/neo4j/__init__.py +++ b/src/testcontainers/community/neo4j/__init__.py @@ -15,10 +15,11 @@ from typing import Optional from neo4j import Driver, GraphDatabase + from testcontainers.core.config import testcontainers_config as c from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs +from testcontainers.core.wait_strategies import LogMessageWaitStrategy class Neo4jContainer(DbContainer): @@ -29,7 +30,7 @@ class Neo4jContainer(DbContainer): .. doctest:: - >>> from testcontainers.neo4j import Neo4jContainer + >>> from testcontainers.community.neo4j import Neo4jContainer >>> with Neo4jContainer() as neo4j, \\ ... neo4j.get_driver() as driver, \\ @@ -60,9 +61,10 @@ def _configure(self) -> None: def get_connection_url(self) -> str: return f"bolt://{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}" - @wait_container_is_ready() def _connect(self) -> None: - wait_for_logs(self, "Remote interface available at", c.timeout) + wait_strategy = LogMessageWaitStrategy("Remote interface available at") + wait_strategy.with_startup_timeout(c.timeout) + wait_strategy.wait_until_ready(self) # Then we actually check that the container really is listening with self.get_driver() as driver: @@ -70,5 +72,5 @@ def _connect(self) -> None: # force them to do a round trip to confirm neo4j is working driver.verify_connectivity() - def get_driver(self, **kwargs) -> Driver: + def get_driver(self, **kwargs: object) -> Driver: return GraphDatabase.driver(self.get_connection_url(), auth=(self.username, self.password), **kwargs) diff --git a/modules/nginx/testcontainers/nginx/__init__.py b/src/testcontainers/community/nginx/__init__.py similarity index 94% rename from modules/nginx/testcontainers/nginx/__init__.py rename to src/testcontainers/community/nginx/__init__.py index ecf4c072e..4ef7890e4 100644 --- a/modules/nginx/testcontainers/nginx/__init__.py +++ b/src/testcontainers/community/nginx/__init__.py @@ -14,19 +14,21 @@ import urllib.parse import urllib.request +from typing_extensions import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready class NginxContainer(DockerContainer): - def __init__(self, image: str = "nginx:latest", port: int = 80, **kwargs) -> None: + def __init__(self, image: str = "nginx:latest", port: int = 80, **kwargs: object) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") super().__init__(image, **kwargs) self.port = port self.with_exposed_ports(self.port) - def start(self) -> "NginxContainer": + def start(self) -> Self: super().start() host = self.get_container_host_ip() diff --git a/modules/ollama/testcontainers/ollama/__init__.py b/src/testcontainers/community/ollama/__init__.py similarity index 93% rename from modules/ollama/testcontainers/ollama/__init__.py rename to src/testcontainers/community/ollama/__init__.py index ea089f149..8a5c08241 100644 --- a/modules/ollama/testcontainers/ollama/__init__.py +++ b/src/testcontainers/community/ollama/__init__.py @@ -16,6 +16,7 @@ from docker.types.containers import DeviceRequest from requests import get +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_for_logs @@ -44,7 +45,7 @@ class OllamaContainer(DockerContainer): .. doctest:: - >>> from testcontainers.ollama import OllamaContainer + >>> from testcontainers.community.ollama import OllamaContainer >>> with OllamaContainer() as ollama: ... ollama.list_models() [] @@ -54,7 +55,7 @@ class OllamaContainer(DockerContainer): >>> from json import loads >>> from pathlib import Path >>> from requests import post - >>> from testcontainers.ollama import OllamaContainer + >>> from testcontainers.community.ollama import OllamaContainer >>> def split_by_line(generator): ... data = b'' ... for each_item in generator: @@ -92,7 +93,7 @@ def __init__( ollama_home: Optional[Union[str, PathLike]] = None, **kwargs, # - ): + ) -> None: super().__init__(image=image, **kwargs) self.ollama_home = ollama_home self.with_exposed_ports(OllamaContainer.OLLAMA_PORT) @@ -101,9 +102,9 @@ def __init__( def _check_and_add_gpu_capabilities(self): info = self.get_docker_client().client.info() if "nvidia" in info["Runtimes"]: - self._kwargs = {**self._kwargs, "device_requests": DeviceRequest(count=-1, capabilities=[["gpu"]])} + self._kwargs = {**self._kwargs, "device_requests": [DeviceRequest(count=-1, capabilities=[["gpu"]])]} - def start(self) -> "OllamaContainer": + def start(self) -> Self: """ Start the Ollama server """ diff --git a/src/testcontainers/community/openfga/__init__.py b/src/testcontainers/community/openfga/__init__.py new file mode 100644 index 000000000..d0048945c --- /dev/null +++ b/src/testcontainers/community/openfga/__init__.py @@ -0,0 +1,107 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from typing import Optional + +import requests +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + +no_client = False +try: + from openfga_sdk import ClientConfiguration + from openfga_sdk.credentials import CredentialConfiguration, Credentials + from openfga_sdk.sync import OpenFgaClient +except ImportError: + no_client = True + + class OpenFgaClient: + pass + + +_DEFAULT_RUN_COMMAND = "run" + + +class OpenFGAContainer(DockerContainer): + """ + OpenFGAContainer container. + + Example: + + .. doctest:: + + >>> from testcontainers.community.openfga import OpenFGAContainer + >>> from sys import version_info + + >>> with OpenFGAContainer("openfga/openfga:v1.8.4") as openfga: + ... {"continuation_token": "", 'stores': []} if version_info < (3, 10) else openfga.get_client().list_stores() + {'continuation_token': '', 'stores': []} + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + image: str = "openfga/openfga:latest", + preshared_keys: Optional[list[str]] = None, + playground_port: int = 3000, + http_port: int = 8080, + grpc_port: int = 8081, + cmd: str = _DEFAULT_RUN_COMMAND, + ) -> None: + super().__init__(image=image) + self.preshared_keys = preshared_keys + self.playground_port = playground_port + self.http_port = http_port + self.grpc_port = grpc_port + self.with_exposed_ports(self.playground_port, self.http_port, self.grpc_port) + self.cmd = cmd + + def _configure(self) -> None: + if self.preshared_keys: + self.cmd += " --authn-method=preshared" + self.cmd += f' --authn-preshared-keys="{",".join(self.preshared_keys)}"' + self.with_command(self.cmd) + + def get_api_url(self) -> str: + host = self.get_container_host_ip() + port = self.get_exposed_port(self.http_port) + return f"http://{host}:{port}" + + @wait_container_is_ready(requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout) + def _readiness_probe(self) -> None: + self.exec(["grpc_health_probe", "-addr=0.0.0.0:8081"]) # from chart + + def start(self) -> Self: + super().start() + self._readiness_probe() + return self + + def get_preshared_keys(self) -> Optional[list[str]]: + return self.preshared_keys + + def get_client(self) -> "OpenFgaClient": + if no_client: + raise NotImplementedError("failed to import openfga_sdk: is python < 3.10?") + + credentials = None + if preshared_keys := self.get_preshared_keys(): + credentials = Credentials( + method="api_token", + configuration=CredentialConfiguration( + api_token=preshared_keys[0], + ), + ) + client_configuration = ClientConfiguration(api_url=self.get_api_url(), credentials=credentials) + return OpenFgaClient(client_configuration) diff --git a/modules/opensearch/testcontainers/opensearch/__init__.py b/src/testcontainers/community/opensearch/__init__.py similarity index 91% rename from modules/opensearch/testcontainers/opensearch/__init__.py rename to src/testcontainers/community/opensearch/__init__.py index 8a02dbb02..257300239 100644 --- a/modules/opensearch/testcontainers/opensearch/__init__.py +++ b/src/testcontainers/community/opensearch/__init__.py @@ -2,12 +2,15 @@ from opensearchpy import OpenSearch from opensearchpy.exceptions import ConnectionError, TransportError +from typing_extensions import Self from urllib3.exceptions import ProtocolError from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter from testcontainers.core.waiting_utils import wait_container_is_ready +MIN_REQUIRED_INITIAL_ADMIN_PASSWORD = [2, 12, 0] + class OpenSearchContainer(DockerContainer): """ @@ -23,7 +26,7 @@ class OpenSearchContainer(DockerContainer): .. doctest:: - >>> from testcontainers.opensearch import OpenSearchContainer + >>> from testcontainers.community.opensearch import OpenSearchContainer >>> with OpenSearchContainer() as opensearch: ... client = opensearch.get_client() @@ -57,7 +60,7 @@ def __init__( self.with_exposed_ports(self.port) self.with_env("discovery.type", "single-node") - self.with_env("plugins.security.disabled", "false" if security_enabled else "true") + self.with_env("DISABLE_SECURITY_PLUGIN", "false" if security_enabled else "true") if self._supports_initial_admin_password(str(image)): self.with_env("OPENSEARCH_INITIAL_ADMIN_PASSWORD", self.initial_admin_password) if security_enabled: @@ -65,7 +68,7 @@ def __init__( def _supports_initial_admin_password(self, image: str) -> bool: with suppress(Exception): - return [int(n) for n in image.split(":")[-1].split(".")] >= [int(n) for n in "2.12.0".split(".")] + return [int(n) for n in image.split(":")[-1].split(".")] >= MIN_REQUIRED_INITIAL_ADMIN_PASSWORD return False def get_config(self) -> dict: @@ -83,7 +86,7 @@ def get_config(self) -> dict: "password": self.initial_admin_password, } - def get_client(self, verify_certs: bool = False, **kwargs) -> OpenSearch: + def get_client(self, verify_certs: bool = False, **kwargs: object) -> OpenSearch: """Returns a OpenSearch client to connect to the container. Returns: @@ -111,7 +114,7 @@ def _healthcheck(self) -> None: client: OpenSearchContainer = self.get_client() client.cluster.health(wait_for_status="green") - def start(self) -> "OpenSearchContainer": + def start(self) -> Self: """This method starts the OpenSearch container and runs the healthcheck to verify that the container is ready to use.""" super().start() diff --git a/modules/oracle-free/testcontainers/oracle/__init__.py b/src/testcontainers/community/oracle/__init__.py similarity index 97% rename from modules/oracle-free/testcontainers/oracle/__init__.py rename to src/testcontainers/community/oracle/__init__.py index 781be4280..3c53b9f62 100644 --- a/modules/oracle-free/testcontainers/oracle/__init__.py +++ b/src/testcontainers/community/oracle/__init__.py @@ -19,7 +19,7 @@ class OracleDbContainer(DbContainer): ... pytest.skip("linux only test") >>> import sqlalchemy - >>> from testcontainers.oracle import OracleDbContainer + >>> from testcontainers.community.oracle import OracleDbContainer >>> with OracleDbContainer() as oracle: ... engine = sqlalchemy.create_engine(oracle.get_connection_url()) diff --git a/modules/postgres/testcontainers/postgres/__init__.py b/src/testcontainers/community/postgres/__init__.py similarity index 92% rename from modules/postgres/testcontainers/postgres/__init__.py rename to src/testcontainers/community/postgres/__init__.py index bde21d5b3..1de6e09d5 100644 --- a/modules/postgres/testcontainers/postgres/__init__.py +++ b/src/testcontainers/community/postgres/__init__.py @@ -15,7 +15,7 @@ from testcontainers.core.generic import DbContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.wait_strategies import ExecWaitStrategy _UNSET = object() @@ -33,7 +33,7 @@ class PostgresContainer(DbContainer): .. doctest:: - >>> from testcontainers.postgres import PostgresContainer + >>> from testcontainers.community.postgres import PostgresContainer >>> import sqlalchemy >>> with PostgresContainer("postgres:16") as postgres: @@ -87,15 +87,14 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] = port=self.port, ) - @wait_container_is_ready() def _connect(self) -> None: + """Wait for Postgres to be ready by executing a query via psql.""" escaped_single_password = self.password.replace("'", "'\"'\"'") - result = self.exec( + strategy = ExecWaitStrategy( [ "sh", "-c", f"PGPASSWORD='{escaped_single_password}' psql --username {self.username} --dbname {self.dbname} --host 127.0.0.1 -c 'select version();'", ] ) - if result.exit_code: - raise ConnectionError("pg_isready is not ready yet") + strategy.wait_until_ready(self) diff --git a/modules/qdrant/testcontainers/qdrant/__init__.py b/src/testcontainers/community/qdrant/__init__.py similarity index 88% rename from modules/qdrant/testcontainers/qdrant/__init__.py rename to src/testcontainers/community/qdrant/__init__.py index d36fe62ee..17dc65f98 100644 --- a/modules/qdrant/testcontainers/qdrant/__init__.py +++ b/src/testcontainers/community/qdrant/__init__.py @@ -15,19 +15,18 @@ from pathlib import Path from typing import Optional -from testcontainers.core.config import testcontainers_config as c -from testcontainers.core.generic import DbContainer -from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import LogMessageWaitStrategy -class QdrantContainer(DbContainer): +class QdrantContainer(DockerContainer): """ Qdrant vector database container. Example: .. doctest:: - >>> from testcontainers.qdrant import QdrantContainer + >>> from testcontainers.community.qdrant import QdrantContainer >>> with QdrantContainer() as qdrant: ... client = qdrant.get_client() @@ -39,7 +38,7 @@ class QdrantContainer(DbContainer): def __init__( self, - image: str = "qdrant/qdrant:v1.8.3", + image: str = "qdrant/qdrant:v1.16.2", rest_port: int = 6333, grpc_port: int = 6334, api_key: Optional[str] = None, @@ -55,15 +54,12 @@ def __init__( self.with_volume_mapping(host=str(config_file_path), container=QdrantContainer.QDRANT_CONFIG_FILE_PATH) self.with_exposed_ports(self._rest_port, self._grpc_port) + self.waiting_for(LogMessageWaitStrategy(".*Actix runtime found; starting in Actix runtime.*")) def _configure(self) -> None: self.with_env("QDRANT__SERVICE__API_KEY", self._api_key) - @wait_container_is_ready() - def _connect(self) -> None: - wait_for_logs(self, ".*Actix runtime found; starting in Actix runtime.*", c.timeout) - - def get_client(self, **kwargs): + def get_client(self, **kwargs: object): """ Get a `qdrant_client.QdrantClient` instance associated with the container. @@ -88,7 +84,7 @@ def get_client(self, **kwargs): **kwargs, ) - def get_async_client(self, **kwargs): + def get_async_client(self, **kwargs: object): """ Get a `qdrant_client.AsyncQdrantClient` instance associated with the container. diff --git a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py b/src/testcontainers/community/rabbitmq/__init__.py similarity index 92% rename from modules/rabbitmq/testcontainers/rabbitmq/__init__.py rename to src/testcontainers/community/rabbitmq/__init__.py index cd7b079a4..023f97c71 100644 --- a/modules/rabbitmq/testcontainers/rabbitmq/__init__.py +++ b/src/testcontainers/community/rabbitmq/__init__.py @@ -2,6 +2,7 @@ from typing import Optional import pika +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -10,7 +11,7 @@ class RabbitMqContainer(DockerContainer): """ Test container for RabbitMQ. The example below spins up a RabbitMQ broker and uses the - `pika client library <(https://pypi.org/project/pika/)>`__ to establish a connection to the + `pika client library `__ to establish a connection to the broker. Example: @@ -18,7 +19,7 @@ class RabbitMqContainer(DockerContainer): .. doctest:: >>> import pika - >>> from testcontainers.rabbitmq import RabbitMqContainer + >>> from testcontainers.community.rabbitmq import RabbitMqContainer >>> with RabbitMqContainer("rabbitmq:3.9.10") as rabbitmq: ... connection = pika.BlockingConnection(rabbitmq.get_connection_params()) @@ -78,7 +79,7 @@ def get_connection_params(self) -> pika.ConnectionParameters: credentials=credentials, ) - def start(self) -> "RabbitMqContainer": + def start(self) -> Self: """Start the test container.""" super().start() self.readiness_probe() diff --git a/modules/redis/testcontainers/redis/__init__.py b/src/testcontainers/community/redis/__init__.py similarity index 72% rename from modules/redis/testcontainers/redis/__init__.py rename to src/testcontainers/community/redis/__init__.py index 7a4d46613..e1f55bb3c 100644 --- a/modules/redis/testcontainers/redis/__init__.py +++ b/src/testcontainers/community/redis/__init__.py @@ -15,9 +15,10 @@ import redis from redis.asyncio import Redis as asyncRedis + from testcontainers.core.container import DockerContainer from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.waiting_utils import WaitStrategy, WaitStrategyTarget class RedisContainer(DockerContainer): @@ -28,28 +29,24 @@ class RedisContainer(DockerContainer): .. doctest:: - >>> from testcontainers.redis import RedisContainer + >>> from testcontainers.community.redis import RedisContainer >>> with RedisContainer() as redis_container: ... redis_client = redis_container.get_client() """ - def __init__(self, image: str = "redis:latest", port: int = 6379, password: Optional[str] = None, **kwargs) -> None: + def __init__( + self, image: str = "redis:latest", port: int = 6379, password: Optional[str] = None, **kwargs: object + ) -> None: raise_for_deprecated_parameter(kwargs, "port_to_expose", "port") - super().__init__(image, **kwargs) + super().__init__(image, _wait_strategy=PingWaitStrategy(), **kwargs) self.port = port self.password = password self.with_exposed_ports(self.port) if self.password: self.with_command(f"redis-server --requirepass {self.password}") - @wait_container_is_ready(redis.exceptions.ConnectionError) - def _connect(self) -> None: - client = self.get_client() - if not client.ping(): - raise redis.exceptions.ConnectionError("Could not connect to Redis") - - def get_client(self, **kwargs) -> redis.Redis: + def get_client(self, **kwargs: object) -> redis.Redis: """ Get a redis client. @@ -66,10 +63,15 @@ def get_client(self, **kwargs) -> redis.Redis: **kwargs, ) - def start(self) -> "RedisContainer": - super().start() - self._connect() - return self + +class PingWaitStrategy(WaitStrategy): + def __init__(self) -> None: + super().__init__() + self.with_transient_exceptions(redis.exceptions.ConnectionError) + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + if not self._poll(lambda: container.get_client().ping()): + raise redis.exceptions.ConnectionError("Could not connect to Redis") class AsyncRedisContainer(RedisContainer): @@ -80,16 +82,16 @@ class AsyncRedisContainer(RedisContainer): ------- .. doctest:: - >>> from testcontainers.redis import AsyncRedisContainer + >>> from testcontainers.community.redis import AsyncRedisContainer >>> with AsyncRedisContainer() as redis_container: ... redis_client =await redis_container.get_async_client() """ - def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs): + def __init__(self, image="redis:latest", port_to_expose=6379, password=None, **kwargs: object) -> None: super().__init__(image, port_to_expose, password, **kwargs) - async def get_async_client(self, **kwargs): + async def get_async_client(self, **kwargs: object): return await asyncRedis( host=self.get_container_host_ip(), port=self.get_exposed_port(self.port), diff --git a/modules/registry/testcontainers/registry/__init__.py b/src/testcontainers/community/registry/__init__.py similarity index 84% rename from modules/registry/testcontainers/registry/__init__.py rename to src/testcontainers/community/registry/__init__.py index 7b846ad5c..b7fb588ad 100644 --- a/modules/registry/testcontainers/registry/__init__.py +++ b/src/testcontainers/community/registry/__init__.py @@ -1,12 +1,13 @@ import time from io import BytesIO from tarfile import TarFile, TarInfo -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Any, Optional import bcrypt from requests import get from requests.auth import HTTPBasicAuth from requests.exceptions import ConnectionError, ReadTimeout +from typing_extensions import Self from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -25,7 +26,7 @@ def __init__( port: int = 5000, username: Optional[str] = None, password: Optional[str] = None, - **kwargs, + **kwargs: Any, ) -> None: super().__init__(image=image, **kwargs) self.port: int = port @@ -35,6 +36,8 @@ def __init__( def _copy_credentials(self) -> None: # Create credentials and write them to the container + if self.password is None: + raise ValueError("Password cannot be None") hashed_password: str = bcrypt.hashpw( self.password.encode("utf-8"), bcrypt.gensalt(rounds=12, prefix=b"2a"), @@ -44,7 +47,7 @@ def _copy_credentials(self) -> None: with BytesIO() as tar_archive_object, TarFile(fileobj=tar_archive_object, mode="w") as tmp_tarfile: tarinfo: TarInfo = TarInfo(name=self.credentials_path) tarinfo.size = len(content) - tarinfo.mtime = time.time() + tarinfo.mtime = int(time.time()) tmp_tarfile.addfile(tarinfo, BytesIO(content)) tar_archive_object.seek(0) @@ -54,12 +57,13 @@ def _copy_credentials(self) -> None: def _readiness_probe(self) -> None: url: str = f"http://{self.get_registry()}/v2" if self.username and self.password: - response: Response = get(url, auth=HTTPBasicAuth(self.username, self.password), timeout=1) + auth_response: Response = get(url, auth=HTTPBasicAuth(self.username, self.password), timeout=1) + auth_response.raise_for_status() else: response: Response = get(url, timeout=1) - response.raise_for_status() + response.raise_for_status() - def start(self): + def start(self) -> Self: if self.username and self.password: self.with_env("REGISTRY_AUTH_HTPASSWD_REALM", "local-registry") self.with_env("REGISTRY_AUTH_HTPASSWD_PATH", self.credentials_path) diff --git a/modules/scylla/testcontainers/scylla/__init__.py b/src/testcontainers/community/scylla/__init__.py similarity index 71% rename from modules/scylla/testcontainers/scylla/__init__.py rename to src/testcontainers/community/scylla/__init__.py index ca0f44afb..ebd70d20c 100644 --- a/modules/scylla/testcontainers/scylla/__init__.py +++ b/src/testcontainers/community/scylla/__init__.py @@ -1,4 +1,5 @@ -from testcontainers.core.config import MAX_TRIES +from typing_extensions import Self + from testcontainers.core.generic import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs @@ -11,7 +12,7 @@ class ScyllaContainer(DockerContainer): ------- .. doctest:: - >>> from testcontainers.scylla import ScyllaContainer + >>> from testcontainers.community.scylla import ScyllaContainer >>> with ScyllaContainer() as scylla: ... cluster = scylla.get_cluster() @@ -21,27 +22,26 @@ class ScyllaContainer(DockerContainer): ... "= {'class': 'SimpleStrategy', 'replication_factor': '1'};") """ - def __init__(self, image="scylladb/scylla:latest", ports_to_expose=(9042,)): + def __init__(self, image="scylladb/scylla:latest", ports_to_expose=(9042,)) -> None: super().__init__(image) self.ports_to_expose = ports_to_expose self.with_exposed_ports(*self.ports_to_expose) self.with_command("--skip-wait-for-gossip-to-settle=0") - @wait_container_is_ready() + @wait_container_is_ready(OSError) def _connect(self): - wait_for_logs(self, predicate="Starting listening for CQL clients", timeout=MAX_TRIES) + wait_for_logs(self, predicate="Starting listening for CQL clients") cluster = self.get_cluster() cluster.connect() - def start(self): + def start(self) -> Self: super().start() self._connect() return self - def get_cluster(self, **kwargs): + def get_cluster(self, **kwargs: object): from cassandra.cluster import Cluster - container = self.get_wrapped_container() - container.reload() - hostname = container.attrs["NetworkSettings"]["IPAddress"] - return Cluster(contact_points=[hostname], **kwargs) + hostname = self.get_container_host_ip() + port = self.get_exposed_port(9042) + return Cluster(contact_points=[hostname], port=port, **kwargs) diff --git a/modules/selenium/testcontainers/selenium/__init__.py b/src/testcontainers/community/selenium/__init__.py similarity index 94% rename from modules/selenium/testcontainers/selenium/__init__.py rename to src/testcontainers/community/selenium/__init__.py index 53305ef93..77c3a2ccf 100644 --- a/modules/selenium/testcontainers/selenium/__init__.py +++ b/src/testcontainers/community/selenium/__init__.py @@ -14,14 +14,14 @@ from typing import Any, Optional import urllib3 -from typing_extensions import Self - from selenium import webdriver from selenium.webdriver.common.options import ArgOptions +from typing_extensions import Self + +from testcontainers.community.selenium.video import SeleniumVideoContainer from testcontainers.core.container import DockerContainer from testcontainers.core.network import Network from testcontainers.core.waiting_utils import wait_container_is_ready -from testcontainers.selenium.video import SeleniumVideoContainer IMAGES = {"firefox": "selenium/standalone-firefox:latest", "chrome": "selenium/standalone-chrome:latest"} @@ -38,7 +38,7 @@ class BrowserWebDriverContainer(DockerContainer): .. doctest:: - >>> from testcontainers.selenium import BrowserWebDriverContainer + >>> from testcontainers.community.selenium import BrowserWebDriverContainer >>> from selenium.webdriver import DesiredCapabilities >>> with BrowserWebDriverContainer(DesiredCapabilities.CHROME) as chrome: @@ -85,7 +85,7 @@ def get_connection_url(self) -> str: port = self.get_exposed_port(self.port) return f"http://{ip}:{port}/wd/hub" - def with_options(self, options: Optional[ArgOptions]): + def with_options(self, options: Optional[ArgOptions]) -> Self: self.options = options return self @@ -102,7 +102,7 @@ def with_video(self, image: Optional[str] = None, video_path: Optional[Path] = N return self - def start(self) -> "DockerContainer": + def start(self) -> Self: if not self.video: super().start() return self diff --git a/modules/selenium/testcontainers/selenium/video.py b/src/testcontainers/community/selenium/video.py similarity index 79% rename from modules/selenium/testcontainers/selenium/video.py rename to src/testcontainers/community/selenium/video.py index debf098db..e4eff861e 100644 --- a/modules/selenium/testcontainers/selenium/video.py +++ b/src/testcontainers/community/selenium/video.py @@ -12,6 +12,8 @@ # under the License. from typing import Optional +from typing_extensions import Self + from testcontainers.core.container import DockerContainer VIDEO_DEFAULT_IMAGE = "selenium/video:ffmpeg-6.1-20240402" @@ -22,18 +24,18 @@ class SeleniumVideoContainer(DockerContainer): Selenium video container. """ - def __init__(self, image: Optional[str] = None, **kwargs) -> None: + def __init__(self, image: Optional[str] = None, **kwargs: object) -> None: self.image = image or VIDEO_DEFAULT_IMAGE super().__init__(image=self.image, **kwargs) - def set_video_name(self, video_name: str) -> "DockerContainer": + def set_video_name(self, video_name: str) -> Self: self.with_env("FILE_NAME", video_name) return self - def set_videos_host_path(self, host_path: str) -> "DockerContainer": + def set_videos_host_path(self, host_path: str) -> Self: self.with_volume_mapping(host_path, "/videos", "rw") return self - def set_selenium_container_host(self, host: str) -> "DockerContainer": + def set_selenium_container_host(self, host: str) -> Self: self.with_env("DISPLAY_CONTAINER_NAME", host) return self diff --git a/modules/sftp/testcontainers/sftp/__init__.py b/src/testcontainers/community/sftp/__init__.py similarity index 94% rename from modules/sftp/testcontainers/sftp/__init__.py rename to src/testcontainers/community/sftp/__init__.py index 0e073ea1a..f1b54982e 100644 --- a/modules/sftp/testcontainers/sftp/__init__.py +++ b/src/testcontainers/community/sftp/__init__.py @@ -20,7 +20,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa from testcontainers.core.container import DockerContainer -from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.core.wait_strategies import LogMessageWaitStrategy if TYPE_CHECKING: from typing_extensions import Self @@ -59,7 +59,7 @@ class SFTPUser: .. doctest:: - >>> from testcontainers.sftp import SFTPUser + >>> from testcontainers.community.sftp import SFTPUser >>> users = [ ... SFTPUser("jane", password="secret"), @@ -192,14 +192,14 @@ class SFTPContainer(DockerContainer): >>> import paramiko - >>> from testcontainers.sftp import SFTPContainer + >>> from testcontainers.community.sftp import SFTPContainer >>> with SFTPContainer() as sftp_container: ... host_ip = sftp_container.get_container_host_ip() ... host_port = sftp_container.get_exposed_sftp_port() ... ssh = paramiko.SSHClient() ... ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ... ssh.connect(host_ip, host_port, "basic", "password") + ... ssh.connect(host_ip, host_port, "basic", "password", allow_agent=False, look_for_keys=False) ... # ssh.get(...) ... # ssh.listdir() ... # ssh.chdir("upload") @@ -211,7 +211,7 @@ class SFTPContainer(DockerContainer): >>> import paramiko - >>> from testcontainers.sftp import SFTPContainer + >>> from testcontainers.community.sftp import SFTPContainer >>> with SFTPContainer() as sftp_container: ... host_ip = sftp_container.get_container_host_ip() @@ -219,7 +219,7 @@ class SFTPContainer(DockerContainer): ... ssh = paramiko.SSHClient() ... ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ... private_key_file = sftp_container.users[1].private_key_file - ... ssh.connect(host_ip, host_port, "keypair", key_filename=private_key_file) + ... ssh.connect(host_ip, host_port, "keypair", key_filename=private_key_file, allow_agent=False) ... # ssh.listdir() ... # ssh.get(...) ... # ssh.chdir("upload") @@ -265,8 +265,9 @@ def _configure(self) -> None: self.with_exposed_ports(self.port) def start(self) -> Self: + strategy = LogMessageWaitStrategy(f".*Server listening on 0.0.0.0 port {self.port}.*") + self.waiting_for(strategy) super().start() - wait_for_logs(self, f".*Server listening on 0.0.0.0 port {self.port}.*") return self def get_exposed_sftp_port(self) -> int: diff --git a/src/testcontainers/community/toxiproxy/__init__.py b/src/testcontainers/community/toxiproxy/__init__.py new file mode 100644 index 000000000..8253bbb6d --- /dev/null +++ b/src/testcontainers/community/toxiproxy/__init__.py @@ -0,0 +1,137 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import json +from typing import Any, Optional +from urllib.request import Request, urlopen + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import HttpWaitStrategy + + +def _control_request(url: str, method: str = "GET", body: Optional[dict[str, Any]] = None) -> dict[str, Any]: + data = json.dumps(body).encode() if body is not None else None + request = Request(url, data=data, method=method, headers={"Content-Type": "application/json"}) + with urlopen(request, timeout=5) as response: + raw = response.read().decode() + return json.loads(raw) if raw else {} + + +class ToxiproxyProxy: + """A proxy created on a running :class:`ToxiproxyContainer`. + + Connect to the upstream *through* the proxy using :attr:`host` and + :attr:`proxy_port`, then inject failures with :meth:`add_toxic`. + """ + + def __init__(self, name: str, host: str, proxy_port: int, control_url: str) -> None: + self.name = name + self.host = host + self.proxy_port = proxy_port + self._control_url = control_url + + def add_toxic( + self, + toxic_type: str, + attributes: dict[str, Any], + stream: str = "downstream", + toxicity: float = 1.0, + name: Optional[str] = None, + ) -> dict[str, Any]: + """Add a toxic (e.g. ``latency``, ``bandwidth``, ``timeout``) to the proxy. + + See https://github.com/Shopify/toxiproxy#toxics for the available types + and their attributes. + """ + payload = { + "name": name or f"{self.name}_{toxic_type}_{stream}", + "type": toxic_type, + "stream": stream, + "toxicity": toxicity, + "attributes": attributes, + } + return _control_request(f"{self._control_url}/proxies/{self.name}/toxics", "POST", payload) + + +class ToxiproxyContainer(DockerContainer): + """Toxiproxy TCP proxy for simulating adverse network conditions in tests. + + Toxiproxy sits in front of another service and lets tests inject latency, + bandwidth limits, connection drops and other failures to verify resilience. + See https://github.com/Shopify/toxiproxy. + + Example: + + .. doctest:: + + >>> from testcontainers.core.network import Network + >>> from testcontainers.community.nginx import NginxContainer + >>> from testcontainers.community.toxiproxy import ToxiproxyContainer + + >>> with Network() as network: + ... nginx = NginxContainer("nginx:alpine").with_network(network).with_network_aliases("nginx") + ... toxiproxy = ToxiproxyContainer().with_network(network) + ... with nginx, toxiproxy: + ... proxy = toxiproxy.create_proxy("nginx", "nginx:80") + ... _ = proxy.add_toxic("latency", {"latency": 1000}) + """ + + CONTROL_PORT = 8474 + FIRST_PROXY_PORT = 8666 + LAST_PROXY_PORT = 8697 + + def __init__(self, image: str = "ghcr.io/shopify/toxiproxy:2.11.0", **kwargs: object) -> None: + super().__init__(image, **kwargs) + proxy_ports = range(self.FIRST_PROXY_PORT, self.LAST_PROXY_PORT + 1) + self.with_exposed_ports(self.CONTROL_PORT, *proxy_ports) + self.waiting_for(HttpWaitStrategy(self.CONTROL_PORT, "/version")) + self._next_proxy_port = self.FIRST_PROXY_PORT + + def get_control_port(self) -> int: + """Host port mapped to the Toxiproxy HTTP control API.""" + return self.get_exposed_port(self.CONTROL_PORT) + + def get_control_url(self) -> str: + """Base URL of the Toxiproxy HTTP control API, reachable from the host.""" + return f"http://{self.get_container_host_ip()}:{self.get_control_port()}" + + def create_proxy(self, name: str, upstream: str) -> ToxiproxyProxy: + """Create a proxy in front of ``upstream``. + + ``upstream`` must be reachable from the Toxiproxy container itself, e.g. + ``"host:port"`` of another container that shares a network (via + :meth:`~testcontainers.core.container.DockerContainer.with_network_aliases`). + Returns a :class:`ToxiproxyProxy` whose ``host``/``proxy_port`` you + connect through instead of talking to the upstream directly. + """ + if self._next_proxy_port > self.LAST_PROXY_PORT: + max_proxies = self.LAST_PROXY_PORT - self.FIRST_PROXY_PORT + 1 + raise RuntimeError(f"No free proxy ports left (at most {max_proxies} proxies are supported).") + listen_port = self._next_proxy_port + self._next_proxy_port += 1 + _control_request( + f"{self.get_control_url()}/proxies", + "POST", + {"name": name, "listen": f"0.0.0.0:{listen_port}", "upstream": upstream, "enabled": True}, + ) + return ToxiproxyProxy( + name=name, + host=self.get_container_host_ip(), + proxy_port=self.get_exposed_port(listen_port), + control_url=self.get_control_url(), + ) + + def start(self) -> Self: + super().start() + return self diff --git a/modules/trino/testcontainers/trino/__init__.py b/src/testcontainers/community/trino/__init__.py similarity index 60% rename from modules/trino/testcontainers/trino/__init__.py rename to src/testcontainers/community/trino/__init__.py index 97e3f9de4..9b2503d6e 100644 --- a/modules/trino/testcontainers/trino/__init__.py +++ b/src/testcontainers/community/trino/__init__.py @@ -13,41 +13,29 @@ import re from testcontainers.core.config import testcontainers_config as c -from testcontainers.core.generic import DbContainer -from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs -from trino.dbapi import connect +from testcontainers.core.generic import DockerContainer +from testcontainers.core.wait_strategies import LogMessageWaitStrategy -class TrinoContainer(DbContainer): +class TrinoContainer(DockerContainer): def __init__( self, image="trinodb/trino:latest", user: str = "test", port: int = 8080, + container_start_timeout: int = 60, + wait_strategy_check_string: str = ".*======== SERVER STARTED ========.*", **kwargs, - ): + ) -> None: super().__init__(image=image, **kwargs) self.user = user self.port = port self.with_exposed_ports(self.port) - - @wait_container_is_ready() - def _connect(self) -> None: - wait_for_logs( - self, - re.compile(".*======== SERVER STARTED ========.*", re.MULTILINE).search, - c.max_tries, - c.sleep_time, - ) - conn = connect( - host=self.get_container_host_ip(), - port=self.get_exposed_port(self.port), - user=self.user, + self.waiting_for( + LogMessageWaitStrategy(re.compile(wait_strategy_check_string, re.MULTILINE)) + .with_poll_interval(c.sleep_time) + .with_startup_timeout(container_start_timeout) ) - cur = conn.cursor() - cur.execute("SELECT 1") - cur.fetchall() - conn.close() def get_connection_url(self): return f"trino://{self.user}@{self.get_container_host_ip()}:{self.port}" diff --git a/src/testcontainers/community/valkey/__init__.py b/src/testcontainers/community/valkey/__init__.py new file mode 100644 index 000000000..e4f48da39 --- /dev/null +++ b/src/testcontainers/community/valkey/__init__.py @@ -0,0 +1,105 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import ExecWaitStrategy + +_BASE_IMAGE = "valkey/valkey" +_BUNDLE_IMAGE = "valkey/valkey-bundle" + + +class ValkeyContainer(DockerContainer): + """ + Valkey container. + + """ + + def __init__(self, image: str = f"{_BASE_IMAGE}:latest", port: int = 6379, **kwargs: object) -> None: + super().__init__(image, **kwargs) + self.port = port + self.password: str | None = None + self.with_exposed_ports(self.port) + self.waiting_for(ExecWaitStrategy(["valkey-cli", "ping"])) + + def with_password(self, password: str) -> Self: + """ + Configure authentication for Valkey. + + Args: + password: Password for Valkey authentication. + + Returns: + self: Container instance for method chaining. + """ + self.password = password + self.with_command(["valkey-server", "--requirepass", password]) + self.waiting_for(ExecWaitStrategy(["valkey-cli", "-a", password, "ping"])) + return self + + def with_image_tag(self, tag: str) -> Self: + """ + Specify Valkey version. + + Args: + tag: Image tag (e.g., '8.0', 'latest'). + + Returns: + self: Container instance for method chaining. + """ + base_image = self.image.rsplit(":", 1)[0] + self.image = f"{base_image}:{tag}" + return self + + def with_bundle(self) -> Self: + """ + Enable all modules by switching to valkey-bundle image. + + Returns: + self: Container instance for method chaining. + """ + tag = self.image.rsplit(":", 1)[-1] + self.image = f"{_BUNDLE_IMAGE}:{tag}" + return self + + def get_connection_url(self) -> str: + """ + Get connection URL for Valkey. + + Returns: + url: Connection URL in format valkey://[:password@]host:port + """ + host = self.get_host() + port = self.get_exposed_port() + if self.password: + return f"valkey://:{self.password}@{host}:{port}" + return f"valkey://{host}:{port}" + + def get_host(self) -> str: + """ + Get container host. + + Returns: + host: Container host IP. + """ + return self.get_container_host_ip() + + def get_exposed_port(self) -> int: + """ + Get mapped port. + + Returns: + port: Exposed port number. + """ + return int(super().get_exposed_port(self.port)) diff --git a/modules/vault/testcontainers/vault/__init__.py b/src/testcontainers/community/vault/__init__.py similarity index 94% rename from modules/vault/testcontainers/vault/__init__.py rename to src/testcontainers/community/vault/__init__.py index c89b27ea5..96333b1db 100644 --- a/modules/vault/testcontainers/vault/__init__.py +++ b/src/testcontainers/community/vault/__init__.py @@ -15,6 +15,8 @@ from urllib.error import URLError from urllib.request import urlopen +from typing_extensions import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.waiting_utils import wait_container_is_ready @@ -27,7 +29,7 @@ class VaultContainer(DockerContainer): .. doctest:: - >>> from testcontainers.vault import VaultContainer + >>> from testcontainers.community.vault import VaultContainer >>> import hvac >>> with VaultContainer("hashicorp/vault:1.16.1") as vault_container: @@ -69,7 +71,7 @@ def _healthcheck(self) -> None: if res.status > 299: raise HTTPException() - def start(self) -> "VaultContainer": + def start(self) -> Self: super().start() self._healthcheck() return self diff --git a/modules/weaviate/testcontainers/weaviate/__init__.py b/src/testcontainers/community/weaviate/__init__.py similarity index 73% rename from modules/weaviate/testcontainers/weaviate/__init__.py rename to src/testcontainers/community/weaviate/__init__.py index c33983121..0379582d1 100644 --- a/modules/weaviate/testcontainers/weaviate/__init__.py +++ b/src/testcontainers/community/weaviate/__init__.py @@ -10,18 +10,18 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from typing import TYPE_CHECKING, Optional +import contextlib +from typing import Optional -from requests import ConnectionError, get +from docker.types import IPAMConfig, IPAMPool +from typing_extensions import Self -from testcontainers.core.generic import DbContainer -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.container import DockerContainer +from testcontainers.core.network import Network +from testcontainers.core.wait_strategies import HttpWaitStrategy -if TYPE_CHECKING: - from requests import Response - -class WeaviateContainer(DbContainer): +class WeaviateContainer(DockerContainer): """ Weaviate vector database container. @@ -36,7 +36,7 @@ class WeaviateContainer(DbContainer): .. doctest:: - >>> from testcontainers.weaviate import WeaviateContainer + >>> from testcontainers.community.weaviate import WeaviateContainer >>> with WeaviateContainer() as container: ... with container.get_client() as client: @@ -47,7 +47,7 @@ class WeaviateContainer(DbContainer): .. doctest:: - >>> from testcontainers.weaviate import WeaviateContainer + >>> from testcontainers.community.weaviate import WeaviateContainer >>> with WeaviateContainer( ... env_vars={ @@ -63,7 +63,7 @@ class WeaviateContainer(DbContainer): def __init__( self, - image: str = "semitechnologies/weaviate:1.24.5", + image: str = "semitechnologies/weaviate:1.28.4", env_vars: Optional[dict[str, str]] = None, **kwargs, ) -> None: @@ -71,22 +71,38 @@ def __init__( self._http_port = 8080 self._grpc_port = 50051 - self.with_command(f"--host 0.0.0.0 --scheme http --port {self._http_port}") + # Weaviate's memberlist gossip requires an RFC1918 private IP to advertise. + # Docker bridge subnets are not always in RFC1918 ranges, so we create a + # dedicated network using 10.10.10.0/24 which is always recognised as private. + self._weaviate_network = Network( + docker_network_kw={"ipam": IPAMConfig(pool_configs=[IPAMPool(subnet="10.10.10.0/24")])} + ) + self.with_network(self._weaviate_network) self.with_exposed_ports(self._http_port, self._grpc_port) + self.waiting_for(HttpWaitStrategy(self._http_port, "/v1/.well-known/ready")) if env_vars is not None: for key, value in env_vars.items(): self.with_env(key, value) def _configure(self) -> None: + self.with_env("HOST", "0.0.0.0") + self.with_env("PORT", str(self._http_port)) + self.with_env("SCHEME", "http") self.with_env("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "true") self.with_env("PERSISTENCE_DATA_PATH", "/var/lib/weaviate") - - @wait_container_is_ready(ConnectionError) - def _connect(self) -> None: - url = f"http://{self.get_http_host()}:{self.get_http_port()}/v1/.well-known/ready" - response: Response = get(url) - response.raise_for_status() + self.with_env("DEFAULT_VECTORIZER_MODULE", "none") + self.with_env("CLUSTER_HOSTNAME", "node1") + + def start(self) -> Self: + self._weaviate_network.create() + return super().start() + + def stop(self, force: bool = True, delete_volume: bool = True) -> None: + super().stop(force=force, delete_volume=delete_volume) + if self._weaviate_network._network is not None: + with contextlib.suppress(Exception): + self._weaviate_network.remove() def get_client( self, diff --git a/src/testcontainers/compose/__init__.py b/src/testcontainers/compose/__init__.py new file mode 100644 index 000000000..8eb8e100d --- /dev/null +++ b/src/testcontainers/compose/__init__.py @@ -0,0 +1,15 @@ +# flake8: noqa: F401 +from testcontainers.compose.compose import ( + ComposeContainer, + DockerCompose, + PublishedPortModel, +) +from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed + +__all__ = [ + "ComposeContainer", + "ContainerIsNotRunning", + "DockerCompose", + "NoSuchPortExposed", + "PublishedPortModel", +] diff --git a/core/testcontainers/compose/compose.py b/src/testcontainers/compose/compose.py similarity index 51% rename from core/testcontainers/compose/compose.py rename to src/testcontainers/compose/compose.py index e8ce37451..83d9d2e81 100644 --- a/core/testcontainers/compose/compose.py +++ b/src/testcontainers/compose/compose.py @@ -1,52 +1,72 @@ -from dataclasses import asdict, dataclass, field, fields +import shutil +import sys +from dataclasses import asdict, dataclass, field from functools import cached_property from json import loads -from logging import warning +from logging import getLogger, warning from os import PathLike from platform import system from re import split -from subprocess import CompletedProcess +from subprocess import CalledProcessError, CompletedProcess from subprocess import run as subprocess_run +from types import TracebackType from typing import Any, Callable, Literal, Optional, TypeVar, Union, cast -from urllib.error import HTTPError, URLError -from urllib.request import urlopen +from typing_extensions import Self + +from testcontainers.core.docker_client import DockerClient, get_docker_host_hostname, is_podman from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.inspect import ContainerInspectInfo, _ignore_properties +from testcontainers.core.waiting_utils import WaitStrategy -_IPT = TypeVar("_IPT") _WARNINGS = {"DOCKER_COMPOSE_GET_CONFIG": "get_config is experimental, see testcontainers/testcontainers-python#669"} +logger = getLogger(__name__) + -def _ignore_properties(cls: type[_IPT], dict_: any) -> _IPT: - """omits extra fields like @JsonIgnoreProperties(ignoreUnknown = true) +def _default_compose_binary() -> str: + """Return the binary used to drive ``compose`` subcommands. - https://gist.github.com/alexanderankin/2a4549ac03554a31bef6eaaf2eaf7fd5""" - if isinstance(dict_, cls): - return dict_ - class_fields = {f.name for f in fields(cls)} - filtered = {k: v for k, v in dict_.items() if k in class_fields} - return cls(**filtered) + Prefers ``docker`` when available; otherwise falls back to ``podman`` + when the daemon is detected as podman. This means a pure-podman host + without the ``podman-docker`` shim works out of the box. + """ + if shutil.which("docker"): + return "docker" + if is_podman() and shutil.which("podman"): + return "podman" + return "docker" @dataclass -class PublishedPort: +class PublishedPortModel: """ - Class that represents the response we get from compose when inquiring status - via `DockerCompose.get_running_containers()`. + Class that represents the response we get from compose when inquiring status via `DockerCompose.get_running_containers()`. """ URL: Optional[str] = None - TargetPort: Optional[str] = None - PublishedPort: Optional[str] = None + TargetPort: Optional[int] = None + PublishedPort: Optional[int] = None Protocol: Optional[str] = None - def normalize(self): - url_not_usable = system() == "Windows" and self.URL == "0.0.0.0" - if url_not_usable: + def normalize(self) -> "PublishedPortModel": + url = self.URL + + # For SSH-based DOCKER_HOST, local addresses (0.0.0.0, 127.0.0.1, localhost, ::, ::1) + # refer to the remote machine, not the local one. + # Replace them with the actual remote hostname. + # Podman may also return empty string or None for the URL. + ssh_host = get_docker_host_hostname() + if ssh_host and (not url or url in ("0.0.0.0", "127.0.0.1", "localhost", "::", "::1")): + url = ssh_host + # On Windows, 0.0.0.0 is not usable — replace with 127.0.0.1 + elif system() == "Windows" and url == "0.0.0.0": + url = "127.0.0.1" + + if url != self.URL: self_dict = asdict(self) - self_dict.update({"URL": "127.0.0.1"}) - return PublishedPort(**self_dict) + self_dict.update({"URL": url}) + return PublishedPortModel(**self_dict) return self @@ -75,19 +95,21 @@ class ComposeContainer: Service: Optional[str] = None State: Optional[str] = None Health: Optional[str] = None - ExitCode: Optional[str] = None - Publishers: list[PublishedPort] = field(default_factory=list) + ExitCode: Optional[int] = None + Publishers: list[PublishedPortModel] = field(default_factory=list) + _docker_compose: Optional["DockerCompose"] = field(default=None, init=False, repr=False) + _cached_container_info: Optional[ContainerInspectInfo] = field(default=None, init=False, repr=False) - def __post_init__(self): + def __post_init__(self) -> None: if self.Publishers: - self.Publishers = [_ignore_properties(PublishedPort, p) for p in self.Publishers] + self.Publishers = [_ignore_properties(PublishedPortModel, p) for p in self.Publishers] def get_publisher( self, by_port: Optional[int] = None, by_host: Optional[str] = None, - prefer_ip_version: Literal["IPV4", "IPv6"] = "IPv4", - ) -> PublishedPort: + prefer_ip_version: Literal["IPv4", "IPv6"] = "IPv4", + ) -> PublishedPortModel: remaining_publishers = self.Publishers remaining_publishers = [r for r in remaining_publishers if self._matches_protocol(prefer_ip_version, r)] @@ -109,8 +131,66 @@ def get_publisher( ) @staticmethod - def _matches_protocol(prefer_ip_version, r): - return (":" in r.URL) is (prefer_ip_version == "IPv6") + def _matches_protocol(prefer_ip_version: str, r: PublishedPortModel) -> bool: + r_url = r.URL + return (r_url is not None and ":" in r_url) is (prefer_ip_version == "IPv6") + + # WaitStrategy compatibility methods + def get_container_host_ip(self) -> str: + """Get the host IP for the container.""" + # Simplified implementation - wait strategies don't use this yet + return "127.0.0.1" + + def get_exposed_port(self, port: int) -> int: + """Get the exposed port mapping for the given internal port.""" + # Simplified implementation - wait strategies don't use this yet + return port + + def get_logs(self) -> tuple[bytes, bytes]: + """Get container logs.""" + if not self._docker_compose: + raise RuntimeError("DockerCompose reference not set on ComposeContainer") + if not self.Service: + raise RuntimeError("Service name not set on ComposeContainer") + stdout, stderr = self._docker_compose.get_logs(self.Service) + return stdout.encode(), stderr.encode() + + def get_wrapped_container(self) -> Self: + """Get the underlying container object for compatibility.""" + return self + + def reload(self) -> None: + """Reload container information for compatibility with wait strategies.""" + # ComposeContainer doesn't need explicit reloading as it's fetched fresh + # each time through get_container(), but we need this method for compatibility + pass + + def get_container_info(self) -> Optional[ContainerInspectInfo]: + """Get container information via docker inspect (lazy loaded). + + Returns: + Container inspect information or None if container is not started. + """ + if self._cached_container_info is not None: + return self._cached_container_info + + if not self._docker_compose or not self.ID: + return None + + try: + docker_client = self._docker_compose._get_docker_client() + self._cached_container_info = docker_client.get_container_inspect_info(self.ID) + + except Exception as e: + logger.warning(f"Failed to get container info for {self.ID}: {e}") + self._cached_container_info = None + + return self._cached_container_info + + @property + def status(self) -> str: + """Get container status for compatibility with wait strategies.""" + return self.State or "unknown" @dataclass @@ -133,12 +213,18 @@ class DockerCompose: Wait for the services to be healthy (as per healthcheck definitions in the docker compose configuration) env_file: - Path to an '.env' file containing environment variables + Path(s) to an '.env' file containing environment variables to pass to docker compose. services: The list of services to use from this DockerCompose. - client_args: - arguments to pass to docker.from_env() + docker_command_path: + The docker compose command to run. + profiles: + The list of profiles to enable. + quiet_pull: + Whether to suppress output when pulling images. + quiet_build: + Whether to suppress output when building images. Example: @@ -148,11 +234,11 @@ class DockerCompose: >>> from testcontainers.compose import DockerCompose - >>> compose = DockerCompose("compose/tests", compose_file_name="docker-compose-4.yml", + >>> compose = DockerCompose(f"{TEST_DIR}/core/compose_fixtures/basic", compose_file_name="hello.yaml", ... pull=True) >>> with compose: ... stdout, stderr = compose.get_logs() - >>> b"Hello from Docker!" in stdout + >>> "Hello from Docker!" in stdout True .. code-block:: yaml @@ -162,26 +248,38 @@ class DockerCompose: image: "hello-world" """ - context: Union[str, PathLike] + context: Union[str, PathLike[str]] compose_file_name: Optional[Union[str, list[str]]] = None pull: bool = False build: bool = False wait: bool = True keep_volumes: bool = False - env_file: Optional[str] = None + env_file: Optional[Union[str, list[str]]] = None services: Optional[list[str]] = None docker_command_path: Optional[str] = None profiles: Optional[list[str]] = None + quiet_pull: bool = False + quiet_build: bool = False + _wait_strategies: Optional[dict[str, Any]] = field(default=None, init=False, repr=False) + _docker_client: Optional[DockerClient] = field(default=None, init=False, repr=False) - def __post_init__(self): + def __post_init__(self) -> None: if isinstance(self.compose_file_name, str): self.compose_file_name = [self.compose_file_name] - - def __enter__(self) -> "DockerCompose": - self.start() - return self - - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + if isinstance(self.env_file, str): + self.env_file = [self.env_file] + + def __enter__(self) -> Self: + try: + self.start() + return self + except: # noqa: E722, RUF100 + self.__exit__(*sys.exc_info()) + raise + + def __exit__( + self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] + ) -> None: self.stop(not self.keep_volumes) def docker_compose_command(self) -> list[str]: @@ -189,22 +287,34 @@ def docker_compose_command(self) -> list[str]: Returns command parts used for the docker compose commands Returns: - cmd: Docker compose command parts. + list[str]: Docker compose command parts. """ return self.compose_command_property @cached_property def compose_command_property(self) -> list[str]: - docker_compose_cmd = [self.docker_command_path or "docker", "compose"] + binary = self.docker_command_path or _default_compose_binary() + docker_compose_cmd = [binary, "compose"] if self.compose_file_name: for file in self.compose_file_name: docker_compose_cmd += ["-f", file] if self.profiles: docker_compose_cmd += [item for profile in self.profiles for item in ["--profile", profile]] if self.env_file: - docker_compose_cmd += ["--env-file", self.env_file] + for env_file in self.env_file: + docker_compose_cmd += ["--env-file", env_file] return docker_compose_cmd + def waiting_for(self, strategies: dict[str, WaitStrategy]) -> Self: + """ + Set wait strategies for specific services. + + Args: + strategies: Dictionary mapping service names to wait strategies + """ + self._wait_strategies = strategies + return self + def start(self) -> None: """ Starts the docker compose environment. @@ -214,6 +324,8 @@ def start(self) -> None: # pull means running a separate command before starting if self.pull: pull_cmd = [*base_cmd, "pull"] + if self.quiet_pull: + pull_cmd.append("--quiet") self._run_command(cmd=pull_cmd) up_cmd = [*base_cmd, "up"] @@ -221,6 +333,8 @@ def start(self) -> None: # build means modifying the up command if self.build: up_cmd.append("--build") + if self.quiet_build: + up_cmd.append("--quiet-build") if self.wait: up_cmd.append("--wait") @@ -233,7 +347,12 @@ def start(self) -> None: self._run_command(cmd=up_cmd) - def stop(self, down=True) -> None: + if self._wait_strategies: + for service, strategy in self._wait_strategies.items(): + container = self.get_container(service_name=service) + strategy.wait_until_ready(container) + + def stop(self, down: bool = True) -> None: """ Stops the docker compose environment. """ @@ -255,8 +374,8 @@ def get_logs(self, *services: str) -> tuple[str, str]: :param services: which services to get the logs for (or omit, for all) Returns: - stdout: Standard output stream. - stderr: Standard error stream. + str: stdout: Standard output stream. + str: stderr: Standard error stream. """ logs_cmd = [*self.compose_command_property, "logs", *services] @@ -291,9 +410,9 @@ def get_config( config_cmd.append("--no-interpolate") cmd_output = self._run_command(cmd=config_cmd).stdout - return cast(dict[str, Any], loads(cmd_output)) + return cast(dict[str, Any], loads(cmd_output)) # noqa: TC006 - def get_containers(self, include_all=False) -> list[ComposeContainer]: + def get_containers(self, include_all: bool = False) -> list[ComposeContainer]: """ Fetch information about running containers via `docker compose ps --format json`. Available only in V2 of compose. @@ -309,7 +428,7 @@ def get_containers(self, include_all=False) -> list[ComposeContainer]: result = self._run_command(cmd=cmd) stdout = split(r"\r?\n", result.stdout.decode("utf-8")) - containers = [] + containers: list[ComposeContainer] = [] # one line per service in docker 25, single array for docker 24.0.2 for line in stdout: if not line: @@ -320,6 +439,10 @@ def get_containers(self, include_all=False) -> list[ComposeContainer]: else: containers.append(_ignore_properties(ComposeContainer, data)) + # Set the docker_compose reference on each container + for container in containers: + container._docker_compose = self + return containers def get_container( @@ -344,6 +467,7 @@ def get_container( if not matching_containers: raise ContainerIsNotRunning(f"{service_name} is not running in the compose context") + matching_containers[0]._docker_compose = self return matching_containers[0] def exec_in_container( @@ -356,41 +480,48 @@ def exec_in_container( Args: service_name: Name of the docker compose service to run the command in. - command: Command to execute. + command: Command to execute. :param service_name: specify the service name :param command: the command to run in the container Returns: - stdout: Standard output stream. - stderr: Standard error stream. - exit_code: The command's exit code. + str: stdout: Standard output stream. + str: stderr: Standard error stream. + int: exit_code: The command's exit code. """ if not service_name: service_name = self.get_container().Service - exec_cmd = [*self.compose_command_property, "exec", "-T", service_name, *command] + assert service_name + exec_cmd: list[str] = [*self.compose_command_property, "exec", "-T", service_name, *command] result = self._run_command(cmd=exec_cmd) - return (result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode) + return result.stdout.decode("utf-8"), result.stderr.decode("utf-8"), result.returncode def _run_command( self, cmd: Union[str, list[str]], context: Optional[str] = None, ) -> CompletedProcess[bytes]: - context = context or self.context - return subprocess_run( - cmd, - capture_output=True, - check=True, - cwd=context, - ) + context = context or str(self.context) + try: + return subprocess_run( + cmd, + capture_output=True, + check=True, + cwd=context, + ) + except CalledProcessError as e: + logger.error(f"Command '{e.cmd}' failed with exit code {e.returncode}") + logger.error(f"STDOUT:\n{e.stdout.decode(errors='ignore')}") + logger.error(f"STDERR:\n{e.stderr.decode(errors='ignore')}") + raise e from e def get_service_port( self, service_name: Optional[str] = None, port: Optional[int] = None, - ): + ) -> Optional[int]: """ Returns the mapped port for one of the services. @@ -403,16 +534,17 @@ def get_service_port( Returns ------- - str: + int: The mapped port on the host """ - return self.get_container(service_name).get_publisher(by_port=port).normalize().PublishedPort + normalize: PublishedPortModel = self.get_container(service_name).get_publisher(by_port=port).normalize() + return normalize.PublishedPort def get_service_host( self, service_name: Optional[str] = None, port: Optional[int] = None, - ): + ) -> Optional[str]: """ Returns the host for one of the services. @@ -428,26 +560,70 @@ def get_service_host( str: The hostname for the service """ - return self.get_container(service_name).get_publisher(by_port=port).normalize().URL + container: ComposeContainer = self.get_container(service_name) + publisher: PublishedPortModel = container.get_publisher(by_port=port) + normalize: PublishedPortModel = publisher.normalize() + url: Optional[str] = normalize.URL + return url def get_service_host_and_port( self, service_name: Optional[str] = None, port: Optional[int] = None, - ): + ) -> tuple[Optional[str], Optional[int]]: publisher = self.get_container(service_name).get_publisher(by_port=port).normalize() return publisher.URL, publisher.PublishedPort - @wait_container_is_ready(HTTPError, URLError) - def wait_for(self, url: str) -> "DockerCompose": + def wait_for(self, url: str) -> Self: """ Waits for a response from a given URL. This is typically used to block until a service in the environment has started and is responding. Note that it does not assert any sort of return code, only check that the connection was successful. + This is a convenience method that internally uses HttpWaitStrategy. For more complex + wait scenarios, consider using the structured wait strategies with `waiting_for()`. + Args: url: URL from one of the services in the environment to use to wait on. + + Example: + # Simple URL wait (legacy style) + compose.wait_for("http://localhost:8080") \ + \ + # For more complex scenarios, use structured wait strategies: + from testcontainers.core.waiting_utils import HttpWaitStrategy, LogMessageWaitStrategy \ + \ + compose.waiting_for({ \ + "web": HttpWaitStrategy(8080).for_status_code(200), \ + "db": LogMessageWaitStrategy("database system is ready to accept connections") \ + }) """ - with urlopen(url) as response: - response.read() - return self + import time + from urllib.error import HTTPError, URLError + from urllib.request import Request, urlopen + + # For simple URL waiting when we have multiple containers, + # we'll do a direct HTTP check instead of using the container-based strategy + start_time = time.time() + timeout = 120 # Default timeout + + while True: + if time.time() - start_time > timeout: + raise TimeoutError(f"URL {url} not ready within {timeout} seconds") + + try: + request = Request(url, method="GET") + with urlopen(request, timeout=1) as response: + if 200 <= response.status < 400: + return self + except (URLError, HTTPError, ConnectionResetError, ConnectionRefusedError, BrokenPipeError, OSError): + # Any connection error means we should keep waiting + pass + + time.sleep(1) + + def _get_docker_client(self) -> DockerClient: + """Get Docker client instance.""" + if self._docker_client is None: + self._docker_client = DockerClient() + return self._docker_client diff --git a/src/testcontainers/core/__init__.py b/src/testcontainers/core/__init__.py new file mode 100644 index 000000000..fdae0086b --- /dev/null +++ b/src/testcontainers/core/__init__.py @@ -0,0 +1,3 @@ +from .config import testcontainers_config + +__all__ = ["testcontainers_config"] diff --git a/core/testcontainers/core/auth.py b/src/testcontainers/core/auth.py similarity index 100% rename from core/testcontainers/core/auth.py rename to src/testcontainers/core/auth.py diff --git a/src/testcontainers/core/config.py b/src/testcontainers/core/config.py new file mode 100644 index 000000000..f5a9afc9f --- /dev/null +++ b/src/testcontainers/core/config.py @@ -0,0 +1,214 @@ +import types +import warnings +from collections.abc import Mapping +from dataclasses import dataclass, field +from enum import Enum +from logging import warning +from os import environ +from os.path import exists +from pathlib import Path +from typing import Final, Optional, Union + +import docker + +ENABLE_FLAGS = ("yes", "true", "t", "y", "1") + + +class ConnectionMode(Enum): + bridge_ip = "bridge_ip" + gateway_ip = "gateway_ip" + docker_host = "docker_host" + + @property + def use_mapped_port(self) -> bool: + """ + Return True if mapped ports should be used for this connection mode. + + Mapped ports are used for all connection modes except 'bridge_ip'. + """ + return self != ConnectionMode.bridge_ip + + +def get_docker_socket() -> str: + """ + Determine the docker socket, prefer value given by env variable + + Using the docker api ensure we handle rootless docker properly + """ + if socket_path := environ.get("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", ""): + return socket_path + + try: + client = docker.from_env() + # if adapter does not define socket_path, we return default anyway, so ignore types + socket_path = client.api.get_adapter(client.api.base_url).socket_path # type: ignore[attr-defined] + # return the normalized path as string + return str(Path(socket_path).absolute()) + except Exception: + return "/var/run/docker.sock" + + +TC_FILE = ".testcontainers.properties" +TC_GLOBAL = Path.home() / TC_FILE + + +def get_user_overwritten_connection_mode() -> Optional[ConnectionMode]: + """ + Return the user overwritten connection mode. + """ + connection_mode: Union[str, None] = environ.get("TESTCONTAINERS_CONNECTION_MODE") + if connection_mode: + try: + return ConnectionMode(connection_mode) + except ValueError as e: + raise ValueError(f"Error parsing TESTCONTAINERS_CONNECTION_MODE: {e}") from e + return None + + +def read_tc_properties() -> dict[str, str]: + """ + Read the .testcontainers.properties for settings. (see the Java implementation for details) + Currently we only support the ~/.testcontainers.properties but may extend to per-project variables later. + + :return: the merged properties from the sources. + """ + tc_files = [item for item in [TC_GLOBAL] if exists(item)] + if not tc_files: + return {} + settings: dict[str, str] = {} + + for file in tc_files: + with open(file) as contents: + tuples = [line.split("=") for line in contents.readlines() if "=" in line] + settings = {**settings, **{item[0].strip(): item[1].strip() for item in tuples}} + return settings + + +_WARNINGS = {"DOCKER_AUTH_CONFIG": "DOCKER_AUTH_CONFIG is experimental, see testcontainers/testcontainers-python#566"} + + +@dataclass +class TestcontainersConfiguration: + __test__ = False + + def _render_bool(self, env_name: str, prop_name: str) -> bool: + env_val = environ.get(env_name, None) + if env_val is not None: + return env_val.lower() in ENABLE_FLAGS + prop_val = self.tc_properties.get(prop_name, None) + if prop_val is not None: + return prop_val.lower() in ENABLE_FLAGS + return False + + max_tries: int = int(environ.get("TC_MAX_TRIES", "120")) + sleep_time: float = float(environ.get("TC_POOLING_INTERVAL", "1")) + ryuk_image: str = environ.get("RYUK_CONTAINER_IMAGE", "testcontainers/ryuk:0.8.1") + _ryuk_privileged: Optional[bool] = None + _ryuk_disabled: Optional[bool] = None + _ryuk_docker_socket: str = "" + ryuk_reconnection_timeout: str = environ.get("RYUK_RECONNECTION_TIMEOUT", "10s") + tc_properties: dict[str, str] = field(default_factory=read_tc_properties) + _docker_auth_config: Optional[str] = field(default_factory=lambda: environ.get("DOCKER_AUTH_CONFIG")) + tc_host_override: Optional[str] = environ.get("TC_HOST", environ.get("TESTCONTAINERS_HOST_OVERRIDE")) + connection_mode_override: Optional[ConnectionMode] = field(default_factory=get_user_overwritten_connection_mode) + """ + https://github.com/testcontainers/testcontainers-go/blob/dd76d1e39c654433a3d80429690d07abcec04424/docker.go#L644 + if os env TC_HOST is set, use it + """ + hub_image_name_prefix: str = field(default_factory=lambda: environ.get("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "")) + """ Prefix to use for hub image names, e.g. for private registries. """ + + @property + def docker_auth_config(self) -> Optional[str]: + config = self._docker_auth_config + if config and "DOCKER_AUTH_CONFIG" in _WARNINGS: + warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) + return config + + @docker_auth_config.setter + def docker_auth_config(self, value: str) -> None: + if "DOCKER_AUTH_CONFIG" in _WARNINGS: + warning(_WARNINGS.pop("DOCKER_AUTH_CONFIG")) + self._docker_auth_config = value + + def tc_properties_get_tc_host(self) -> Union[str, None]: + return self.tc_properties.get("tc.host") + + @property + def ryuk_privileged(self) -> bool: + if self._ryuk_privileged is not None: + return bool(self._ryuk_privileged) + self._ryuk_privileged = self._render_bool("TESTCONTAINERS_RYUK_PRIVILEGED", "ryuk.container.privileged") + return self._ryuk_privileged + + @ryuk_privileged.setter + def ryuk_privileged(self, value: bool) -> None: + self._ryuk_privileged = value + + @property + def ryuk_disabled(self) -> bool: + if self._ryuk_disabled is not None: + return bool(self._ryuk_disabled) + self._ryuk_disabled = self._render_bool("TESTCONTAINERS_RYUK_DISABLED", "ryuk.disabled") + return self._ryuk_disabled + + @ryuk_disabled.setter + def ryuk_disabled(self, value: bool) -> None: + self._ryuk_disabled = value + + @property + def timeout(self) -> float: + return self.max_tries * self.sleep_time + + @property + def ryuk_docker_socket(self) -> str: + if not self._ryuk_docker_socket: + self.ryuk_docker_socket = get_docker_socket() + return self._ryuk_docker_socket + + @ryuk_docker_socket.setter + def ryuk_docker_socket(self, value: str) -> None: + self._ryuk_docker_socket = value + + +testcontainers_config: Final = TestcontainersConfiguration() + +__all__ = [ + # Public API of this module: + "ConnectionMode", + "testcontainers_config", +] + +_deprecated_attribute_mapping: Final[Mapping[str, str]] = types.MappingProxyType( + { + "MAX_TRIES": "max_tries", + "RYUK_DISABLED": "ryuk_disabled", + "RYUK_DOCKER_SOCKET": "ryuk_docker_socket", + "RYUK_IMAGE": "ryuk_image", + "RYUK_PRIVILEGED": "ryuk_privileged", + "RYUK_RECONNECTION_TIMEOUT": "ryuk_reconnection_timeout", + "SLEEP_TIME": "sleep_time", + "TIMEOUT": "timeout", + } +) + + +def __dir__() -> list[str]: + return __all__ + list(_deprecated_attribute_mapping.keys()) + + +def __getattr__(name: str) -> object: + """ + Allow getting deprecated legacy settings. + """ + module = f"{__name__!r}" + + if name in _deprecated_attribute_mapping: + attrib = _deprecated_attribute_mapping[name] + warnings.warn( + f"{module}.{name} is deprecated. Use {module}.testcontainers_config.{attrib} instead.", + DeprecationWarning, + stacklevel=2, + ) + return getattr(testcontainers_config, attrib) + raise AttributeError(f"module {module} has no attribute {name!r}") diff --git a/src/testcontainers/core/container.py b/src/testcontainers/core/container.py new file mode 100644 index 000000000..76fce9c14 --- /dev/null +++ b/src/testcontainers/core/container.py @@ -0,0 +1,546 @@ +import atexit +import contextlib +import io +import pathlib +import sys +import tarfile +from dataclasses import dataclass +from os import PathLike +from socket import socket +from types import TracebackType +from typing import TYPE_CHECKING, Any, Optional, TypedDict, Union + +import docker.errors +from docker import version +from docker.errors import APIError +from docker.models.containers import ExecResult +from docker.types import EndpointConfig +from dotenv import dotenv_values +from typing_extensions import Self, assert_never + +from testcontainers.core.config import ConnectionMode +from testcontainers.core.config import testcontainers_config as c +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.exceptions import ContainerConnectException, ContainerStartException +from testcontainers.core.inspect import ContainerInspectInfo +from testcontainers.core.labels import LABEL_SESSION_ID, SESSION_ID +from testcontainers.core.network import Network +from testcontainers.core.transferable import Transferable, TransferSpec, build_transfer_tar +from testcontainers.core.utils import is_arm, setup_logger +from testcontainers.core.wait_strategies import LogMessageWaitStrategy +from testcontainers.core.waiting_utils import WaitStrategy + +if TYPE_CHECKING: + from docker.models.containers import Container + +logger = setup_logger(__name__) + + +class Mount(TypedDict): + bind: str + mode: str + + +class BytesExecResult(ExecResult): + exit_code: int | None + output: bytes + + +@dataclass(frozen=True) +class ExecConfig: + """Configuration for a command executed inside a running container. + + Backend-agnostic data carrier. Only `command` is required, and every other field + defaults to `None`/`False`. + + `command` accepts either an argv `list[str]` or a `str`. Both are forwarded untouched. + """ + + command: Union[str, list[str]] + user: Optional[str] = None + environment: Optional[dict[str, str]] = None + workdir: Optional[Union[str, PathLike[str]]] = None + privileged: bool = False + + def to_exec_run_kwargs(self) -> dict[str, Any]: + """Yield the kwargs-dict equivalent for a `docker-py` `exec_run()` call.""" + + return { + "cmd": self.command, + "user": self.user or "", + "environment": self.environment, + "workdir": str(self.workdir) if self.workdir is not None else None, + "privileged": self.privileged, + } + + +class DockerContainer: + """ + + Basic container object to spin up Docker instances. + + Args: + image: The name of the image to start. + docker_client_kw: Dictionary with arguments that will be passed to the docker.DockerClient init. + command: Optional execution command for the container. + name: Optional name for the container. + ports: Ports to be exposed by the container. The port number will be automatically assigned on the host, use :code:`get_exposed_port(PORT)` method to get the port number on the host. + volumes: Volumes to mount into the container. Each entry should be a tuple with three values: host path, container path and mode (default 'ro'). + network: Optional network to connect the container to. + network_aliases: Optional list of aliases for the container in the network. + + .. doctest:: + + >>> from testcontainers.core.container import DockerContainer + >>> from testcontainers.core.waiting_utils import wait_for_logs + + >>> with DockerContainer("hello-world") as container: + ... delay = wait_for_logs(container, "Hello from Docker!") + """ + + def __init__( + self, + image: str, + docker_client_kw: Optional[dict[str, Any]] = None, + command: Optional[str] = None, + env: Optional[dict[str, str]] = None, + name: Optional[str] = None, + ports: Optional[list[int]] = None, + volumes: Optional[list[tuple[str, str, str]]] = None, + network: Optional[Network] = None, + network_aliases: Optional[list[str]] = None, + _wait_strategy: Optional[WaitStrategy] = None, + transferables: Optional[list[TransferSpec]] = None, + **kwargs: Any, + ) -> None: + self.env = env or {} + + self.ports: dict[str, Optional[int]] = {} + if ports: + self.with_exposed_ports(*ports) + + self.volumes: dict[str, Mount] = {} + if volumes: + for vol in volumes: + self.with_volume_mapping(*vol) + + self.tmpfs: dict[str, str] = {} + self.image = c.hub_image_name_prefix + image + self._docker = DockerClient(**(docker_client_kw or {})) + self._container: Optional[Container] = None + self._command: Optional[Union[str, list[str]]] = command + self._name = name + self._network: Optional[Network] = None + if network is not None: + self.with_network(network) + + self._network_aliases: Optional[list[str]] = None + if network_aliases: + self.with_network_aliases(*network_aliases) + + self._kwargs = kwargs + self._wait_strategy: Optional[WaitStrategy] = _wait_strategy + self._cached_container_info: Optional[ContainerInspectInfo] = None + + self._transferable_specs: list[TransferSpec] = [] + if transferables: + for t in transferables: + self.with_copy_into_container(*t) + + def with_env(self, key: str, value: str) -> Self: + self.env[key] = value + return self + + def with_envs(self, **variables: str) -> Self: + self.env.update(variables) + return self + + def with_env_file(self, env_file: Union[str, PathLike[str]]) -> Self: + env_values = dotenv_values(env_file) + for key, value in env_values.items(): + assert value is not None + self.with_env(key, value) + return self + + def with_bind_ports(self, container: Union[str, int], host: Optional[Union[str, int]] = None) -> Self: + """ + Bind container port to host port + + :param container: container port + :param host: host port + + :doctest: + + >>> from testcontainers.core.container import DockerContainer + >>> container = DockerContainer("nginx") + >>> container = container.with_bind_ports("8080/tcp", 8080) + >>> container = container.with_bind_ports("8081/tcp", 8081) + + """ + container = str(container) + host_protocol = "" + host_port: Optional[int] = None + + # normalize the host port to fit the typing protocol and podman api + if isinstance(host, int): + host_port = host + elif host: + host_port_str, _, host_protocol = host.partition("/") + try: + host_port = int(host_port_str) + except ValueError: + raise APIError(f"Host port '{host_port_str}' of '{host}' is not an integer.") from None + + if host_protocol: + container_port, _, container_protocol = container.partition("/") + if not container_protocol: + container = f"{container_port}/{host_protocol}" + elif container_protocol != host_protocol: + raise APIError(f"Container protocol {container_protocol} does not match host protocol {host_protocol}") + + self.ports[container] = host_port + return self + + def with_exposed_ports(self, *ports: Union[str, int]) -> Self: + """ + Expose ports from the container without binding them to the host. + + :param ports: ports to expose + + :doctest: + + >>> from testcontainers.core.container import DockerContainer + >>> container = DockerContainer("nginx") + >>> container = container.with_exposed_ports("8080/tcp", "8081/tcp") + + """ + + for port in ports: + self.ports[str(port)] = None + return self + + def with_network(self, network: Network) -> Self: + self._network = network + return self + + def with_network_aliases(self, *aliases: str) -> Self: + self._network_aliases = list(aliases) + return self + + def with_kwargs(self, **kwargs: Any) -> Self: + self._kwargs = kwargs + return self + + def maybe_emulate_amd64(self) -> Self: + if is_arm(): + return self.with_kwargs(platform="linux/amd64") + return self + + def waiting_for(self, strategy: WaitStrategy) -> Self: + """Set a wait strategy to be used after container start.""" + self._wait_strategy = strategy + return self + + def start(self) -> Self: + if not c.ryuk_disabled and self.image != c.ryuk_image: + logger.debug("Creating Ryuk container") + Reaper.get_instance() + logger.info("Pulling image %s", self.image) + docker_client = self.get_docker_client() + self._configure() + + network_kwargs = ( + { + "network": self._network.name, + "networking_config": { + self._network.name: EndpointConfig(version.__version__, aliases=self._network_aliases) + }, + } + if self._network + else {} + ) + + self._container = docker_client.create( + self.image, + command=self._command, + environment=self.env, + ports=self.ports, + name=self._name, + # dict is invariant: expanding fields explicitly lets mypy verify each value type. + volumes={host: {"bind": mount["bind"], "mode": mount["mode"]} for host, mount in self.volumes.items()}, + tmpfs=self.tmpfs, + **{**network_kwargs, **self._kwargs}, + ) + + for t in self._transferable_specs: + self._transfer_into_container(*t) + + docker_client.start(self._container) + + if self._wait_strategy is not None: + try: + self._wait_strategy.wait_until_ready(self) + except TimeoutError as ex: + if hasattr(ex, "add_note"): + ex.add_note(self._container.logs().decode()) + raise ex + + logger.info("Container started: %s", self._container.short_id) + + return self + + def stop(self, force: bool = True, delete_volume: bool = True) -> None: + if self._container: + self._container.remove(force=force, v=delete_volume) + self.get_docker_client().client.close() + + def __enter__(self) -> Self: + try: + return self.start() + except: # noqa: E722, RUF100 + self.__exit__(*sys.exc_info()) + raise + + def __exit__( + self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] + ) -> None: + self.stop() + + def get_container_id(self) -> str: + if self._container is None or self._container.id is None: + raise ContainerStartException("Container is not started") + return self._container.id + + def get_container_host_ip(self) -> str: + connection_mode: ConnectionMode + connection_mode = self.get_docker_client().get_connection_mode() + + if connection_mode == ConnectionMode.docker_host: + return self.get_docker_client().host() + elif connection_mode == ConnectionMode.gateway_ip: + return self.get_docker_client().gateway_ip(self.get_container_id()) + elif connection_mode == ConnectionMode.bridge_ip: + return self.get_docker_client().bridge_ip(self.get_container_id()) + else: + # ensure that we covered all possible connection_modes + assert_never(connection_mode) + + def get_exposed_port(self, port: int) -> int: + from testcontainers.core.wait_strategies import ContainerStatusWaitStrategy as C + + C().wait_until_ready(self) + return self._get_exposed_port(port) + + def _get_exposed_port(self, port: int) -> int: + if self.get_docker_client().get_connection_mode().use_mapped_port: + return int(self.get_docker_client().port(self.get_container_id(), port)) + return port + + def with_command(self, command: Union[str, list[str]]) -> Self: + self._command = command + return self + + def with_name(self, name: str) -> Self: + self._name = name + return self + + def with_volume_mapping(self, host: Union[str, PathLike[str]], container: str, mode: str = "ro") -> Self: + mapping: Mount = {"bind": container, "mode": mode} + self.volumes[str(host)] = mapping + return self + + def with_tmpfs_mount(self, container_path: str, size: Optional[str] = None) -> Self: + """Mount a tmpfs volume on the container. + + :param container_path: Container path to mount tmpfs on (e.g., '/data') + :param size: Optional size limit (e.g., '256m', '1g'). If None, unbounded. + :return: Self for chaining + """ + self.tmpfs[container_path] = size or "" + return self + + def get_wrapped_container(self) -> "Container": + if self._container is None: + raise ContainerStartException("Container is not started") + return self._container + + def get_docker_client(self) -> DockerClient: + """ + :meta private: + """ + return self._docker + + def get_logs(self) -> tuple[bytes, bytes]: + if not self._container: + raise ContainerStartException("Container should be started before getting logs") + return self._container.logs(stderr=False), self._container.logs(stdout=False) + + def reload(self) -> None: + """Reload container information for compatibility with wait strategies.""" + if self._container: + self._container.reload() + + @property + def status(self) -> str: + """Get container status for compatibility with wait strategies.""" + if not self._container: + return "not_started" + return self._container.status + + def exec(self, command: Union[str, list[str], ExecConfig]) -> BytesExecResult: + if not self._container: + raise ContainerStartException("Container should be started before executing a command") + config = command if isinstance(command, ExecConfig) else ExecConfig(command=command) + result = self._container.exec_run(**config.to_exec_run_kwargs()) + assert isinstance(result.output, bytes) + return BytesExecResult(result[0], result[1]) + + def wait(self) -> int: + """Wait for the container to stop and return its exit code.""" + if not self._container: + raise ContainerStartException("Container should be started before waiting") + result = self._container.wait() + return int(result["StatusCode"]) + + def get_container_info(self) -> Optional[ContainerInspectInfo]: + """Get container information via docker inspect (lazy loaded). + + Returns: + Container inspect information or None if container is not started. + """ + if self._cached_container_info is not None: + return self._cached_container_info + + if not self._container or self._container.id is None: + return None + + try: + self._cached_container_info = self.get_docker_client().get_container_inspect_info(self._container.id) + + except Exception as e: + logger.warning(f"Failed to get container info for {self._container.id}: {e}") + self._cached_container_info = None + + return self._cached_container_info + + def _configure(self) -> None: + # placeholder if subclasses want to define this and use the default start method + pass + + def with_copy_into_container( + self, transferable: Transferable, destination_in_container: str, mode: int = 0o644 + ) -> Self: + self._transferable_specs.append((transferable, destination_in_container, mode)) + return self + + def copy_into_container(self, transferable: Transferable, destination_in_container: str, mode: int = 0o644) -> None: + return self._transfer_into_container(transferable, destination_in_container, mode) + + def _transfer_into_container(self, transferable: Transferable, destination_in_container: str, mode: int) -> None: + if not self._container: + raise ContainerStartException("Container must be started before transferring files") + + data = build_transfer_tar(transferable, destination_in_container, mode) + if not self._container.put_archive(path="/", data=data): + raise OSError(f"Failed to put archive into container at {destination_in_container}") + + def copy_from_container(self, source_in_container: str, destination_on_host: pathlib.Path) -> None: + if not self._container: + raise ContainerStartException("Container must be started before copying files") + + tar_stream, _ = self._container.get_archive(source_in_container) + + with tarfile.open(fileobj=io.BytesIO(b"".join(tar_stream))) as tar: + for member in tar.getmembers(): + extracted = tar.extractfile(member) + if extracted is not None: + destination_on_host.write_bytes(extracted.read()) + + +class Reaper: + """ + :meta private: + """ + + _instance: "Optional[Reaper]" = None + _container: Optional[DockerContainer] = None + _socket: Optional[socket] = None + + @classmethod + def get_instance(cls) -> "Reaper": + if Reaper._instance is None: + Reaper._instance = Reaper._create_instance() + + return Reaper._instance + + @classmethod + def delete_instance(cls) -> None: + if Reaper._socket is not None: + Reaper._socket.close() + Reaper._socket = None + + if Reaper._container is not None and Reaper._container._container is not None: + with contextlib.suppress(docker.errors.NotFound): + Reaper._container.stop() + Reaper._container = None + + if Reaper._instance is not None: + Reaper._instance = None + + @classmethod + def _create_instance(cls) -> "Reaper": + logger.debug(f"Creating new Reaper for session: {SESSION_ID}") + + Reaper._container = ( + DockerContainer(c.ryuk_image) + .with_name(f"testcontainers-ryuk-{SESSION_ID}") + .with_exposed_ports(8080) + .with_volume_mapping(c.ryuk_docker_socket, "/var/run/docker.sock", "rw") + .with_kwargs(privileged=c.ryuk_privileged, auto_remove=True) + .with_env("RYUK_RECONNECTION_TIMEOUT", c.ryuk_reconnection_timeout) + .start() + ) + rc = Reaper._container + assert rc is not None + rc.waiting_for(LogMessageWaitStrategy(r".* Started!").with_startup_timeout(20)) + + container_host = rc.get_container_host_ip() + container_port = int(rc.get_exposed_port(8080)) + + if not container_host or not container_port: + rcc = rc._container + assert rcc + raise ContainerConnectException( + f"Could not obtain network details for {rcc.id}. Host: {container_host} Port: {container_port}" + ) + + last_connection_exception: Optional[Exception] = None + for _ in range(50): + try: + s = socket() + Reaper._socket = s + s.settimeout(1) + s.connect((container_host, container_port)) + last_connection_exception = None + break + except (ConnectionRefusedError, OSError) as e: + if Reaper._socket is not None: + with contextlib.suppress(Exception): + Reaper._socket.close() + Reaper._socket = None + last_connection_exception = e + + from time import sleep + + sleep(0.5) + if last_connection_exception: + raise last_connection_exception + + rs = Reaper._socket + assert rs is not None + rs.send(f"label={LABEL_SESSION_ID}={SESSION_ID}\r\n".encode()) + + Reaper._instance = Reaper() + atexit.register(Reaper.delete_instance) + + return Reaper._instance diff --git a/src/testcontainers/core/docker_client.py b/src/testcontainers/core/docker_client.py new file mode 100644 index 000000000..2fcd8a5f6 --- /dev/null +++ b/src/testcontainers/core/docker_client.py @@ -0,0 +1,589 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import annotations + +import contextlib +import functools +import importlib.metadata +import ipaddress +import os +import socket +import urllib +import urllib.parse +from collections.abc import Iterable, Iterator, Mapping +from typing import TYPE_CHECKING, Any, Literal, Optional, TypedDict, Union, cast, overload + +import docker +from docker.context import ContextAPI +from docker.models.containers import Container +from docker.models.images import Image +from typing_extensions import Unpack + +from testcontainers.core import utils +from testcontainers.core.auth import DockerAuthInfo, parse_docker_auth_config +from testcontainers.core.config import ConnectionMode +from testcontainers.core.config import testcontainers_config as c +from testcontainers.core.inspect import ContainerInspectInfo +from testcontainers.core.labels import SESSION_ID, create_labels + +if TYPE_CHECKING: + from io import StringIO + from typing import IO + + from docker._types import JSON, ContainerWeightDevice # only exists in docker-stubs, not at runtime + from docker.models.containers import _RestartPolicy + from docker.models.images import _ContainerLimits + from docker.models.networks import Network as DockerNetwork + from docker.types import EndpointConfig + from docker.types.containers import DeviceRequest, LogConfig, Ulimit + from docker.types.services import Mount + + class _RunKwargs(TypedDict, total=False): + """Extra keyword arguments for :meth:`DockerClient.run`. + + Mirrors the parameters of :meth:`docker.models.containers.ContainerCollection.run` + that are not declared explicitly on :meth:`DockerClient.run`. All fields are optional + (``total=False``); omitting a field lets the Docker daemon apply its default. + """ + + auto_remove: bool + blkio_weight_device: Optional[list[ContainerWeightDevice]] + blkio_weight: Optional[int] + cap_add: Optional[list[str]] + cap_drop: Optional[list[str]] + cgroup_parent: Optional[str] + cgroupns: Optional[Literal["private", "host"]] + cpu_count: Optional[int] + cpu_percent: Optional[int] + cpu_period: Optional[int] + cpu_quota: Optional[int] + cpu_rt_period: Optional[int] + cpu_rt_runtime: Optional[int] + cpu_shares: Optional[int] + cpuset_cpus: Optional[str] + cpuset_mems: Optional[str] + device_cgroup_rules: Optional[list[str]] + device_read_bps: Optional[list[Mapping[str, Union[str, int]]]] + device_read_iops: Optional[list[Mapping[str, Union[str, int]]]] + device_write_bps: Optional[list[Mapping[str, Union[str, int]]]] + device_write_iops: Optional[list[Mapping[str, Union[str, int]]]] + devices: Optional[list[str]] + device_requests: Optional[list[DeviceRequest]] + dns: Optional[list[str]] + dns_opt: Optional[list[str]] + dns_search: Optional[list[str]] + domainname: Optional[Union[str, list[str]]] + entrypoint: Optional[Union[str, list[str]]] + extra_hosts: Optional[dict[str, str]] + group_add: Optional[Iterable[Union[str, int]]] + healthcheck: Optional[dict[str, Any]] + hostname: Optional[str] + init: Optional[bool] + init_path: Optional[str] + ipc_mode: Optional[str] + isolation: Optional[str] + kernel_memory: Optional[Union[str, int]] + links: Optional[ + Union[dict[str, str], dict[str, None], dict[str, Union[str, None]], Iterable[tuple[str, Union[str, None]]]] + ] + log_config: Optional[LogConfig] + lxc_conf: Optional[dict[str, str]] + mac_address: Optional[str] + mem_limit: Optional[Union[str, int]] + mem_reservation: Optional[Union[str, int]] + mem_swappiness: Optional[int] + memswap_limit: Optional[Union[str, int]] + mounts: Optional[list[Mount]] + name: Optional[str] + nano_cpus: Optional[int] + network: Optional[str] + network_disabled: bool + network_mode: Optional[str] + networking_config: Optional[dict[str, EndpointConfig]] + oom_kill_disable: bool + oom_score_adj: Optional[int] + pid_mode: Optional[str] + pids_limit: Optional[int] + platform: Optional[str] + privileged: bool + publish_all_ports: bool + read_only: Optional[bool] + restart_policy: Optional[_RestartPolicy] + runtime: Optional[str] + security_opt: Optional[list[str]] + shm_size: Optional[Union[str, int]] + stdin_open: bool + stop_signal: Optional[str] + storage_opt: Optional[dict[str, str]] + stream: bool + sysctls: Optional[dict[str, str]] + tmpfs: Optional[dict[str, str]] + tty: bool + ulimits: Optional[list[Ulimit]] + use_config_proxy: Optional[bool] + user: Optional[Union[str, int]] + userns_mode: Optional[str] + uts_mode: Optional[str] + version: Optional[str] + volume_driver: Optional[str] + volumes: Optional[Union[dict[str, dict[str, str]], list[str]]] + volumes_from: Optional[list[str]] + working_dir: Optional[str] + + class _BuildKwargs(TypedDict, total=False): + """Extra keyword arguments for :meth:`DockerClient.build`. + + Mirrors the parameters of :meth:`docker.models.images.ImageCollection.build` + that are not declared explicitly on :meth:`DockerClient.build`. All fields are optional; + """ + + fileobj: Union[StringIO, IO[bytes]] + quiet: bool + nocache: bool + timeout: int + custom_context: bool + encoding: str + pull: bool + forcerm: bool + dockerfile: str + buildargs: dict[str, Any] + container_limits: _ContainerLimits + shmsize: int + labels: dict[str, Any] + cache_from: list[str] + target: str + network_mode: str + squash: bool + extra_hosts: Union[list[str], dict[str, str]] + platform: str + isolation: str + use_config_proxy: bool + + +LOGGER = utils.setup_logger(__name__) + + +class DockerClient: + """ + Thin wrapper around :class:`docker.DockerClient` for a more functional interface. + """ + + client: docker.DockerClient + + def __init__(self, **kwargs: Any) -> None: + docker_host = get_docker_host() + + if docker_host: + LOGGER.info(f"using host {docker_host}") + os.environ["DOCKER_HOST"] = docker_host + # Use shell-based SSH client instead of paramiko to avoid conflicts with pytest stdin capture + # (paramiko's invoke library fails when reading from captured stdin). + if docker_host.startswith("ssh://"): + kwargs.setdefault("use_ssh_client", True) + + self.client = docker.from_env(**kwargs) + self.client.api.headers["x-tc-sid"] = SESSION_ID + self.client.api.headers["User-Agent"] = "tc-python/" + importlib.metadata.version("testcontainers") + + # Verify if we have a docker auth config and login if we do + if docker_auth_config := get_docker_auth_config(): + LOGGER.debug(f"DOCKER_AUTH_CONFIG found: {docker_auth_config}") + if auth_config := parse_docker_auth_config(docker_auth_config): + self.login(auth_config[0]) # Only using the first auth config) + + @overload + def run( + self, + image: str, + command: Optional[Union[str, list[str]]] = None, + *, + detach: Literal[True], + environment: Optional[Union[dict[str, str], list[str]]] = None, + ports: Optional[Mapping[str, Union[int, list[int], tuple[str, int], None]]] = None, + labels: Optional[dict[str, str]] = None, + stdout: bool = True, + stderr: bool = False, + remove: bool = False, + **kwargs: Unpack[_RunKwargs], + ) -> Container: ... + + @overload + def run( + self, + image: str, + command: Optional[Union[str, list[str]]] = None, + *, + detach: Literal[False] = False, + environment: Optional[Union[dict[str, str], list[str]]] = None, + ports: Optional[Mapping[str, Union[int, list[int], tuple[str, int], None]]] = None, + labels: Optional[dict[str, str]] = None, + stdout: bool = True, + stderr: bool = False, + remove: bool = False, + **kwargs: Unpack[_RunKwargs], + ) -> bytes: ... + + def run( + self, + image: str, + command: Optional[Union[str, list[str]]] = None, + *, + detach: bool = False, + environment: Optional[Union[dict[str, str], list[str]]] = None, + ports: Optional[Mapping[str, Union[int, list[int], tuple[str, int], None]]] = None, + labels: Optional[dict[str, str]] = None, + stdout: bool = True, + stderr: bool = False, + remove: bool = False, + **kwargs: Unpack[_RunKwargs], + ) -> Union[Container, bytes]: + # If the user has specified a network, we'll assume the user knows best + if "network" not in kwargs and not get_docker_host(): + # Otherwise we'll try to find the docker host for dind usage. + host_network = self.find_host_network() + if host_network: + kwargs["network"] = host_network + if detach: + return self.client.containers.run( + image, + command=command, + stdout=stdout, + stderr=stderr, + remove=remove, + detach=True, + environment=environment, + ports=ports, + labels=create_labels(image, labels), + **kwargs, + ) + else: + return self.client.containers.run( + image, + command=command, + stdout=stdout, + stderr=stderr, + remove=remove, + detach=False, + environment=environment, + ports=ports, + labels=create_labels(image, labels), + **kwargs, + ) + + def create( + self, + image: str, + command: Optional[Union[str, list[str]]] = None, + environment: Optional[dict[str, str]] = None, + ports: Optional[Mapping[str, Union[int, list[int], tuple[str, int], None]]] = None, + labels: Optional[dict[str, str]] = None, + detach: bool = False, + **kwargs: Unpack[_RunKwargs], + ) -> Container: + """Create a container without starting it, pulling the image first if not present locally.""" + if "network" not in kwargs and not get_docker_host(): + host_network = self.find_host_network() + if host_network: + kwargs["network"] = host_network + + try: + # This is more or less a replication of what the self.client.containers.start does internally + self.client.images.get(image) + except docker.errors.ImageNotFound: + self.client.images.pull(image) + + container = self.client.containers.create( + image, + command=command, + environment=environment, + ports=ports, + labels=create_labels(image, labels), + detach=detach, + **kwargs, + ) + return container + + def start(self, container: Container) -> None: + """Start a previously created container.""" + container.start() + + def build( + self, path: str, tag: Optional[str] = None, rm: bool = True, **kwargs: Unpack[_BuildKwargs] + ) -> tuple[Image, Iterator[JSON]]: + """ + Build a Docker image from a directory containing the Dockerfile. + + :return: A tuple containing the image object and the build logs. + """ + image_object, image_logs = self.client.images.build(path=path, tag=tag, rm=rm, **kwargs) + + return image_object, image_logs + + def find_host_network(self) -> Optional[str]: + """ + Try to find the docker host network. + + :return: The network name if found, None if not set. + """ + # If we're docker in docker running on a custom network, we need to inherit the + # network settings, so we can access the resulting container. + + # first to try to find the network the container runs in, if we can determine + container_id = utils.get_running_in_container_id() + if container_id: + with contextlib.suppress(Exception): + return self.network_name(container_id) + + # if this results nothing, try to determine the network based on the + # docker_host + try: + host_ip = socket.gethostbyname(self.host()) + docker_host = ipaddress.IPv4Address(host_ip) + # See if we can find the host on our networks + for network in self.client.networks.list(filters={"type": "custom"}): + if "IPAM" in network.attrs: + for config in network.attrs["IPAM"]["Config"]: + try: + subnet = ipaddress.IPv4Network(config["Subnet"]) + except ipaddress.AddressValueError: + continue + if docker_host in subnet: + return cast("str", network.name) + except (ipaddress.AddressValueError, OSError): + pass + return None + + def port(self, container_id: str, port: int) -> str: + """ + Lookup the public-facing port that is NAT-ed to :code:`port`. + """ + port_mappings = self.client.api.port(container_id, port) + if not port_mappings: + raise ConnectionError(f"Port mapping for container {container_id} and port {port} is not available") + return cast("str", port_mappings[0]["HostPort"]) + + def get_container(self, container_id: str) -> dict[str, Any]: + """ + Get the container with a given identifier. + """ + containers = self.client.api.containers(all=True, filters={"id": container_id}) + if not containers: + raise RuntimeError(f"Could not get container with id {container_id}") + return cast("dict[str, Any]", containers[0]) + + def bridge_ip(self, container_id: str) -> str: + """ + Get the bridge ip address for a container. + """ + container = self.get_container(container_id) + network_name = self.network_name(container_id) + return str(container["NetworkSettings"]["Networks"][network_name]["IPAddress"]) + + def network_name(self, container_id: str) -> str: + """ + Get the name of the network this container runs on + """ + container = self.get_container(container_id) + name = str(container["HostConfig"]["NetworkMode"]) + if name == "default": + return "bridge" + return name + + def gateway_ip(self, container_id: str) -> str: + """ + Get the gateway ip address for a container. + """ + container = self.get_container(container_id) + network_name = self.network_name(container_id) + return str(container["NetworkSettings"]["Networks"][network_name]["Gateway"]) + + def get_connection_mode(self) -> ConnectionMode: + """ + Determine the connection mode. + + See https://github.com/testcontainers/testcontainers-python/issues/475#issuecomment-2407250970 + """ + if c.connection_mode_override: + return c.connection_mode_override + localhosts = {"localhost", "127.0.0.1", "::1"} + if not utils.inside_container() or self.host() not in localhosts: + # if running not inside a container or with a non-local docker client, + # connect ot the docker host per default + return ConnectionMode.docker_host + elif self.find_host_network(): + # a host network could be determined, indicator for DooD, + # so we should connect to the bridge_ip as the container we run in + # and the one we started are connected to the same network + # that might have no access to either docker_host or the gateway + return ConnectionMode.bridge_ip + # default for DinD + return ConnectionMode.gateway_ip + + def host(self) -> str: + """ + Get the hostname or ip address of the docker host. + """ + host = c.tc_host_override + if host: + return host + + # For SSH-based connections, the Docker SDK rewrites base_url to + # "http+docker://ssh" which loses the original hostname. + # Extract it from the original DOCKER_HOST instead. + ssh_host = get_docker_host_hostname() + if ssh_host: + return ssh_host + + try: + url = urllib.parse.urlparse(self.client.api.base_url) + except ValueError: + return "localhost" + + is_http_scheme = "http" in url.scheme + is_tcp_scheme_with_hostname = "tcp" in url.scheme and url.hostname + if is_http_scheme or is_tcp_scheme_with_hostname: + # see https://github.com/testcontainers/testcontainers-python/issues/415 + hostname = url.hostname + if not hostname or (hostname == "localnpipe" and utils.is_windows()): + return "localhost" + return cast("str", url.hostname) + if utils.inside_container() and ("unix" in url.scheme or "npipe" in url.scheme): + ip_address = utils.default_gateway_ip() + if ip_address: + return ip_address + return "localhost" + + def login(self, auth_config: DockerAuthInfo) -> None: + """ + Login to a docker registry using the given auth config. + """ + login_info = self.client.login(**auth_config._asdict()) + LOGGER.debug(f"logged in using {login_info}") + + def client_networks_create(self, name: str, param: dict[str, Any]) -> DockerNetwork: + labels = create_labels("", param.get("labels")) + return self.client.networks.create(name, **{**param, "labels": labels}) + + def get_container_inspect_info(self, container_id: str) -> ContainerInspectInfo: + """Get container inspect information with fresh data.""" + container = self.client.containers.get(container_id) + return ContainerInspectInfo.from_dict(container.attrs) + + +def get_docker_host() -> Optional[str]: + host = c.tc_properties_get_tc_host() or os.getenv("DOCKER_HOST") or _get_docker_host_from_context() + if host: + return _sanitize_docker_host(host) + return None + + +def _get_docker_host_from_context() -> Optional[str]: + """ + Look up the docker host from the current docker context (e.g. as set by``docker context use``). + This allows users with a remote docker host configured via docker contexts to use testcontainers + without having to additionally export ``DOCKER_HOST``. + """ + try: + context = ContextAPI.get_current_context() + except Exception as e: + LOGGER.debug(f"failed to read current docker context: {e}") + return None + if context is None: + return None + host = context.Host + # The default context points at the local unix socket / named pipe; let + # docker-py fall back to its own defaults in that case. + if not host or context.Name == "default": + return None + return host + + +def get_docker_host_hostname() -> Optional[str]: + """Extract the remote hostname from an SSH-based DOCKER_HOST. + + Returns the hostname (e.g. '192.168.1.42') when DOCKER_HOST is an ssh:// URL, or None otherwise. + """ + docker_host = get_docker_host() + if docker_host and docker_host.startswith("ssh://"): + parsed = urllib.parse.urlparse(docker_host) + if parsed.hostname: + return parsed.hostname + return None + + +def is_ssh_docker_host() -> bool: + """Check if the current DOCKER_HOST is an SSH-based connection.""" + return get_docker_host_hostname() is not None + + +@functools.lru_cache(maxsize=1) +def is_podman() -> bool: + """Detect whether the configured Docker daemon is actually Podman. + + The result is cached for the lifetime of the process: detection requires a + daemon round-trip, and this helper is invoked at test-collection time via + ``pytest.mark.skipif`` decorators. + """ + try: + # Build the client directly rather than via DockerClient() so we avoid + # the constructor's side effects (registry login). We still resolve the + # host the same way DockerClient does so that hosts coming from the + # docker context (and SSH connections) are detected correctly. + docker_host = get_docker_host() + if docker_host: + client_kwargs: dict[str, Any] = {"base_url": docker_host} + if docker_host.startswith("ssh://"): + # Mirror DockerClient: use the shell SSH client to avoid paramiko + # failures under pytest stdin capture. + client_kwargs["use_ssh_client"] = True + version = docker.DockerClient(**client_kwargs).version() + else: + version = docker.from_env().version() + except Exception as e: + LOGGER.debug(f"is_podman: failed to query daemon version: {e}") + return False + + # Prefer the top-level Platform.Name field (matches testcontainers-go). + platform_name = (version.get("Platform") or {}).get("Name", "") + if "podman" in platform_name.lower(): + return True + # Fall back to scanning the Components array for older podman versions. + return any("podman" in comp.get("Name", "").lower() for comp in version.get("Components") or []) + + +def _sanitize_docker_host(docker_host: str) -> str: + """ + Sanitize the DOCKER_HOST value for compatibility with the Docker SDK. + + Strips path components from ``ssh://`` URLs because the Docker SDK + does not support them. A lone trailing ``/`` is treated as + equivalent to no path and silently normalised without a warning. + """ + if docker_host.startswith("ssh://"): + parsed = urllib.parse.urlparse(docker_host) + if parsed.path and parsed.path != "/": + sanitized = urllib.parse.urlunparse(parsed._replace(path="")) + LOGGER.warning( + "Stripped path from SSH DOCKER_HOST (unsupported by Docker SDK): %s -> %s", + docker_host, + sanitized, + ) + return sanitized + if parsed.path == "/": + # Trailing slash is harmless — strip quietly. + return urllib.parse.urlunparse(parsed._replace(path="")) + return docker_host + + +def get_docker_auth_config() -> Optional[str]: + return c.docker_auth_config diff --git a/core/testcontainers/core/exceptions.py b/src/testcontainers/core/exceptions.py similarity index 100% rename from core/testcontainers/core/exceptions.py rename to src/testcontainers/core/exceptions.py diff --git a/core/testcontainers/core/generic.py b/src/testcontainers/core/generic.py similarity index 83% rename from core/testcontainers/core/generic.py rename to src/testcontainers/core/generic.py index b2cd3010d..0ea2f6971 100644 --- a/core/testcontainers/core/generic.py +++ b/src/testcontainers/core/generic.py @@ -10,13 +10,14 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from typing import Optional +from typing import Any, Optional from urllib.parse import quote +from typing_extensions import Self + from testcontainers.core.container import DockerContainer from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.utils import raise_for_deprecated_parameter -from testcontainers.core.waiting_utils import wait_container_is_ready ADDITIONAL_TRANSIENT_ERRORS = [] try: @@ -30,12 +31,17 @@ class DbContainer(DockerContainer): """ **DEPRECATED (for removal)** + Please use database-specific container classes or `SqlContainer` instead. + # from testcontainers.community.generic.sql import SqlContainer Generic database container. """ - @wait_container_is_ready(*ADDITIONAL_TRANSIENT_ERRORS) def _connect(self) -> None: + from testcontainers.core.wait_strategies import ContainerStatusWaitStrategy as C + + C().with_transient_exceptions(*ADDITIONAL_TRANSIENT_ERRORS).wait_until_ready(self) + import sqlalchemy engine = sqlalchemy.create_engine(self.get_connection_url()) @@ -55,13 +61,14 @@ def _create_connection_url( host: Optional[str] = None, port: Optional[int] = None, dbname: Optional[str] = None, - **kwargs, + **kwargs: Any, ) -> str: if raise_for_deprecated_parameter(kwargs, "db_name", "dbname"): raise ValueError(f"Unexpected arguments: {','.join(kwargs)}") if self._container is None: raise ContainerStartException("container has not been started") host = host or self.get_container_host_ip() + assert port is not None port = self.get_exposed_port(port) quoted_password = quote(password, safe=" +") url = f"{dialect}://{username}:{quoted_password}@{host}:{port}" @@ -69,7 +76,7 @@ def _create_connection_url( url = f"{url}/{dbname}" return url - def start(self) -> "DbContainer": + def start(self) -> Self: self._configure() super().start() self._transfer_seed() diff --git a/core/testcontainers/core/image.py b/src/testcontainers/core/image.py similarity index 52% rename from core/testcontainers/core/image.py rename to src/testcontainers/core/image.py index 6d793f83e..329ebedca 100644 --- a/core/testcontainers/core/image.py +++ b/src/testcontainers/core/image.py @@ -1,13 +1,20 @@ +from __future__ import annotations + from os import PathLike -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union +import docker.errors from typing_extensions import Self from testcontainers.core.docker_client import DockerClient from testcontainers.core.utils import setup_logger if TYPE_CHECKING: - from docker.models.containers import Image + from collections.abc import Iterator + from types import TracebackType + + from docker._types import JSON # only exists in docker-stubs, not at runtime + from docker.models.images import Image logger = setup_logger(__name__) @@ -20,40 +27,43 @@ class DockerImage: >>> from testcontainers.core.image import DockerImage - >>> with DockerImage(path="./core/tests/image_fixtures/sample/", tag="test-image") as image: + >>> with DockerImage(path=f"{TEST_DIR}/core/image_fixtures/sample/", tag="test-image") as image: ... logs = image.get_logs() - :param tag: Tag for the image to be built (default: None) :param path: Path to the build context + :param docker_client_kw: Keyword arguments to pass to the DockerClient + :param tag: Tag for the image to be built (default: None) + :param clean_up: Remove the image after exiting the context (default: True) :param dockerfile_path: Path to the Dockerfile within the build context path (default: Dockerfile) :param no_cache: Bypass build cache; CLI's --no-cache + :param kwargs: Additional keyword arguments to pass to the underlying docker-py """ def __init__( self, - path: Union[str, PathLike], - docker_client_kw: Optional[dict] = None, + path: Union[str, PathLike[str]], + docker_client_kw: Optional[dict[str, Any]] = None, tag: Optional[str] = None, clean_up: bool = True, - dockerfile_path: Union[str, PathLike] = "Dockerfile", + dockerfile_path: str = "Dockerfile", no_cache: bool = False, - **kwargs, + **kwargs: Any, ) -> None: self.tag = tag self.path = path self._docker = DockerClient(**(docker_client_kw or {})) self.clean_up = clean_up self._kwargs = kwargs - self._image = None - self._logs = None + self._image: Optional[Image] = None + self._logs: Iterator[JSON] | None = None self._dockerfile_path = dockerfile_path self._no_cache = no_cache - def build(self, **kwargs) -> Self: + def build(self) -> Self: logger.info(f"Building image from {self.path}") docker_client = self.get_docker_client() self._image, self._logs = docker_client.build( - path=str(self.path), tag=self.tag, dockerfile=self._dockerfile_path, nocache=self._no_cache, **kwargs + path=str(self.path), tag=self.tag, dockerfile=self._dockerfile_path, nocache=self._no_cache, **self._kwargs ) logger.info(f"Built image {self.short_id} with tag {self.tag}") return self @@ -63,11 +73,15 @@ def short_id(self) -> str: """ The ID of the image truncated to 12 characters, without the ``sha256:`` prefix. """ - if self._image.id.startswith("sha256:"): - return self._image.id.split(":")[1][:12] - return self._image.id[:12] - - def remove(self, force=True, noprune=False) -> None: + i = self._image + assert i + i_id = i.id + assert isinstance(i_id, str) + if i_id.startswith("sha256:"): + return i_id.split(":")[1][:12] + return i_id[:12] + + def remove(self, force: bool = True, noprune: bool = False) -> None: """ Remove the image. @@ -75,8 +89,11 @@ def remove(self, force=True, noprune=False) -> None: :param noprune: Do not delete untagged parent images """ if self._image and self.clean_up: - logger.info(f"Removing image {self.short_id}") - self._image.remove(force=force, noprune=noprune) + try: + logger.info(f"Removing image {self.short_id}") + self._image.remove(force=force, noprune=noprune) + except docker.errors.NotFound: + logger.debug(f"Image {self.short_id} already removed") self.get_docker_client().client.close() def __str__(self) -> str: @@ -85,14 +102,20 @@ def __str__(self) -> str: def __enter__(self) -> Self: return self.build() - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__( + self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] + ) -> None: self.remove() - def get_wrapped_image(self) -> "Image": + def get_wrapped_image(self) -> Image: + assert self._image is not None return self._image def get_docker_client(self) -> DockerClient: return self._docker - def get_logs(self) -> list[dict]: - return list(self._logs) + def get_logs(self) -> list[JSON]: + logs = self._logs + if logs is None: + return [] + return list(logs) diff --git a/src/testcontainers/core/inspect.py b/src/testcontainers/core/inspect.py new file mode 100644 index 000000000..b83586980 --- /dev/null +++ b/src/testcontainers/core/inspect.py @@ -0,0 +1,634 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Docker Engine API data structures for container inspect responses.""" + +from dataclasses import dataclass, fields, is_dataclass +from typing import Any, Optional, TypeVar + +from typing_extensions import Self + +_IPT = TypeVar("_IPT") + + +def _ignore_properties(cls: type[_IPT], dict_: Any) -> _IPT: + """omits extra fields like @JsonIgnoreProperties(ignoreUnknown = true) + + https://gist.github.com/alexanderankin/2a4549ac03554a31bef6eaaf2eaf7fd5""" + if isinstance(dict_, cls): + return dict_ + if not is_dataclass(cls): + raise TypeError(f"Expected a dataclass type, got {cls}") + class_fields = {f.name for f in fields(cls)} + filtered = {k: v for k, v in dict_.items() if k in class_fields} + return cls(**filtered) + + +@dataclass +class ContainerLog: + """Container health check log entry.""" + + Start: Optional[str] = None + End: Optional[str] = None + ExitCode: Optional[int] = None + Output: Optional[str] = None + + +@dataclass +class ContainerHealth: + """Container health check information.""" + + Status: Optional[str] = None + FailingStreak: Optional[int] = None + Log: Optional[list[ContainerLog]] = None + + +@dataclass +class ContainerState: + """Container state information.""" + + Status: Optional[str] = None + Running: Optional[bool] = None + Paused: Optional[bool] = None + Restarting: Optional[bool] = None + OOMKilled: Optional[bool] = None + Dead: Optional[bool] = None + Pid: Optional[int] = None + ExitCode: Optional[int] = None + Error: Optional[str] = None + StartedAt: Optional[str] = None + FinishedAt: Optional[str] = None + Health: Optional[ContainerHealth] = None + + +@dataclass +class ContainerPlatform: + """Platform information for image manifest.""" + + architecture: Optional[str] = None + os: Optional[str] = None + variant: Optional[str] = None + + +@dataclass +class ContainerImageManifestDescriptor: + """Image manifest descriptor.""" + + mediaType: Optional[str] = None + digest: Optional[str] = None + size: Optional[int] = None + urls: Optional[list[str]] = None + annotations: Optional[dict[str, str]] = None + data: Optional[Any] = None + platform: Optional[ContainerPlatform] = None + artifactType: Optional[str] = None + + +@dataclass +class ContainerBlkioWeightDevice: + """Block IO weight device configuration.""" + + Path: Optional[str] = None + Weight: Optional[int] = None + + +@dataclass +class ContainerBlkioDeviceRate: + """Block IO device rate configuration.""" + + Path: Optional[str] = None + Rate: Optional[int] = None + + +@dataclass +class ContainerDeviceMapping: + """Device mapping configuration.""" + + PathOnHost: Optional[str] = None + PathInContainer: Optional[str] = None + CgroupPermissions: Optional[str] = None + + +@dataclass +class ContainerDeviceRequest: + """Device request configuration.""" + + Driver: Optional[str] = None + Count: Optional[int] = None + DeviceIDs: Optional[list[str]] = None + Capabilities: Optional[list[list[str]]] = None + Options: Optional[dict[str, str]] = None + + +@dataclass +class ContainerUlimit: + """Ulimit configuration.""" + + Name: Optional[str] = None + Soft: Optional[int] = None + Hard: Optional[int] = None + + +@dataclass +class ContainerLogConfig: + """Logging configuration.""" + + Type: Optional[str] = None + Config: Optional[dict[str, str]] = None + + +@dataclass +class ContainerPortBinding: + """Port binding configuration.""" + + HostIp: Optional[str] = None + HostPort: Optional[str] = None + + +@dataclass +class ContainerRestartPolicy: + """Restart policy configuration.""" + + Name: Optional[str] = None + MaximumRetryCount: Optional[int] = None + + +@dataclass +class ContainerBindOptions: + """Bind mount options.""" + + Propagation: Optional[str] = None + NonRecursive: Optional[bool] = None + CreateMountpoint: Optional[bool] = None + ReadOnlyNonRecursive: Optional[bool] = None + ReadOnlyForceRecursive: Optional[bool] = None + + +@dataclass +class ContainerVolumeDriverConfig: + """Volume driver configuration.""" + + Name: Optional[str] = None + Options: Optional[dict[str, str]] = None + + +@dataclass +class ContainerVolumeOptions: + """Volume mount options.""" + + NoCopy: Optional[bool] = None + Labels: Optional[dict[str, str]] = None + DriverConfig: Optional[ContainerVolumeDriverConfig] = None + Subpath: Optional[str] = None + + +@dataclass +class ContainerImageOptions: + """Image mount options.""" + + Subpath: Optional[str] = None + + +@dataclass +class ContainerTmpfsOptions: + """Tmpfs mount options.""" + + SizeBytes: Optional[int] = None + Mode: Optional[int] = None + Options: Optional[list[list[str]]] = None + + +@dataclass +class ContainerMountPoint: + """Mount point configuration.""" + + Target: Optional[str] = None + Source: Optional[str] = None + Type: Optional[str] = None + ReadOnly: Optional[bool] = None + Consistency: Optional[str] = None + BindOptions: Optional[ContainerBindOptions] = None + VolumeOptions: Optional[ContainerVolumeOptions] = None + ImageOptions: Optional[ContainerImageOptions] = None + TmpfsOptions: Optional[ContainerTmpfsOptions] = None + + +@dataclass +class ContainerHostConfig: + """Host configuration for container.""" + + CpuShares: Optional[int] = None + Memory: Optional[int] = None + CgroupParent: Optional[str] = None + BlkioWeight: Optional[int] = None + BlkioWeightDevice: Optional[list[ContainerBlkioWeightDevice]] = None + BlkioDeviceReadBps: Optional[list[ContainerBlkioDeviceRate]] = None + BlkioDeviceWriteBps: Optional[list[ContainerBlkioDeviceRate]] = None + BlkioDeviceReadIOps: Optional[list[ContainerBlkioDeviceRate]] = None + BlkioDeviceWriteIOps: Optional[list[ContainerBlkioDeviceRate]] = None + CpuPeriod: Optional[int] = None + CpuQuota: Optional[int] = None + CpuRealtimePeriod: Optional[int] = None + CpuRealtimeRuntime: Optional[int] = None + CpusetCpus: Optional[str] = None + CpusetMems: Optional[str] = None + Devices: Optional[list[ContainerDeviceMapping]] = None + DeviceCgroupRules: Optional[list[str]] = None + DeviceRequests: Optional[list[ContainerDeviceRequest]] = None + KernelMemoryTCP: Optional[int] = None + MemoryReservation: Optional[int] = None + MemorySwap: Optional[int] = None + MemorySwappiness: Optional[int] = None + NanoCpus: Optional[int] = None + OomKillDisable: Optional[bool] = None + Init: Optional[bool] = None + PidsLimit: Optional[int] = None + Ulimits: Optional[list[ContainerUlimit]] = None + CpuCount: Optional[int] = None + CpuPercent: Optional[int] = None + IOMaximumIOps: Optional[int] = None + IOMaximumBandwidth: Optional[int] = None + Binds: Optional[list[str]] = None + ContainerIDFile: Optional[str] = None + LogConfig: Optional[ContainerLogConfig] = None + NetworkMode: Optional[str] = None + PortBindings: Optional[dict[str, Optional[list[ContainerPortBinding]]]] = None + RestartPolicy: Optional[ContainerRestartPolicy] = None + AutoRemove: Optional[bool] = None + VolumeDriver: Optional[str] = None + VolumesFrom: Optional[list[str]] = None + Mounts: Optional[list[ContainerMountPoint]] = None + ConsoleSize: Optional[list[int]] = None + Annotations: Optional[dict[str, str]] = None + CapAdd: Optional[list[str]] = None + CapDrop: Optional[list[str]] = None + CgroupnsMode: Optional[str] = None + Dns: Optional[list[str]] = None + DnsOptions: Optional[list[str]] = None + DnsSearch: Optional[list[str]] = None + ExtraHosts: Optional[list[str]] = None + GroupAdd: Optional[list[str]] = None + IpcMode: Optional[str] = None + Cgroup: Optional[str] = None + Links: Optional[list[str]] = None + OomScoreAdj: Optional[int] = None + PidMode: Optional[str] = None + Privileged: Optional[bool] = None + PublishAllPorts: Optional[bool] = None + ReadonlyRootfs: Optional[bool] = None + SecurityOpt: Optional[list[str]] = None + StorageOpt: Optional[dict[str, str]] = None + Tmpfs: Optional[dict[str, str]] = None + UTSMode: Optional[str] = None + UsernsMode: Optional[str] = None + ShmSize: Optional[int] = None + Sysctls: Optional[dict[str, str]] = None + Runtime: Optional[str] = None + Isolation: Optional[str] = None + MaskedPaths: Optional[list[str]] = None + ReadonlyPaths: Optional[list[str]] = None + + def __post_init__(self) -> None: + list_conversions = [ + ("BlkioWeightDevice", ContainerBlkioWeightDevice), + ("BlkioDeviceReadBps", ContainerBlkioDeviceRate), + ("BlkioDeviceWriteBps", ContainerBlkioDeviceRate), + ("BlkioDeviceReadIOps", ContainerBlkioDeviceRate), + ("BlkioDeviceWriteIOps", ContainerBlkioDeviceRate), + ("Devices", ContainerDeviceMapping), + ("DeviceRequests", ContainerDeviceRequest), + ("Ulimits", ContainerUlimit), + ("Mounts", ContainerMountPoint), + ] + + for field_name, target_class in list_conversions: + field_value = getattr(self, field_name) + if field_value is not None and isinstance(field_value, list): + setattr( + self, + field_name, + [ + _ignore_properties(target_class, item) if isinstance(item, dict) else item + for item in field_value + ], + ) + + if self.LogConfig is not None and isinstance(self.LogConfig, dict): + self.LogConfig = _ignore_properties(ContainerLogConfig, self.LogConfig) + + if self.RestartPolicy is not None and isinstance(self.RestartPolicy, dict): + self.RestartPolicy = _ignore_properties(ContainerRestartPolicy, self.RestartPolicy) + + if self.PortBindings is not None and isinstance(self.PortBindings, dict): + for port, bindings in self.PortBindings.items(): + if bindings is not None and isinstance(bindings, list): + self.PortBindings[port] = [ + _ignore_properties(ContainerPortBinding, b) if isinstance(b, dict) else b for b in bindings + ] + + +@dataclass +class ContainerGraphDriver: + """Graph driver information.""" + + Name: Optional[str] = None + Data: Optional[dict[str, str]] = None + + +@dataclass +class ContainerMount: + """Mount information.""" + + Type: Optional[str] = None + Name: Optional[str] = None + Source: Optional[str] = None + Destination: Optional[str] = None + Driver: Optional[str] = None + Mode: Optional[str] = None + RW: Optional[bool] = None + Propagation: Optional[str] = None + + +@dataclass +class ContainerHealthcheck: + """Container healthcheck configuration.""" + + Test: Optional[list[str]] = None + Interval: Optional[int] = None + Timeout: Optional[int] = None + Retries: Optional[int] = None + StartPeriod: Optional[int] = None + StartInterval: Optional[int] = None + + +@dataclass +class ContainerConfig: + """Container configuration.""" + + Hostname: Optional[str] = None + Domainname: Optional[str] = None + User: Optional[str] = None + AttachStdin: Optional[bool] = None + AttachStdout: Optional[bool] = None + AttachStderr: Optional[bool] = None + ExposedPorts: Optional[dict[str, dict[str, Any]]] = None + Tty: Optional[bool] = None + OpenStdin: Optional[bool] = None + StdinOnce: Optional[bool] = None + Env: Optional[list[str]] = None + Cmd: Optional[list[str]] = None + Healthcheck: Optional[ContainerHealthcheck] = None + ArgsEscaped: Optional[bool] = None + Image: Optional[str] = None + Volumes: Optional[dict[str, dict[str, Any]]] = None + WorkingDir: Optional[str] = None + Entrypoint: Optional[list[str]] = None + NetworkDisabled: Optional[bool] = None + MacAddress: Optional[str] = None + OnBuild: Optional[list[str]] = None + Labels: Optional[dict[str, str]] = None + StopSignal: Optional[str] = None + StopTimeout: Optional[int] = None + Shell: Optional[list[str]] = None + + +@dataclass +class ContainerIPAMConfig: + """IPAM configuration for network.""" + + IPv4Address: Optional[str] = None + IPv6Address: Optional[str] = None + LinkLocalIPs: Optional[list[str]] = None + + +@dataclass +class ContainerNetworkEndpoint: + """Network endpoint information.""" + + IPAMConfig: Optional[ContainerIPAMConfig] = None + Links: Optional[list[str]] = None + MacAddress: Optional[str] = None + Aliases: Optional[list[str]] = None + DriverOpts: Optional[dict[str, str]] = None + GwPriority: Optional[list[int]] = None + NetworkID: Optional[str] = None + EndpointID: Optional[str] = None + Gateway: Optional[str] = None + IPAddress: Optional[str] = None + IPPrefixLen: Optional[int] = None + IPv6Gateway: Optional[str] = None + GlobalIPv6Address: Optional[str] = None + GlobalIPv6PrefixLen: Optional[int] = None + DNSNames: Optional[list[str]] = None + + +@dataclass +class ContainerAddress: + """IP address information.""" + + Addr: Optional[str] = None + PrefixLen: Optional[int] = None + + +@dataclass +class ContainerNetworkSettings: + """Network settings for container.""" + + Bridge: Optional[str] = None + SandboxID: Optional[str] = None + HairpinMode: Optional[bool] = None + LinkLocalIPv6Address: Optional[str] = None + LinkLocalIPv6PrefixLen: Optional[str] = None + Ports: Optional[dict[str, Optional[list[ContainerPortBinding]]]] = None + SandboxKey: Optional[str] = None + SecondaryIPAddresses: Optional[list[ContainerAddress]] = None + SecondaryIPv6Addresses: Optional[list[ContainerAddress]] = None + EndpointID: Optional[str] = None + Gateway: Optional[str] = None + GlobalIPv6Address: Optional[str] = None + GlobalIPv6PrefixLen: Optional[int] = None + IPAddress: Optional[str] = None + IPPrefixLen: Optional[int] = None + IPv6Gateway: Optional[str] = None + MacAddress: Optional[str] = None + Networks: Optional[dict[str, ContainerNetworkEndpoint]] = None + + def __post_init__(self) -> None: + if self.Ports is not None and isinstance(self.Ports, dict): + for port, bindings in self.Ports.items(): + if bindings is not None and isinstance(bindings, list): + self.Ports[port] = [ + _ignore_properties(ContainerPortBinding, b) if isinstance(b, dict) else b for b in bindings + ] + + if self.Networks is not None and isinstance(self.Networks, dict): + for name, network_data in self.Networks.items(): + if isinstance(network_data, dict): + self.Networks[name] = _ignore_properties(ContainerNetworkEndpoint, network_data) + + if self.SecondaryIPAddresses is not None and isinstance(self.SecondaryIPAddresses, list): + self.SecondaryIPAddresses = [ + _ignore_properties(ContainerAddress, addr) if isinstance(addr, dict) else addr + for addr in self.SecondaryIPAddresses + ] + + if self.SecondaryIPv6Addresses is not None and isinstance(self.SecondaryIPv6Addresses, list): + self.SecondaryIPv6Addresses = [ + _ignore_properties(ContainerAddress, addr) if isinstance(addr, dict) else addr + for addr in self.SecondaryIPv6Addresses + ] + + def get_networks(self) -> Optional[dict[str, ContainerNetworkEndpoint]]: + """Get networks for the container.""" + return self.Networks + + +@dataclass +class ContainerInspectInfo: + """Complete container information from docker inspect.""" + + Id: Optional[str] = None + Created: Optional[str] = None + Path: Optional[str] = None + Args: Optional[list[str]] = None + State: Optional[ContainerState] = None + Image: Optional[str] = None + ResolvConfPath: Optional[str] = None + HostnamePath: Optional[str] = None + HostsPath: Optional[str] = None + LogPath: Optional[str] = None + Name: Optional[str] = None + RestartCount: Optional[int] = None + Driver: Optional[str] = None + Platform: Optional[str] = None + ImageManifestDescriptor: Optional[ContainerImageManifestDescriptor] = None + MountLabel: Optional[str] = None + ProcessLabel: Optional[str] = None + AppArmorProfile: Optional[str] = None + ExecIDs: Optional[list[str]] = None + HostConfig: Optional[ContainerHostConfig] = None + GraphDriver: Optional[ContainerGraphDriver] = None + SizeRw: Optional[str] = None + SizeRootFs: Optional[str] = None + Mounts: Optional[list[ContainerMount]] = None + Config: Optional[ContainerConfig] = None + NetworkSettings: Optional[ContainerNetworkSettings] = None + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> Self: + """Create from docker inspect JSON.""" + return cls( + Id=data.get("Id"), + Created=data.get("Created"), + Path=data.get("Path"), + Args=data.get("Args"), + State=cls._parse_state(data.get("State", {})) if data.get("State") else None, + Image=data.get("Image"), + ResolvConfPath=data.get("ResolvConfPath"), + HostnamePath=data.get("HostnamePath"), + HostsPath=data.get("HostsPath"), + LogPath=data.get("LogPath"), + Name=data.get("Name"), + RestartCount=data.get("RestartCount"), + Driver=data.get("Driver"), + Platform=data.get("Platform"), + ImageManifestDescriptor=cls._parse_image_manifest(data.get("ImageManifestDescriptor", {})) + if data.get("ImageManifestDescriptor") + else None, + MountLabel=data.get("MountLabel"), + ProcessLabel=data.get("ProcessLabel"), + AppArmorProfile=data.get("AppArmorProfile"), + ExecIDs=data.get("ExecIDs"), + HostConfig=_ignore_properties(ContainerHostConfig, data.get("HostConfig", {})) + if data.get("HostConfig") + else None, + GraphDriver=_ignore_properties(ContainerGraphDriver, data.get("GraphDriver", {})) + if data.get("GraphDriver") + else None, + SizeRw=data.get("SizeRw"), + SizeRootFs=data.get("SizeRootFs"), + Mounts=[_ignore_properties(ContainerMount, mount) for mount in data.get("Mounts", [])], + Config=_ignore_properties(ContainerConfig, data.get("Config", {})) if data.get("Config") else None, + NetworkSettings=_ignore_properties(ContainerNetworkSettings, data.get("NetworkSettings", {})) + if data.get("NetworkSettings") + else None, + ) + + @classmethod + def _parse_state(cls, data: dict[str, Any]) -> Optional[ContainerState]: + """Parse State with nested Health object.""" + if not data: + return None + + health_data = data.get("Health", {}) + health = None + if health_data: + logs = [_ignore_properties(ContainerLog, log) for log in health_data.get("Log", [])] + health = ContainerHealth( + Status=health_data.get("Status"), + FailingStreak=health_data.get("FailingStreak"), + Log=logs if logs else None, + ) + + return ContainerState( + Status=data.get("Status"), + Running=data.get("Running"), + Paused=data.get("Paused"), + Restarting=data.get("Restarting"), + OOMKilled=data.get("OOMKilled"), + Dead=data.get("Dead"), + Pid=data.get("Pid"), + ExitCode=data.get("ExitCode"), + Error=data.get("Error"), + StartedAt=data.get("StartedAt"), + FinishedAt=data.get("FinishedAt"), + Health=health, + ) + + @classmethod + def _parse_image_manifest(cls, data: dict[str, Any]) -> Optional[ContainerImageManifestDescriptor]: + """Parse ImageManifestDescriptor with nested Platform.""" + if not data: + return None + + platform_data = data.get("platform", {}) + platform = _ignore_properties(ContainerPlatform, platform_data) if platform_data else None + + return ContainerImageManifestDescriptor( + mediaType=data.get("mediaType"), + digest=data.get("digest"), + size=data.get("size"), + urls=data.get("urls"), + annotations=data.get("annotations"), + data=data.get("data"), + platform=platform, + artifactType=data.get("artifactType"), + ) + + @classmethod + def _parse_host_config(cls, data: dict[str, Any]) -> Optional[ContainerHostConfig]: + """Parse HostConfig with all nested objects.""" + if not data: + return None + return _ignore_properties(ContainerHostConfig, data) + + @classmethod + def _parse_network_settings(cls, data: dict[str, Any]) -> Optional[ContainerNetworkSettings]: + """Parse NetworkSettings with nested Networks and Ports.""" + if not data: + return None + return _ignore_properties(ContainerNetworkSettings, data) + + def get_network_settings(self) -> Optional[ContainerNetworkSettings]: + """Get network settings for the container.""" + return self.NetworkSettings diff --git a/core/testcontainers/core/labels.py b/src/testcontainers/core/labels.py similarity index 73% rename from core/testcontainers/core/labels.py rename to src/testcontainers/core/labels.py index 0570b22cb..1c45b79cf 100644 --- a/core/testcontainers/core/labels.py +++ b/src/testcontainers/core/labels.py @@ -21,12 +21,15 @@ def create_labels(image: str, labels: Optional[dict[str, str]]) -> dict[str, str if k.startswith(TESTCONTAINERS_NAMESPACE): raise ValueError("The org.testcontainers namespace is reserved for internal use") - labels[LABEL_LANG] = "python" - labels[LABEL_TESTCONTAINERS] = "true" - labels[LABEL_VERSION] = importlib.metadata.version("testcontainers") + tc_labels = { + **labels, + LABEL_LANG: "python", + LABEL_TESTCONTAINERS: "true", + LABEL_VERSION: importlib.metadata.version("testcontainers"), + } if image == c.ryuk_image: - return labels + return tc_labels - labels[LABEL_SESSION_ID] = SESSION_ID - return labels + tc_labels[LABEL_SESSION_ID] = SESSION_ID + return tc_labels diff --git a/core/testcontainers/core/network.py b/src/testcontainers/core/network.py similarity index 51% rename from core/testcontainers/core/network.py rename to src/testcontainers/core/network.py index d149d5e48..f6bf166ec 100644 --- a/core/testcontainers/core/network.py +++ b/src/testcontainers/core/network.py @@ -11,34 +11,57 @@ # License for the specific language governing permissions and limitations # under the License. import uuid -from typing import Optional +from types import TracebackType +from typing import TYPE_CHECKING, Any, Optional + +from typing_extensions import Self from testcontainers.core.docker_client import DockerClient +if TYPE_CHECKING: + from docker.models.networks import Network as DockerNetwork + class Network: """ Network context manager for programmatically connecting containers. """ - def __init__(self, docker_client_kw: Optional[dict] = None, docker_network_kw: Optional[dict] = None) -> None: + def __init__( + self, docker_client_kw: Optional[dict[str, Any]] = None, docker_network_kw: Optional[dict[str, Any]] = None + ) -> None: self.name = str(uuid.uuid4()) self._docker = DockerClient(**(docker_client_kw or {})) self._docker_network_kw = docker_network_kw or {} + self._network: Optional[DockerNetwork] = None + + @property + def _unwrap_network(self) -> "DockerNetwork": + s_n = self._network + assert s_n is not None + return s_n + + @property + def id(self) -> Optional[str]: + network_id = self._unwrap_network.id + if isinstance(network_id, str): + return network_id + return None - def connect(self, container_id: str, network_aliases: Optional[list] = None): - self._network.connect(container_id, aliases=network_aliases) + def connect(self, container_id: str, network_aliases: Optional[list[str]] = None) -> None: + self._unwrap_network.connect(container_id, aliases=network_aliases) def remove(self) -> None: - self._network.remove() + self._unwrap_network.remove() - def create(self) -> "Network": + def create(self) -> Self: self._network = self._docker.client_networks_create(self.name, self._docker_network_kw) - self.id = self._network.id return self - def __enter__(self) -> "Network": + def __enter__(self) -> Self: return self.create() - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__( + self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] + ) -> None: self.remove() diff --git a/src/testcontainers/core/transferable.py b/src/testcontainers/core/transferable.py new file mode 100644 index 000000000..1f90f8e07 --- /dev/null +++ b/src/testcontainers/core/transferable.py @@ -0,0 +1,33 @@ +import io +import pathlib +import tarfile +from typing import Union + +Transferable = Union[bytes, pathlib.Path] + +TransferSpec = Union[tuple[Transferable, str], tuple[Transferable, str, int]] + + +def build_transfer_tar(transferable: Transferable, destination: str, mode: int = 0o644) -> bytes: + """Build a tar archive containing the transferable, ready for put_archive(path="/").""" + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + if isinstance(transferable, bytes): + info = tarfile.TarInfo(name=destination) + info.size = len(transferable) + info.mode = mode + tar.addfile(info, io.BytesIO(transferable)) + elif isinstance(transferable, pathlib.Path): + if transferable.is_file(): + info = tarfile.TarInfo(name=destination) + info.size = transferable.stat().st_size + info.mode = mode + with transferable.open("rb") as f: + tar.addfile(info, f) + elif transferable.is_dir(): + tar.add(str(transferable), arcname=f"{destination.rstrip('/')}/{transferable.name}") + else: + raise TypeError(f"Path {transferable} is neither a file nor directory") + else: + raise TypeError("source must be bytes or Path") + return buf.getvalue() diff --git a/core/testcontainers/core/utils.py b/src/testcontainers/core/utils.py similarity index 94% rename from core/testcontainers/core/utils.py rename to src/testcontainers/core/utils.py index 6c6136142..438cf2cd7 100644 --- a/core/testcontainers/core/utils.py +++ b/src/testcontainers/core/utils.py @@ -13,10 +13,10 @@ def setup_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) - logger.setLevel(logging.INFO) - handler = logging.StreamHandler() - handler.setLevel(logging.INFO) - logger.addHandler(handler) + # logger.setLevel(logging.INFO) + # handler = logging.StreamHandler() + # handler.setLevel(logging.INFO) + # logger.addHandler(handler) return logger diff --git a/src/testcontainers/core/version.py b/src/testcontainers/core/version.py new file mode 100644 index 000000000..71e17ec39 --- /dev/null +++ b/src/testcontainers/core/version.py @@ -0,0 +1,32 @@ +from typing import Callable + +from packaging.version import Version + + +class ComparableVersion: + """A wrapper around packaging.version.Version that allows for comparison with strings""" + + def __init__(self, version: str) -> None: + self.version = Version(version) + + def __lt__(self, other: object) -> bool: + return self._apply_op(other, lambda x, y: x < y) + + def __le__(self, other: object) -> bool: + return self._apply_op(other, lambda x, y: x <= y) + + def __eq__(self, other: object) -> bool: + return self._apply_op(other, lambda x, y: x == y) + + def __ne__(self, other: object) -> bool: + return self._apply_op(other, lambda x, y: x != y) + + def __gt__(self, other: object) -> bool: + return self._apply_op(other, lambda x, y: x > y) + + def __ge__(self, other: object) -> bool: + return self._apply_op(other, lambda x, y: x >= y) + + def _apply_op(self, other: object, op: Callable[[Version, Version], bool]) -> bool: + other = Version(str(other)) + return op(self.version, other) diff --git a/src/testcontainers/core/wait_strategies.py b/src/testcontainers/core/wait_strategies.py new file mode 100644 index 000000000..ac83ffa29 --- /dev/null +++ b/src/testcontainers/core/wait_strategies.py @@ -0,0 +1,890 @@ +""" +Structured wait strategies for containers. + +- LogMessageWaitStrategy: Wait for specific log messages +- HttpWaitStrategy: Wait for HTTP endpoints to be available +- HealthcheckWaitStrategy: Wait for Docker health checks to pass +- PortWaitStrategy: Wait for TCP ports to be available +- FileExistsWaitStrategy: Wait for files to exist on the filesystem +- ExecWaitStrategy: Wait for command execution inside container to succeed +- CompositeWaitStrategy: Combine multiple wait strategies + +Example: + Basic usage with containers: + + from testcontainers.core.wait_strategies import HttpWaitStrategy, LogMessageWaitStrategy + + # Wait for HTTP endpoint + container.waiting_for(HttpWaitStrategy(8080).for_status_code(200)) + + # Wait for log message + container.waiting_for(LogMessageWaitStrategy("Server started")) + + # Wait for command execution + container.waiting_for(ExecWaitStrategy(["pg_isready", "-U", "postgres"])) + + # Combine multiple strategies + container.waiting_for(CompositeWaitStrategy( + LogMessageWaitStrategy("Database ready"), + HttpWaitStrategy(8080) + )) +""" + +import re +import socket +import time +from datetime import timedelta +from pathlib import Path +from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +from typing_extensions import Self + +from testcontainers.compose import DockerCompose +from testcontainers.core.utils import setup_logger + +# Import base classes from waiting_utils to make them available for tests +from testcontainers.core.waiting_utils import WaitStrategy, WaitStrategyTarget + +if TYPE_CHECKING: + from testcontainers.core.container import DockerContainer + +logger = setup_logger(__name__) + + +class LogMessageWaitStrategy(WaitStrategy): + """ + Wait for a specific message to appear in the container logs. + + This strategy monitors the container's stdout and stderr streams for a specific + message or regex pattern. It can be configured to wait for the message to appear + multiple times or to require the message in both streams. + + Raises error if container exits before message is found. + + Args: + message: The message or regex pattern to search for in the logs + times: Number of times the message must appear (default: 1) + predicate_streams_and: If True, message must appear in both stdout and stderr (default: False) + + Example: + # Wait for a simple message + strategy = LogMessageWaitStrategy("ready for start") + + # Wait for a regex pattern + strategy = LogMessageWaitStrategy(re.compile(r"database.*ready")) + + # Wait for message in both streams + strategy = LogMessageWaitStrategy("ready", predicate_streams_and=True) + """ + + def __init__( + self, message: Union[str, re.Pattern[str]], times: int = 1, predicate_streams_and: bool = False + ) -> None: + super().__init__() + self._message = message if isinstance(message, re.Pattern) else re.compile(message, re.MULTILINE) + self._times = times + self._predicate_streams_and = predicate_streams_and + + def wait_until_ready(self, container: "WaitStrategyTarget") -> None: + """ + Wait until the specified message appears in the container logs. + + Args: + container: The container to monitor + + Raises: + TimeoutError: If the message doesn't appear within the timeout period + RuntimeError: If the container exits before the message appears + """ + from .waiting_utils import _NOT_EXITED_STATUSES, _get_container_logs_for_debugging, _get_container_status_info + + # Implement our own wait logic to avoid recursive calls to wait_for_logs + wrapped = container.get_wrapped_container() + start_time = time.time() + + while True: + duration = time.time() - start_time + if duration > self._startup_timeout: + # Get current logs and status for debugging + stdout_str, stderr_str = _get_container_logs_for_debugging(container) + status_info = _get_container_status_info(container) + + message_pattern = self._message.pattern if hasattr(self._message, "pattern") else str(self._message) + + raise TimeoutError( + f"Container did not emit logs containing '{message_pattern}' within {self._startup_timeout:.3f} seconds. " + f"Container status: {status_info['status']}, health: {status_info['health_status']}. " + f"Recent stdout: {stdout_str}. " + f"Recent stderr: {stderr_str}. " + f"Hint: Check if the container is starting correctly, the expected message is being logged, " + f"and the log pattern matches what the application actually outputs." + ) + + stdout_bytes, stderr_bytes = container.get_logs() + stdout = stdout_bytes.decode() + stderr = stderr_bytes.decode() + + predicate_result = ( + self._message.search(stdout) or self._message.search(stderr) + if self._predicate_streams_and is False + else self._message.search(stdout) and self._message.search(stderr) + ) + + if predicate_result: + return + + # Check if container has exited + wrapped.reload() + if wrapped.status not in _NOT_EXITED_STATUSES: + # Get exit information for better debugging + status_info = _get_container_status_info(container) + + raise RuntimeError( + f"Container exited (status: {status_info['status']}, exit code: {status_info['exit_code']}) " + f"before emitting logs containing '{self._message.pattern if hasattr(self._message, 'pattern') else str(self._message)}'. " + f"Container error: {status_info['error']}. " + f"Hint: Check container logs and ensure the application is configured to start correctly. " + f"The application may be crashing or exiting early." + ) + + time.sleep(self._poll_interval) + + +class HttpWaitStrategy(WaitStrategy): + """ + Wait for an HTTP endpoint to be available and return expected status code(s). + + This strategy makes HTTP requests to a specified endpoint and waits for it to + return an acceptable status code. It supports various HTTP methods, headers, + authentication, and custom response validation. + + Args: + port: The port number to connect to + path: The HTTP path to request (default: "/") + + Example: + # Basic HTTP check + strategy = HttpWaitStrategy(8080).for_status_code(200) + + # HTTPS with custom path + strategy = HttpWaitStrategy(443, "/health").using_tls().for_status_code(200) + + # Custom validation + strategy = HttpWaitStrategy(8080).for_response_predicate(lambda body: "ready" in body) + + # Create from URL + strategy = HttpWaitStrategy.from_url("https://localhost:8080/api/health") + """ + + def __init__(self, port: int, path: Optional[str] = "/") -> None: + super().__init__() + self._port = port + self._path = "/" if path is None else (path if path.startswith("/") else f"/{path}") + self._status_codes: set[int] = {200} + self._status_code_predicate: Optional[Callable[[int], bool]] = None + self._tls = False + self._headers: dict[str, str] = {} + self._basic_auth: Optional[tuple[str, str]] = None + self._response_predicate: Optional[Callable[[str], bool]] = None + self._method = "GET" + self._body: Optional[str] = None + self._insecure_tls = False + + @classmethod + def from_url(cls, url: str) -> Self: + """ + Create an HttpWaitStrategy from a URL string. + + Args: + url: The URL to wait for (e.g., "http://localhost:8080/api/health") + + Returns: + An HttpWaitStrategy configured for the given URL + + Example: + strategy = HttpWaitStrategy.from_url("https://localhost:8080/api/health") + """ + from urllib.parse import urlparse + + parsed = urlparse(url) + port = parsed.port or (443 if parsed.scheme == "https" else 80) + path = parsed.path or "/" + + strategy = cls(port, path) + + if parsed.scheme == "https": + strategy.using_tls() + + return strategy + + def for_status_code(self, code: int) -> Self: + """ + Add an acceptable status code. + + Args: + code: HTTP status code to accept + + Returns: + self for method chaining + """ + self._status_codes.add(code) + return self + + def for_status_code_matching(self, predicate: Callable[[int], bool]) -> Self: + """ + Set a predicate to match status codes against. + + Args: + predicate: Function that takes a status code and returns True if acceptable + + Returns: + self for method chaining + """ + self._status_code_predicate = predicate + return self + + def for_response_predicate(self, predicate: Callable[[str], bool]) -> Self: + """ + Set a predicate to match response body against. + + Args: + predicate: Function that takes response body and returns True if acceptable + + Returns: + self for method chaining + """ + self._response_predicate = predicate + return self + + def using_tls(self, insecure: bool = False) -> Self: + """ + Use HTTPS instead of HTTP. + + Args: + insecure: If True, skip SSL certificate verification + + Returns: + self for method chaining + """ + self._tls = True + self._insecure_tls = insecure + return self + + def with_header(self, name: str, value: str) -> Self: + """ + Add a header to the request. + + Args: + name: Header name + value: Header value + + Returns: + self for method chaining + """ + self._headers[name] = value + return self + + def with_basic_credentials(self, username: str, password: str) -> Self: + """ + Add basic auth credentials. + + Args: + username: Basic auth username + password: Basic auth password + + Returns: + self for method chaining + """ + self._basic_auth = (username, password) + return self + + def with_method(self, method: str) -> Self: + """ + Set the HTTP method to use. + + Args: + method: HTTP method (GET, POST, PUT, etc.) + + Returns: + self for method chaining + """ + self._method = method.upper() + return self + + def with_body(self, body: str) -> Self: + """ + Set the request body. + + Args: + body: Request body as string + + Returns: + self for method chaining + """ + self._body = body + return self + + def _setup_headers(self) -> dict[str, str]: + """Set up headers for the HTTP request.""" + import base64 + + headers = self._headers.copy() + if self._basic_auth: + auth = base64.b64encode(f"{self._basic_auth[0]}:{self._basic_auth[1]}".encode()).decode() + headers["Authorization"] = f"Basic {auth}" + return headers + + def _setup_ssl_context(self) -> Optional[Any]: + """Set up SSL context if needed.""" + import ssl + + if self._tls and self._insecure_tls: + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + return ssl_context + return None + + def _build_url(self, container: WaitStrategyTarget) -> str: + """Build the URL for the HTTP request.""" + protocol = "https" if self._tls else "http" + host = container.get_container_host_ip() + port = int(container.get_exposed_port(self._port)) + return f"{protocol}://{host}:{port}{self._path}" + + def _check_response(self, response: Any, url: str) -> bool: + """Check if the response is acceptable.""" + status_code = response.status + + # Check status code matches + if status_code in self._status_codes or ( + self._status_code_predicate and self._status_code_predicate(status_code) + ): + # Check response body if needed + if self._response_predicate is not None: + body = response.read().decode() + return self._response_predicate(body) + return True + else: + raise HTTPError(url, status_code, "Unexpected status code", response.headers, None) + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """ + Wait until the HTTP endpoint is ready and returns an acceptable response. + + Args: + container: The container to monitor + + Raises: + TimeoutError: If the endpoint doesn't become ready within the timeout period + """ + start_time = time.time() + headers = self._setup_headers() + ssl_context = self._setup_ssl_context() + url = self._build_url(container) + + while True: + if time.time() - start_time > self._startup_timeout: + self._raise_timeout_error(url) + + if self._try_http_request(url, headers, ssl_context): + return + + time.sleep(self._poll_interval) + + def _raise_timeout_error(self, url: str) -> None: + """Raise a timeout error with detailed information.""" + raise TimeoutError( + f"HTTP endpoint not ready within {self._startup_timeout} seconds. " + f"Endpoint: {url}. " + f"Method: {self._method}. " + f"Expected status codes: {self._status_codes}. " + f"Hint: Check if the service is listening on port {self._port}, " + f"the endpoint path is correct, and the service is configured to respond to {self._method} requests." + ) + + def _try_http_request(self, url: str, headers: dict[str, str], ssl_context: Any) -> bool: + """Try to make an HTTP request and return True if successful.""" + try: + request = Request( + url, + headers=headers, + method=self._method, + data=self._body.encode() if self._body else None, + ) + + with urlopen(request, timeout=1, context=ssl_context) as response: + return self._check_response(response, url) + + except (URLError, HTTPError) as e: + return self._handle_http_error(e) + except (ConnectionResetError, ConnectionRefusedError, BrokenPipeError, OSError) as e: + # Handle connection-level errors that can occur during HTTP requests + logger.debug(f"HTTP connection failed: {e!s}") + return False + + def _handle_http_error(self, error: Union[URLError, HTTPError]) -> bool: + """Handle HTTP errors and return True if error is acceptable.""" + if isinstance(error, HTTPError) and ( + error.code in self._status_codes + or (self._status_code_predicate and self._status_code_predicate(error.code)) + ): + return True + logger.debug(f"HTTP request failed: {error!s}") + return False + + +class HealthcheckWaitStrategy(WaitStrategy): + """ + Wait for the container's health check to report as healthy. + + This strategy monitors the container's Docker health check status and waits + for it to report as "healthy". It requires the container to have a health + check configured in its Dockerfile or container configuration. + + Example: + # Wait for container to be healthy + strategy = HealthcheckWaitStrategy() + + # With custom timeout + strategy = HealthcheckWaitStrategy().with_startup_timeout(60) + + Note: + The container must have a HEALTHCHECK instruction in its Dockerfile + or health check configured during container creation for this strategy + to work. If no health check is configured, this strategy will raise + a RuntimeError. + """ + + def __init__(self) -> None: + super().__init__() + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """ + Wait until the container's health check reports as healthy. + + Args: + container: The container to monitor + + Raises: + TimeoutError: If the health check doesn't report healthy within the timeout period + RuntimeError: If no health check is configured or if the health check reports unhealthy + """ + start_time = time.time() + wrapped = container.get_wrapped_container() + + while True: + if time.time() - start_time > self._startup_timeout: + wrapped.reload() # Refresh container state + health = wrapped.attrs.get("State", {}).get("Health", {}) + status = health.get("Status") if health else "no health check" + raise TimeoutError( + f"Container health check did not report healthy within {self._startup_timeout} seconds. " + f"Current status: {status}. " + f"Hint: Check if the health check command is working correctly, " + f"the application is starting properly, and the health check interval is appropriate." + ) + + wrapped.reload() # Refresh container state + health = wrapped.attrs.get("State", {}).get("Health", {}) + + # No health check configured + if not health: + raise RuntimeError( + "No health check configured for container. " + "Add HEALTHCHECK instruction to Dockerfile or configure health check in container creation. " + "Example: HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1" + ) + + status = health.get("Status") + + if status == "healthy": + return + elif status == "unhealthy": + # Get the last health check log for better error reporting + log = health.get("Log", []) + last_log = log[-1] if log else {} + exit_code = last_log.get("ExitCode", "unknown") + output = last_log.get("Output", "no output") + + raise RuntimeError( + f"Container health check reported unhealthy. " + f"Exit code: {exit_code}, " + f"Output: {output}. " + f"Hint: Check the health check command, ensure the application is responding correctly, " + f"and verify the health check endpoint or command is working as expected." + ) + + time.sleep(self._poll_interval) + + +class PortWaitStrategy(WaitStrategy): + """ + Wait for a port to be available on the container. + + This strategy attempts to establish a TCP connection to a specified port + on the container and waits until the connection succeeds. It's useful for + waiting for services that need to be listening on a specific port. + + Args: + port: The port number to check for availability + + Example: + # Wait for port 8080 to be available + strategy = PortWaitStrategy(8080) + + # Wait for database port with custom timeout + strategy = PortWaitStrategy(5432).with_startup_timeout(30) + """ + + def __init__(self, port: int) -> None: + super().__init__() + self._port = port + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """ + Wait until the specified port is available for connection. + + Args: + container: The container to monitor + + Raises: + TimeoutError: If the port doesn't become available within the timeout period + """ + start_time = time.time() + host = container.get_container_host_ip() + port = int(container.get_exposed_port(self._port)) + + while True: + if time.time() - start_time > self._startup_timeout: + raise TimeoutError( + f"Port {self._port} not available within {self._startup_timeout} seconds. " + f"Attempted connection to {host}:{port}. " + f"Hint: Check if the service is configured to listen on port {self._port}, " + f"the service is starting correctly, and there are no firewall or network issues." + ) + + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(1) + s.connect((host, port)) + return + except (socket.timeout, ConnectionRefusedError, OSError): + time.sleep(self._poll_interval) + + +class FileExistsWaitStrategy(WaitStrategy): + """ + Wait for a file to exist on the host filesystem. + + This strategy waits for a specific file to exist on the host filesystem, + typically used for waiting for files created by containers via volume mounts. + This is useful for scenarios like Docker-in-Docker where certificate files + need to be generated before they can be used. + + Args: + file_path: Path to the file to wait for (can be str or Path object) + + Example: + # Wait for a certificate file + strategy = FileExistsWaitStrategy("/tmp/certs/ca.pem") + + # Wait for a configuration file + from pathlib import Path + strategy = FileExistsWaitStrategy(Path("/tmp/config/app.conf")) + """ + + def __init__(self, file_path: Union[str, Path]) -> None: + super().__init__() + self._file_path = Path(file_path) + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """ + Wait until the specified file exists on the host filesystem. + + Args: + container: The container (used for timeout/polling configuration) + + Raises: + TimeoutError: If the file doesn't exist within the timeout period + """ + start_time = time.time() + + logger.debug( + f"FileExistsWaitStrategy: Waiting for file {self._file_path} with timeout {self._startup_timeout}s" + ) + + while True: + if time.time() - start_time > self._startup_timeout: + # Check what files actually exist in the directory + parent_dir = self._file_path.parent + existing_files = [] + if parent_dir.exists(): + existing_files = [str(f) for f in parent_dir.rglob("*") if f.is_file()] + + logger.error(f"FileExistsWaitStrategy: File {self._file_path} not found after timeout") + logger.debug(f"FileExistsWaitStrategy: Parent directory exists: {parent_dir.exists()}") + logger.debug(f"FileExistsWaitStrategy: Files in parent directory: {existing_files}") + + raise TimeoutError( + f"File {self._file_path} did not exist within {self._startup_timeout:.3f} seconds. " + f"Parent directory exists: {parent_dir.exists()}. " + f"Files in parent directory: {existing_files}. " + f"Hint: Check if the container is configured to create the file at the expected location, " + f"and verify that volume mounts are set up correctly." + ) + + if self._file_path.is_file(): + logger.debug( + f"FileExistsWaitStrategy: File {self._file_path} found after {time.time() - start_time:.2f}s" + ) + return + + logger.debug( + f"FileExistsWaitStrategy: Polling - file {self._file_path} not found yet, elapsed: {time.time() - start_time:.2f}s" + ) + time.sleep(self._poll_interval) + + +class ContainerStatusWaitStrategy(WaitStrategy): + """ + The possible values for the container status are: + created + running + paused + restarting + exited + removing + dead + https://docs.docker.com/reference/cli/docker/container/ls/#status + """ + + CONTINUE_STATUSES = frozenset(("created", "restarting")) + + def __init__(self) -> None: + super().__init__() + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + result = self._poll(lambda: self.running(self.get_status(container))) + if not result: + raise TimeoutError("container did not become running") + + @staticmethod + def running(status: str) -> bool: + if status == "running": + logger.debug("status is now running") + return True + if status in ContainerStatusWaitStrategy.CONTINUE_STATUSES: + logger.debug( + "status is %s, which is valid for continuing (%s)", + status, + ContainerStatusWaitStrategy.CONTINUE_STATUSES, + ) + return False + raise StopIteration(f"container status not valid for continuing: {status}") + + def get_status(self, container: Any) -> str: + from testcontainers.core.container import DockerContainer + + if isinstance(container, DockerContainer): + return self._get_status_tc_container(container) + if isinstance(container, DockerCompose): + return self._get_status_compose_container(container) + raise TypeError(f"not supported operation: 'get_status' for type: {type(container)}") + + @staticmethod + def _get_status_tc_container(container: "DockerContainer") -> str: + logger.debug("fetching status of container %s", container) + wrapped = container.get_wrapped_container() + wrapped.reload() + return wrapped.status + + @staticmethod + def _get_status_compose_container(container: DockerCompose) -> str: + logger.debug("fetching status of compose container %s", container) + raise NotImplementedError + + +class CompositeWaitStrategy(WaitStrategy): + """ + Wait for multiple conditions to be satisfied in sequence. + + This strategy allows combining multiple wait strategies that must all be satisfied. + Each strategy is executed in the order they were added, and all must succeed + for the composite strategy to be considered ready. + + Args: + strategies: Variable number of WaitStrategy objects to execute in sequence + + Example: + # Wait for log message AND file to exist + strategy = CompositeWaitStrategy( + LogMessageWaitStrategy("API listen on"), + FileExistsWaitStrategy("/tmp/certs/ca.pem") + ) + + # Wait for multiple conditions + strategy = CompositeWaitStrategy( + LogMessageWaitStrategy("Database ready"), + PortWaitStrategy(5432), + HttpWaitStrategy(8080, "/health").for_status_code(200) + ) + """ + + def __init__(self, *strategies: WaitStrategy) -> None: + super().__init__() + self._strategies = list(strategies) + + def with_poll_interval(self, interval: Union[float, timedelta]) -> Self: + super().with_poll_interval(interval) + for _strategy in self._strategies: + _strategy.with_poll_interval(interval) + return self + + def with_startup_timeout(self, timeout: Union[int, timedelta]) -> Self: + super().with_startup_timeout(timeout) + for _strategy in self._strategies: + _strategy.with_startup_timeout(timeout) + return self + + def with_transient_exceptions(self, *transient_exceptions: type[Exception]) -> Self: + super().with_transient_exceptions(*transient_exceptions) + for _strategy in self._strategies: + _strategy.with_transient_exceptions(*transient_exceptions) + return self + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """ + Wait until all contained strategies are ready. + + Args: + container: The container to monitor + + Raises: + TimeoutError: If any strategy doesn't become ready within the timeout period + """ + logger.debug(f"CompositeWaitStrategy: Starting execution of {len(self._strategies)} strategies") + + for i, strategy in enumerate(self._strategies): + try: + logger.debug( + f"CompositeWaitStrategy: Executing strategy {i + 1}/{len(self._strategies)}: {type(strategy).__name__}" + ) + strategy.wait_until_ready(container) + logger.debug(f"CompositeWaitStrategy: Strategy {i + 1}/{len(self._strategies)} completed successfully") + except TimeoutError as e: + logger.error(f"CompositeWaitStrategy: Strategy {i + 1}/{len(self._strategies)} failed: {e}") + raise TimeoutError( + f"Composite wait strategy failed at step {i + 1}/{len(self._strategies)}: {e}" + ) from e + + logger.debug("CompositeWaitStrategy: All strategies completed successfully") + + +class ExecWaitStrategy(WaitStrategy): + """ + Wait for a command execution inside the container to succeed. + + This strategy executes a command inside the container and waits for it to + return a successful exit code. It's useful for databases and services + that provide CLI tools to check readiness. + + Args: + command: Command to execute (list of strings or single string) + expected_exit_code: Expected exit code for success (default: 0) + + Example: + # Wait for Postgres readiness + strategy = ExecWaitStrategy( + ["sh", "-c", + "PGPASSWORD='password' psql -U user -d db -h 127.0.0.1 -c 'select 1;'"] + ) + + # Wait for Redis readiness + strategy = ExecWaitStrategy(["redis-cli", "ping"]) + + # Check for specific exit code + strategy = ExecWaitStrategy(["custom-healthcheck.sh"], expected_exit_code=0) + """ + + def __init__( + self, + command: Union[str, list[str]], + expected_exit_code: int = 0, + ) -> None: + super().__init__() + self._command = command if isinstance(command, list) else [command] + self._expected_exit_code = expected_exit_code + + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """ + Wait until command execution succeeds with the expected exit code. + + Args: + container: The container to execute commands in + + Raises: + TimeoutError: If the command doesn't succeed within the timeout period + RuntimeError: If the container doesn't support exec + """ + # Check if container supports exec (DockerContainer does, ComposeContainer doesn't) + if not hasattr(container, "exec"): + raise RuntimeError( + f"ExecWaitStrategy requires a container with exec support. " + f"Container type {type(container).__name__} does not support exec." + ) + + start_time = time.time() + last_exit_code = None + last_output = None + + while True: + duration = time.time() - start_time + if duration > self._startup_timeout: + command_str = " ".join(self._command) + raise TimeoutError( + f"Command execution did not succeed within {self._startup_timeout:.3f} seconds. " + f"Command: {command_str}. " + f"Expected exit code: {self._expected_exit_code}, " + f"last exit code: {last_exit_code}. " + f"Last output: {last_output}. " + f"Hint: Check if the service is starting correctly, the command is valid, " + f"and all required environment variables or credentials are properly configured." + ) + + try: + result = container.exec(self._command) + last_exit_code = result.exit_code + last_output = result.output.decode() if hasattr(result.output, "decode") else str(result.output) + + if result.exit_code == self._expected_exit_code: + logger.debug( + f"ExecWaitStrategy: Command succeeded with exit code {result.exit_code} after {duration:.2f}s" + ) + return + + logger.debug( + f"ExecWaitStrategy: Command failed with exit code {result.exit_code}, " + f"expected {self._expected_exit_code}. Retrying..." + ) + except Exception as e: + logger.debug(f"ExecWaitStrategy: Command execution failed with exception: {e}. Retrying...") + last_output = str(e) + + time.sleep(self._poll_interval) + + +__all__ = [ + "CompositeWaitStrategy", + "ContainerStatusWaitStrategy", + "ExecWaitStrategy", + "FileExistsWaitStrategy", + "HealthcheckWaitStrategy", + "HttpWaitStrategy", + "LogMessageWaitStrategy", + "PortWaitStrategy", + "WaitStrategy", + "WaitStrategyTarget", +] diff --git a/src/testcontainers/core/waiting_utils.py b/src/testcontainers/core/waiting_utils.py new file mode 100644 index 000000000..c6241bb48 --- /dev/null +++ b/src/testcontainers/core/waiting_utils.py @@ -0,0 +1,413 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import re +import time +import warnings +from abc import ABC, abstractmethod +from datetime import timedelta +from typing import Any, Callable, Optional, Protocol, TypeVar, Union, cast + +import wrapt +from typing_extensions import Self + +from testcontainers.core.config import testcontainers_config +from testcontainers.core.utils import setup_logger + +logger = setup_logger(__name__) + +# Get a tuple of transient exceptions for which we'll retry. Other exceptions will be raised. +TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionError) + +# Type variables for generic functions +F = TypeVar("F", bound=Callable[..., Any]) + + +class WaitStrategyTarget(Protocol): + """ + Protocol defining the interface that containers must implement for wait strategies. + This allows wait strategies to work with both DockerContainer and ComposeContainer + without requiring inheritance or type ignores. + Implementation requirement: + - DockerContainer: Implements this protocol (see core/tests/test_protocol_compliance.py) + - ComposeContainer: Implements this protocol (see core/tests/test_protocol_compliance.py) + """ + + def get_container_host_ip(self) -> str: + """Get the host IP address for the container.""" + ... + + def get_exposed_port(self, port: int) -> int: + """Get the exposed port mapping for the given internal port.""" + ... + + def get_wrapped_container(self) -> Any: + """Get the underlying container object.""" + ... + + def get_logs(self) -> tuple[bytes, bytes]: + """Get container logs as (stdout, stderr) tuple.""" + ... + + def reload(self) -> None: + """Reload container information.""" + ... + + @property + def status(self) -> str: + """Get container status.""" + ... + + +class WaitStrategy(ABC): + """Base class for all wait strategies.""" + + def __init__(self) -> None: + self._startup_timeout: float = testcontainers_config.timeout + self._poll_interval: float = testcontainers_config.sleep_time + self._transient_exceptions: list[type[Exception]] = [*TRANSIENT_EXCEPTIONS] + + def with_startup_timeout(self, timeout: Union[int, timedelta]) -> Self: + """Set the maximum time to wait for the container to be ready.""" + if isinstance(timeout, timedelta): + self._startup_timeout = float(int(timeout.total_seconds())) + else: + self._startup_timeout = float(timeout) + return self + + def with_poll_interval(self, interval: Union[float, timedelta]) -> Self: + """Set how frequently to check if the container is ready.""" + if isinstance(interval, timedelta): + self._poll_interval = interval.total_seconds() + else: + self._poll_interval = interval + return self + + def with_transient_exceptions(self, *transient_exceptions: type[Exception]) -> Self: + self._transient_exceptions.extend(transient_exceptions) + return self + + @abstractmethod + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + """Wait until the container is ready.""" + pass + + def _poll(self, check: Callable[[], bool], transient_exceptions: Optional[list[type[Exception]]] = None) -> bool: + if not transient_exceptions: + all_te_types = self._transient_exceptions + else: + all_te_types = [*self._transient_exceptions, *(transient_exceptions or [])] + + start = time.time() + while True: + start_attempt = time.time() + duration = start_attempt - start + if duration > self._startup_timeout: + return False + + # noinspection PyBroadException + try: + result = check() + if result: + return result + except StopIteration: + return False + except Exception as e: # noqa: E722, RUF100 + is_transient = False + for et in all_te_types: + if isinstance(e, et): + is_transient = True + if not is_transient: + raise RuntimeError(f"exception while checking for strategy {self}") from e + + seconds_left_until_next = self._poll_interval - (time.time() - start_attempt) + time.sleep(max(0.0, seconds_left_until_next)) + + +# Keep existing wait_container_is_ready but make it use the new system internally +def wait_container_is_ready(*transient_exceptions: type[Exception]) -> Callable[[F], F]: # noqa: C901 + """ + Legacy wait decorator that uses the new wait strategy system internally. + Maintains backwards compatibility with existing code. + This decorator can be used to wait for a function to succeed without raising + transient exceptions. It's useful for simple wait scenarios, but for more + complex cases, consider using structured wait strategies directly. + Example: + @wait_container_is_ready(HTTPError, URLError) + def check_http(container): + with urlopen("http://localhost:8080") as response: + return response.status == 200 + # For more complex scenarios, use structured wait strategies: + container.waiting_for(HttpWaitStrategy(8080).for_status_code(200)) + """ + warnings.warn( + "The @wait_container_is_ready decorator is deprecated and will be removed in a future version. " + "Use structured wait strategies instead: " + "container.waiting_for(HttpWaitStrategy(8080).for_status_code(200)) or " + "container.waiting_for(LogMessageWaitStrategy('ready'))", + DeprecationWarning, + stacklevel=2, + ) + + class LegacyWaitStrategy(WaitStrategy): + def __init__(self, func: Callable[..., Any], instance: Any, args: tuple[Any], kwargs: dict[str, Any]) -> None: + super().__init__() + self.func = func + self.instance = instance + self.args = args + self.kwargs = kwargs + self.transient_exceptions: tuple[type[Exception], ...] = TRANSIENT_EXCEPTIONS + tuple(transient_exceptions) + + def wait_until_ready(self, container: WaitStrategyTarget) -> Any: + start_time = time.time() + while True: + try: + # Handle different function call patterns: + # 1. Standalone functions (like wait_for): call with just args/kwargs + # 2. Methods: call with instance as first argument + if self.instance is None: + # Standalone function case + result = self.func(*self.args, **self.kwargs) + elif self.instance is container: + # Staticmethod case: self.instance is the container + result = self.func(*self.args, **self.kwargs) + else: + # Method case: self.instance is the instance (self) + result = self.func(self.instance, *self.args, **self.kwargs) + return result + except self.transient_exceptions as e: + if time.time() - start_time > self._startup_timeout: + raise TimeoutError( + f"Wait time ({self._startup_timeout}s) exceeded for {self.func.__name__}" + f"(args: {self.args}, kwargs: {self.kwargs}). Exception: {e}. " + f"Hint: Check if the container is ready, the function parameters are correct, " + f"and the expected conditions are met for the function to succeed." + ) from e + logger.debug(f"Connection attempt failed: {e!s}") + time.sleep(self._poll_interval) + + @wrapt.decorator + def wrapper(wrapped: Callable[..., Any], instance: Any, args: tuple[Any], kwargs: dict[str, Any]) -> Any: + # Use the LegacyWaitStrategy to handle retries with proper timeout + strategy = LegacyWaitStrategy(wrapped, instance, args, kwargs) + # For backwards compatibility, assume the instance is the container + try: + first_arg = args[0] + except IndexError: + first_arg = None + container = instance if hasattr(instance, "get_container_host_ip") else first_arg + if container: + return strategy.wait_until_ready(container) + else: + # Fallback to direct call if we can't identify the container + return wrapped(*args, **kwargs) + + return cast("Callable[[F], F]", wrapper) + + +def wait_for(condition: Callable[..., bool]) -> bool: + warnings.warn( + "The wait_for function is deprecated and will be removed in a future version. " + "Use structured wait strategies instead: " + "container.waiting_for(LogMessageWaitStrategy('ready')) or " + "container.waiting_for(HttpWaitStrategy(8080).for_status_code(200))", + DeprecationWarning, + stacklevel=2, + ) + + class CallableWaitStrategy(WaitStrategy): + def wait_until_ready(self, container: WaitStrategyTarget) -> None: + pass # Required by ABC, but unused + + strategy = CallableWaitStrategy() + if not strategy._poll(condition): + raise TimeoutError(f"Condition not satisfied within {strategy._startup_timeout}s") + return True + + +_NOT_EXITED_STATUSES = {"running", "created"} + + +def wait_for_logs( + container: WaitStrategyTarget, + predicate: Union[Callable[[str], bool], str, WaitStrategy], + timeout: float = testcontainers_config.timeout, + interval: float = 1, + predicate_streams_and: bool = False, + raise_on_exit: bool = False, + # +) -> float: + """ + Enhanced version of wait_for_logs that supports both old and new interfaces. + + This function waits for container logs to satisfy a predicate. It supports + multiple input types for the predicate and maintains backwards compatibility + with existing code while adding support for the new WaitStrategy system. + + This is a convenience function that can be used for simple log-based waits. + For more complex scenarios, consider using structured wait strategies directly. + + Args: + container: The DockerContainer to monitor + predicate: The predicate to check against logs. Can be: + - A callable function that takes log text and returns bool + - A string that will be compiled to a regex pattern + - A WaitStrategy object + timeout: Maximum time to wait in seconds (default: config.timeout) + interval: How frequently to check in seconds (default: 1) + predicate_streams_and: If True, predicate must match both stdout and stderr (default: False) + raise_on_exit: If True, raise RuntimeError if container exits before predicate matches (default: False) + + Returns: + The time in seconds that was spent waiting + + Raises: + TimeoutError: If the predicate is not satisfied within the timeout period + RuntimeError: If raise_on_exit is True and container exits before predicate matches + + Example: + # Wait for a simple string + wait_for_logs(container, "ready for start") + + # Wait with custom predicate + wait_for_logs(container, lambda logs: "database" in logs and "ready" in logs) + + # Wait with WaitStrategy + strategy = LogMessageWaitStrategy("ready") + wait_for_logs(container, strategy) + + # For more complex scenarios, use structured wait strategies directly: + container.waiting_for(LogMessageWaitStrategy("ready")) + """ + if isinstance(predicate, WaitStrategy): + start = time.time() + predicate.with_startup_timeout(int(timeout)).with_poll_interval(interval) + predicate.wait_until_ready(container) + return time.time() - start + else: + # Only warn for legacy usage (string or callable predicates, not WaitStrategy objects) + warnings.warn( + "The wait_for_logs function with string or callable predicates is deprecated and will be removed in a future version. " + "Use structured wait strategies instead: " + "container.waiting_for(LogMessageWaitStrategy('ready')) or " + "container.waiting_for(LogMessageWaitStrategy(re.compile(r'pattern')))", + DeprecationWarning, + stacklevel=2, + ) + + # Original implementation for backwards compatibility + re_predicate: Optional[Callable[[str], Any]] = None + if timeout is None: + timeout = testcontainers_config.timeout + if isinstance(predicate, str): + re_predicate = re.compile(predicate, re.MULTILINE).search + elif callable(predicate): + # some modules like mysql sends the search directly to the predicate + re_predicate = predicate + else: + raise TypeError("Predicate must be a string or callable") + wrapped = container.get_wrapped_container() + start = time.time() + while True: + duration = time.time() - start + stdout_b, stderr_b = container.get_logs() + stdout = stdout_b.decode() + stderr = stderr_b.decode() + predicate_result = ( + re_predicate(stdout) or re_predicate(stderr) + if predicate_streams_and is False + else re_predicate(stdout) and re_predicate(stderr) + # + ) + if predicate_result: + return duration + if duration > timeout: + # Get current logs and status for debugging + stdout_str, stderr_str = _get_container_logs_for_debugging(container) + status_info = _get_container_status_info(container) + + raise TimeoutError( + f"Container did not emit logs satisfying predicate in {timeout:.3f} seconds. " + f"Container status: {status_info['status']}, health: {status_info['health_status']}. " + f"Recent stdout: {stdout_str}. " + f"Recent stderr: {stderr_str}. " + f"Hint: Check if the container is starting correctly and the expected log pattern is being generated. " + f"Verify the predicate function or pattern matches the actual log output." + ) + if raise_on_exit: + wrapped.reload() + if wrapped.status not in _NOT_EXITED_STATUSES: + raise RuntimeError("Container exited before emitting logs satisfying predicate") + time.sleep(interval) + + +def _get_container_logs_for_debugging(container: WaitStrategyTarget, max_length: int = 200) -> tuple[str, str]: + """ + Get container logs for debugging purposes. + Args: + container: The container to get logs from + max_length: Maximum length of log output to include in error messages + Returns: + Tuple of (stdout, stderr) as strings + """ + try: + stdout_bytes, stderr_bytes = container.get_logs() + stdout_str = stdout_bytes.decode() if stdout_bytes else "" + stderr_str = stderr_bytes.decode() if stderr_bytes else "" + + # Truncate if too long + if len(stdout_str) > max_length: + stdout_str = "..." + stdout_str[-max_length:] + if len(stderr_str) > max_length: + stderr_str = "..." + stderr_str[-max_length:] + return stdout_str, stderr_str + except Exception: + return "(failed to get logs)", "(failed to get logs)" + + +def _get_container_status_info(container: WaitStrategyTarget) -> dict[str, str]: + """ + Get container status information for debugging. + Args: + container: The container to get status from + Returns: + Dictionary with status information + """ + try: + wrapped = container.get_wrapped_container() + wrapped.reload() + + state = wrapped.attrs.get("State", {}) + return { + "status": wrapped.status, + "exit_code": str(state.get("ExitCode", "unknown")), + "error": state.get("Error", ""), + "health_status": state.get("Health", {}).get("Status", "no health check"), + } + except Exception: + return { + "status": "unknown", + "exit_code": "unknown", + "error": "failed to get status", + "health_status": "unknown", + } + + +__all__ = [ + "WaitStrategy", + "WaitStrategyTarget", + "wait_container_is_ready", + "wait_for", + "wait_for_logs", +] diff --git a/src/testcontainers/cosmosdb.py b/src/testcontainers/cosmosdb.py new file mode 100644 index 000000000..9625383a8 --- /dev/null +++ b/src/testcontainers/cosmosdb.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.cosmosdb import ( + CosmosDBMongoEndpointContainer, + CosmosDBNoSQLEndpointContainer, +) + +warnings.warn( + "testcontainers.cosmosdb is deprecated, use testcontainers.community.cosmosdb instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "CosmosDBMongoEndpointContainer", + "CosmosDBNoSQLEndpointContainer", +] diff --git a/src/testcontainers/cratedb.py b/src/testcontainers/cratedb.py new file mode 100644 index 000000000..eae33909c --- /dev/null +++ b/src/testcontainers/cratedb.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.cratedb import ( + CrateDBContainer, +) + +warnings.warn( + "testcontainers.cratedb is deprecated, use testcontainers.community.cratedb instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "CrateDBContainer", +] diff --git a/src/testcontainers/db2.py b/src/testcontainers/db2.py new file mode 100644 index 000000000..f7aae6057 --- /dev/null +++ b/src/testcontainers/db2.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.db2 import ( + Db2Container, +) + +warnings.warn( + "testcontainers.db2 is deprecated, use testcontainers.community.db2 instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "Db2Container", +] diff --git a/src/testcontainers/elasticsearch.py b/src/testcontainers/elasticsearch.py new file mode 100644 index 000000000..2cd217d52 --- /dev/null +++ b/src/testcontainers/elasticsearch.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.elasticsearch import ( + ElasticSearchContainer, +) + +warnings.warn( + "testcontainers.elasticsearch is deprecated, use testcontainers.community.elasticsearch instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ElasticSearchContainer", +] diff --git a/src/testcontainers/generic.py b/src/testcontainers/generic.py new file mode 100644 index 000000000..2016fa442 --- /dev/null +++ b/src/testcontainers/generic.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.generic import ( + ServerContainer, + SqlContainer, +) + +warnings.warn( + "testcontainers.generic is deprecated, use testcontainers.community.generic instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ServerContainer", + "SqlContainer", +] diff --git a/src/testcontainers/google.py b/src/testcontainers/google.py new file mode 100644 index 000000000..dfc177cc7 --- /dev/null +++ b/src/testcontainers/google.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.google import ( + DatastoreContainer, + PubSubContainer, +) + +warnings.warn( + "testcontainers.google is deprecated, use testcontainers.community.google instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "DatastoreContainer", + "PubSubContainer", +] diff --git a/src/testcontainers/influxdb.py b/src/testcontainers/influxdb.py new file mode 100644 index 000000000..84abe4ae8 --- /dev/null +++ b/src/testcontainers/influxdb.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.influxdb import ( + InfluxDbContainer, +) + +warnings.warn( + "testcontainers.influxdb is deprecated, use testcontainers.community.influxdb instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "InfluxDbContainer", +] diff --git a/src/testcontainers/influxdb1.py b/src/testcontainers/influxdb1.py new file mode 100644 index 000000000..aa84ba58a --- /dev/null +++ b/src/testcontainers/influxdb1.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.influxdb import ( + InfluxDb1Container, +) + +warnings.warn( + "testcontainers.influxdb1 is deprecated, use testcontainers.community.influxdb1 instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "InfluxDb1Container", +] diff --git a/src/testcontainers/influxdb2.py b/src/testcontainers/influxdb2.py new file mode 100644 index 000000000..293da1fa3 --- /dev/null +++ b/src/testcontainers/influxdb2.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.influxdb import ( + InfluxDb2Container, +) + +warnings.warn( + "testcontainers.influxdb2 is deprecated, use testcontainers.community.influxdb2 instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "InfluxDb2Container", +] diff --git a/src/testcontainers/k3s.py b/src/testcontainers/k3s.py new file mode 100644 index 000000000..feebb0caf --- /dev/null +++ b/src/testcontainers/k3s.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.k3s import ( + K3SContainer, +) + +warnings.warn( + "testcontainers.k3s is deprecated, use testcontainers.community.k3s instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "K3SContainer", +] diff --git a/src/testcontainers/kafka.py b/src/testcontainers/kafka.py new file mode 100644 index 000000000..a79d501db --- /dev/null +++ b/src/testcontainers/kafka.py @@ -0,0 +1,19 @@ +import warnings + +from testcontainers.community.kafka import ( + KafkaContainer, + RedpandaContainer, + kafka_config, +) + +warnings.warn( + "testcontainers.kafka is deprecated, use testcontainers.community.kafka instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "KafkaContainer", + "RedpandaContainer", + "kafka_config", +] diff --git a/src/testcontainers/keycloak.py b/src/testcontainers/keycloak.py new file mode 100644 index 000000000..25f9c7c11 --- /dev/null +++ b/src/testcontainers/keycloak.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.keycloak import ( + KeycloakContainer, +) + +warnings.warn( + "testcontainers.keycloak is deprecated, use testcontainers.community.keycloak instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "KeycloakContainer", +] diff --git a/src/testcontainers/localstack.py b/src/testcontainers/localstack.py new file mode 100644 index 000000000..2dc549efb --- /dev/null +++ b/src/testcontainers/localstack.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.localstack import ( + LocalStackContainer, +) + +warnings.warn( + "testcontainers.localstack is deprecated, use testcontainers.community.localstack instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "LocalStackContainer", +] diff --git a/src/testcontainers/mailpit.py b/src/testcontainers/mailpit.py new file mode 100644 index 000000000..73463531e --- /dev/null +++ b/src/testcontainers/mailpit.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.mailpit import ( + MailpitContainer, + MailpitUser, +) + +warnings.warn( + "testcontainers.mailpit is deprecated, use testcontainers.community.mailpit instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MailpitContainer", + "MailpitUser", +] diff --git a/src/testcontainers/memcached.py b/src/testcontainers/memcached.py new file mode 100644 index 000000000..819448150 --- /dev/null +++ b/src/testcontainers/memcached.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.memcached import ( + MemcachedContainer, + MemcachedNotReady, +) + +warnings.warn( + "testcontainers.memcached is deprecated, use testcontainers.community.memcached instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MemcachedContainer", + "MemcachedNotReady", +] diff --git a/src/testcontainers/milvus.py b/src/testcontainers/milvus.py new file mode 100644 index 000000000..edf0e4963 --- /dev/null +++ b/src/testcontainers/milvus.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.milvus import ( + MilvusContainer, +) + +warnings.warn( + "testcontainers.milvus is deprecated, use testcontainers.community.milvus instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MilvusContainer", +] diff --git a/src/testcontainers/minio.py b/src/testcontainers/minio.py new file mode 100644 index 000000000..e25e997af --- /dev/null +++ b/src/testcontainers/minio.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.minio import ( + MinioContainer, +) + +warnings.warn( + "testcontainers.minio is deprecated, use testcontainers.community.minio instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MinioContainer", +] diff --git a/src/testcontainers/mongodb.py b/src/testcontainers/mongodb.py new file mode 100644 index 000000000..498eca702 --- /dev/null +++ b/src/testcontainers/mongodb.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.mongodb import ( + MongoDBAtlasLocalContainer, + MongoDbContainer, +) + +warnings.warn( + "testcontainers.mongodb is deprecated, use testcontainers.community.mongodb instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MongoDBAtlasLocalContainer", + "MongoDbContainer", +] diff --git a/src/testcontainers/mqtt.py b/src/testcontainers/mqtt.py new file mode 100644 index 000000000..2bbb5e214 --- /dev/null +++ b/src/testcontainers/mqtt.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.mqtt import ( + MosquittoContainer, +) + +warnings.warn( + "testcontainers.mqtt is deprecated, use testcontainers.community.mqtt instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MosquittoContainer", +] diff --git a/src/testcontainers/mssql.py b/src/testcontainers/mssql.py new file mode 100644 index 000000000..102f021bd --- /dev/null +++ b/src/testcontainers/mssql.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.mssql import ( + SqlServerContainer, +) + +warnings.warn( + "testcontainers.mssql is deprecated, use testcontainers.community.mssql instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "SqlServerContainer", +] diff --git a/src/testcontainers/mysql.py b/src/testcontainers/mysql.py new file mode 100644 index 000000000..7258e0573 --- /dev/null +++ b/src/testcontainers/mysql.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.mysql import ( + MySqlContainer, +) + +warnings.warn( + "testcontainers.mysql is deprecated, use testcontainers.community.mysql instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "MySqlContainer", +] diff --git a/src/testcontainers/nats.py b/src/testcontainers/nats.py new file mode 100644 index 000000000..d48827692 --- /dev/null +++ b/src/testcontainers/nats.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.nats import ( + NatsContainer, +) + +warnings.warn( + "testcontainers.nats is deprecated, use testcontainers.community.nats instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "NatsContainer", +] diff --git a/src/testcontainers/neo4j.py b/src/testcontainers/neo4j.py new file mode 100644 index 000000000..6d27e58a0 --- /dev/null +++ b/src/testcontainers/neo4j.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.neo4j import ( + Neo4jContainer, +) + +warnings.warn( + "testcontainers.neo4j is deprecated, use testcontainers.community.neo4j instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "Neo4jContainer", +] diff --git a/src/testcontainers/nginx.py b/src/testcontainers/nginx.py new file mode 100644 index 000000000..d433c9e0d --- /dev/null +++ b/src/testcontainers/nginx.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.nginx import ( + NginxContainer, +) + +warnings.warn( + "testcontainers.nginx is deprecated, use testcontainers.community.nginx instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "NginxContainer", +] diff --git a/src/testcontainers/ollama.py b/src/testcontainers/ollama.py new file mode 100644 index 000000000..aba69f731 --- /dev/null +++ b/src/testcontainers/ollama.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.ollama import ( + OllamaContainer, + OllamaModel, +) + +warnings.warn( + "testcontainers.ollama is deprecated, use testcontainers.community.ollama instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "OllamaContainer", + "OllamaModel", +] diff --git a/src/testcontainers/openfga.py b/src/testcontainers/openfga.py new file mode 100644 index 000000000..102f0a8e7 --- /dev/null +++ b/src/testcontainers/openfga.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.openfga import ( + OpenFGAContainer, +) + +warnings.warn( + "testcontainers.openfga is deprecated, use testcontainers.community.openfga instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "OpenFGAContainer", +] diff --git a/src/testcontainers/opensearch.py b/src/testcontainers/opensearch.py new file mode 100644 index 000000000..df3579cc6 --- /dev/null +++ b/src/testcontainers/opensearch.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.opensearch import ( + OpenSearchContainer, +) + +warnings.warn( + "testcontainers.opensearch is deprecated, use testcontainers.community.opensearch instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "OpenSearchContainer", +] diff --git a/src/testcontainers/oracle.py b/src/testcontainers/oracle.py new file mode 100644 index 000000000..0c2d238c5 --- /dev/null +++ b/src/testcontainers/oracle.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.oracle import ( + OracleDbContainer, +) + +warnings.warn( + "testcontainers.oracle is deprecated, use testcontainers.community.oracle instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "OracleDbContainer", +] diff --git a/src/testcontainers/postgres.py b/src/testcontainers/postgres.py new file mode 100644 index 000000000..f8603526d --- /dev/null +++ b/src/testcontainers/postgres.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.postgres import ( + PostgresContainer, +) + +warnings.warn( + "testcontainers.postgres is deprecated, use testcontainers.community.postgres instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "PostgresContainer", +] diff --git a/modules/mailpit/testcontainers/mailpit/py.typed b/src/testcontainers/py.typed similarity index 100% rename from modules/mailpit/testcontainers/mailpit/py.typed rename to src/testcontainers/py.typed diff --git a/src/testcontainers/qdrant.py b/src/testcontainers/qdrant.py new file mode 100644 index 000000000..531ad99cb --- /dev/null +++ b/src/testcontainers/qdrant.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.qdrant import ( + QdrantContainer, +) + +warnings.warn( + "testcontainers.qdrant is deprecated, use testcontainers.community.qdrant instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "QdrantContainer", +] diff --git a/src/testcontainers/rabbitmq.py b/src/testcontainers/rabbitmq.py new file mode 100644 index 000000000..053929b29 --- /dev/null +++ b/src/testcontainers/rabbitmq.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.rabbitmq import ( + RabbitMqContainer, +) + +warnings.warn( + "testcontainers.rabbitmq is deprecated, use testcontainers.community.rabbitmq instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "RabbitMqContainer", +] diff --git a/src/testcontainers/redis.py b/src/testcontainers/redis.py new file mode 100644 index 000000000..23f2839e7 --- /dev/null +++ b/src/testcontainers/redis.py @@ -0,0 +1,19 @@ +import warnings + +from testcontainers.community.redis import ( + AsyncRedisContainer, + PingWaitStrategy, + RedisContainer, +) + +warnings.warn( + "testcontainers.redis is deprecated, use testcontainers.community.redis instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "AsyncRedisContainer", + "PingWaitStrategy", + "RedisContainer", +] diff --git a/src/testcontainers/registry.py b/src/testcontainers/registry.py new file mode 100644 index 000000000..e57c7f0b7 --- /dev/null +++ b/src/testcontainers/registry.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.registry import ( + DockerRegistryContainer, +) + +warnings.warn( + "testcontainers.registry is deprecated, use testcontainers.community.registry instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "DockerRegistryContainer", +] diff --git a/src/testcontainers/scylla.py b/src/testcontainers/scylla.py new file mode 100644 index 000000000..643e13b06 --- /dev/null +++ b/src/testcontainers/scylla.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.scylla import ( + ScyllaContainer, +) + +warnings.warn( + "testcontainers.scylla is deprecated, use testcontainers.community.scylla instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ScyllaContainer", +] diff --git a/src/testcontainers/selenium.py b/src/testcontainers/selenium.py new file mode 100644 index 000000000..27c670ebe --- /dev/null +++ b/src/testcontainers/selenium.py @@ -0,0 +1,19 @@ +import warnings + +from testcontainers.community.selenium import ( + BrowserWebDriverContainer, + SeleniumVideoContainer, + get_image_name, +) + +warnings.warn( + "testcontainers.selenium is deprecated, use testcontainers.community.selenium instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "BrowserWebDriverContainer", + "SeleniumVideoContainer", + "get_image_name", +] diff --git a/src/testcontainers/sftp.py b/src/testcontainers/sftp.py new file mode 100644 index 000000000..6493af8b1 --- /dev/null +++ b/src/testcontainers/sftp.py @@ -0,0 +1,17 @@ +import warnings + +from testcontainers.community.sftp import ( + SFTPContainer, + SFTPUser, +) + +warnings.warn( + "testcontainers.sftp is deprecated, use testcontainers.community.sftp instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "SFTPContainer", + "SFTPUser", +] diff --git a/src/testcontainers/socat/__init__.py b/src/testcontainers/socat/__init__.py new file mode 100644 index 000000000..e2a1bcb1d --- /dev/null +++ b/src/testcontainers/socat/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa: F401 +from testcontainers.socat.socat import SocatContainer diff --git a/src/testcontainers/socat/socat.py b/src/testcontainers/socat/socat.py new file mode 100644 index 000000000..b19fd3677 --- /dev/null +++ b/src/testcontainers/socat/socat.py @@ -0,0 +1,84 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +import random +import string +from typing import Any, Optional + +from typing_extensions import Self + +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import PortWaitStrategy + + +class SocatContainer(DockerContainer): + """ + A container that uses socat to forward TCP connections. + """ + + def __init__( + self, + image: str = "alpine/socat:1.7.4.3-r0", + **kwargs: Any, + ) -> None: + """ + Initialize a new SocatContainer with the given image. + + Args: + image: The Docker image to use. Defaults to "alpine/socat:1.7.4.3-r0". + **kwargs: Additional keyword arguments to pass to the DockerContainer constructor. + """ + # Dictionary to store targets (port -> host:port mappings) + self.targets: dict[int, str] = {} + + kwargs["entrypoint"] = "/bin/sh" + + random_suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8)) + self.with_name(f"testcontainers-socat-{random_suffix}") + + super().__init__(image=image, **kwargs) + + def with_target(self, exposed_port: int, host: str, internal_port: Optional[int] = None) -> Self: + """ + Add a target to forward connections from the exposed port to the given host and port. + + Args: + exposed_port: The port to expose on the container. + host: The host to forward connections to. + internal_port: The port on the host to forward connections to. Defaults to the exposed_port if not provided. + + Returns: + Self: The container instance for chaining. + """ + if internal_port is None: + internal_port = exposed_port + + self.with_exposed_ports(exposed_port) + self.targets[exposed_port] = f"{host}:{internal_port}" + return self + + def _configure(self) -> None: + if not self.targets: + return + + socat_commands = [] + for port, target in self.targets.items(): + socat_commands.append(f"socat TCP-LISTEN:{port},fork,reuseaddr TCP:{target}") + + command = " & ".join(socat_commands) + + self.with_command(f'-c "{command}"') + + def start(self) -> Self: + if self.targets: + self.waiting_for(PortWaitStrategy(next(iter(self.targets)))) + return super().start() diff --git a/src/testcontainers/trino.py b/src/testcontainers/trino.py new file mode 100644 index 000000000..4af86818b --- /dev/null +++ b/src/testcontainers/trino.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.trino import ( + TrinoContainer, +) + +warnings.warn( + "testcontainers.trino is deprecated, use testcontainers.community.trino instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "TrinoContainer", +] diff --git a/src/testcontainers/valkey.py b/src/testcontainers/valkey.py new file mode 100644 index 000000000..10b150ef8 --- /dev/null +++ b/src/testcontainers/valkey.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.valkey import ( + ValkeyContainer, +) + +warnings.warn( + "testcontainers.valkey is deprecated, use testcontainers.community.valkey instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "ValkeyContainer", +] diff --git a/src/testcontainers/vault.py b/src/testcontainers/vault.py new file mode 100644 index 000000000..50e48ff64 --- /dev/null +++ b/src/testcontainers/vault.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.vault import ( + VaultContainer, +) + +warnings.warn( + "testcontainers.vault is deprecated, use testcontainers.community.vault instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "VaultContainer", +] diff --git a/src/testcontainers/weaviate.py b/src/testcontainers/weaviate.py new file mode 100644 index 000000000..07915042e --- /dev/null +++ b/src/testcontainers/weaviate.py @@ -0,0 +1,15 @@ +import warnings + +from testcontainers.community.weaviate import ( + WeaviateContainer, +) + +warnings.warn( + "testcontainers.weaviate is deprecated, use testcontainers.community.weaviate instead", + DeprecationWarning, + stacklevel=2, +) + +__all__ = [ + "WeaviateContainer", +] diff --git a/modules/sftp/testcontainers/sftp/py.typed b/tests/community/__init__.py similarity index 100% rename from modules/sftp/testcontainers/sftp/py.typed rename to tests/community/__init__.py diff --git a/modules/arangodb/tests/test_arangodb.py b/tests/community/arangodb/test_arangodb.py similarity index 96% rename from modules/arangodb/tests/test_arangodb.py rename to tests/community/arangodb/test_arangodb.py index 01e4643a7..fc191c48d 100644 --- a/modules/arangodb/tests/test_arangodb.py +++ b/tests/community/arangodb/test_arangodb.py @@ -2,12 +2,13 @@ ArangoDB Container Tests """ +import platform + import pytest from arango import ArangoClient from arango.exceptions import DatabaseCreateError, ServerVersionError -from testcontainers.arangodb import ArangoDbContainer -import platform +from testcontainers.community.arangodb import ArangoDbContainer ARANGODB_IMAGE_NAME = "arangodb" IMAGE_VERSION = "3.11.8" @@ -34,7 +35,7 @@ def arango_test_ops(arango_client, expected_version, username="root", password=" students = database.create_collection("students") # Add a hash index to the collection. - students.add_hash_index(fields=["name"], unique=True) + students.add_persistent_index(fields=["name"], unique=True) # Insert new documents into the collection. (students_to_insert_cnt) students.insert({"name": "jane", "age": 39}) diff --git a/modules/aws/tests/lambda_sample/Dockerfile b/tests/community/aws/lambda_sample/Dockerfile similarity index 100% rename from modules/aws/tests/lambda_sample/Dockerfile rename to tests/community/aws/lambda_sample/Dockerfile diff --git a/modules/aws/tests/lambda_sample/lambda_function.py b/tests/community/aws/lambda_sample/lambda_function.py similarity index 100% rename from modules/aws/tests/lambda_sample/lambda_function.py rename to tests/community/aws/lambda_sample/lambda_function.py diff --git a/modules/aws/tests/test_aws.py b/tests/community/aws/test_aws.py similarity index 88% rename from modules/aws/tests/test_aws.py rename to tests/community/aws/test_aws.py index 873b87352..4466f642a 100644 --- a/modules/aws/tests/test_aws.py +++ b/tests/community/aws/test_aws.py @@ -1,14 +1,15 @@ -import re import os +import re +from pathlib import Path +from unittest.mock import patch import pytest -from unittest.mock import patch +from testcontainers.community.aws import AWSLambdaContainer +from testcontainers.community.aws.aws_lambda import RIE_PATH from testcontainers.core.image import DockerImage -from testcontainers.aws import AWSLambdaContainer -from testcontainers.aws.aws_lambda import RIE_PATH -DOCKER_FILE_PATH = "./modules/aws/tests/lambda_sample" +DOCKER_FILE_PATH = Path(__file__).parent / "lambda_sample" IMAGE_TAG = "lambda:test" @@ -29,12 +30,12 @@ def test_aws_lambda_container(): def test_aws_lambda_container_external_env_vars(): - vars = { + variables = { "AWS_DEFAULT_REGION": "region", "AWS_ACCESS_KEY_ID": "id", "AWS_SECRET_ACCESS_KEY": "key", } - with patch.dict(os.environ, vars): + with patch.dict(os.environ, variables): with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-env-vars:latest") as image: with AWSLambdaContainer(image=image, port=8080) as func: assert func.env["AWS_DEFAULT_REGION"] == "region" @@ -51,6 +52,6 @@ def test_aws_lambda_container_no_port(): def test_aws_lambda_container_no_path(): with pytest.raises(TypeError): - with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-no-path:latest") as image: + with DockerImage(path=DOCKER_FILE_PATH, tag="test-lambda-no-path:latest"): with AWSLambdaContainer() as func: # noqa: F841 pass diff --git a/tests/community/azurite/samples/network_container/Dockerfile b/tests/community/azurite/samples/network_container/Dockerfile new file mode 100644 index 000000000..7ee7a2675 --- /dev/null +++ b/tests/community/azurite/samples/network_container/Dockerfile @@ -0,0 +1,12 @@ +# Use an official Python runtime as a parent image +FROM python:3.10-slim + +# Set the working directory in the container +WORKDIR /app + +RUN pip install azure-storage-blob==12.19.0 + +COPY ./netowrk_container.py netowrk_container.py +EXPOSE 80 +# Define the command to run the application +CMD ["python", "netowrk_container.py"] diff --git a/tests/community/azurite/samples/network_container/netowrk_container.py b/tests/community/azurite/samples/network_container/netowrk_container.py new file mode 100644 index 000000000..76b420d4b --- /dev/null +++ b/tests/community/azurite/samples/network_container/netowrk_container.py @@ -0,0 +1,28 @@ +import os + +from azure.storage.blob import BlobServiceClient + + +def hello_from_external_container(): + """ + Entry point function for a custom Docker container to test connectivity + and operations with Azurite (or Azure Blob Storage). + + This function is designed to run inside a separate container within the + same Docker network as an Azurite instance. It retrieves connection + details from environment variables and attempts to create a new + blob container on the connected storage account. + """ + connection_string = os.environ["AZURE_CONNECTION_STRING"] + container_to_create = os.environ["AZURE_CONTAINER"] + blob_service_client = BlobServiceClient.from_connection_string(connection_string) + # create dummy container just to make sure we can process the + try: + blob_service_client.create_container(name=container_to_create) + print("Azure Storage Container created.") + except Exception as e: + print(f"Something went wrong : {e}") + + +if __name__ == "__main__": + hello_from_external_container() diff --git a/tests/community/azurite/test_azurite.py b/tests/community/azurite/test_azurite.py new file mode 100644 index 000000000..f1dd305c2 --- /dev/null +++ b/tests/community/azurite/test_azurite.py @@ -0,0 +1,73 @@ +import logging +from pathlib import Path + +from azure.storage.blob import BlobServiceClient + +from testcontainers.community.azurite import AzuriteContainer, ConnectionStringType +from testcontainers.core.container import DockerContainer +from testcontainers.core.image import DockerImage +from testcontainers.core.network import Network +from testcontainers.core.waiting_utils import wait_for_logs + +logger = logging.getLogger(__name__) + + +DOCKER_FILE_PATH = ".modules/azurite/tests/external_container_sample" +IMAGE_TAG = "external_container:test" + +TEST_DIR = Path(__file__).parent + + +def test_docker_run_azurite(): + with AzuriteContainer() as azurite_container: + blob_service_client = BlobServiceClient.from_connection_string( + azurite_container.get_connection_string(), api_version="2019-12-12" + ) + + blob_service_client.create_container("test-container") + + +def test_docker_run_azurite_inter_container_communication(): + """Tests inter-container communication between an Azurite container and a custom + application container within the same Docker network, while also verifying + local machine access to Azurite. + + This test case validates the following: + 1. An Azurite container can be successfully started and configured with a + custom Docker network and a network alias. + 2. A custom application container can connect to the Azurite container + using a network-specific connection string (via its network alias) + within the shared Docker network. + 3. The Azurite container remains accessible from the local test machine + using a host-specific connection string. + 4. Operations performed by the custom container on Azurite (e.g., creating + a storage container) are visible and verifiable from the local machine. + """ + container_name = "test-container" + with Network() as network: + with ( + AzuriteContainer() + .with_network(network) + .with_network_aliases("azurite_server") + .with_exposed_ports(10000, 10000) + .with_exposed_ports(10001, 10001) as azurite_container + ): + network_connection_string = azurite_container.get_connection_string(ConnectionStringType.NETWORK) + local_connection_string = azurite_container.get_connection_string() + with DockerImage(path=TEST_DIR / "samples/network_container", tag=IMAGE_TAG) as image: + with ( + DockerContainer(image=str(image)) + .with_env("AZURE_CONNECTION_STRING", network_connection_string) + .with_env("AZURE_CONTAINER", container_name) + .with_network(network) + .with_network_aliases("network_container") + .with_exposed_ports(80, 80) as container + ): + wait_for_logs(container, "Azure Storage Container created.") + blob_service_client = BlobServiceClient.from_connection_string( + local_connection_string, api_version="2019-12-12" + ) + # make sure the container was actually created + assert container_name in [ + blob_container["name"] for blob_container in blob_service_client.list_containers() + ] diff --git a/tests/community/cassandra/test_cassandra.py b/tests/community/cassandra/test_cassandra.py new file mode 100644 index 000000000..7acfd249d --- /dev/null +++ b/tests/community/cassandra/test_cassandra.py @@ -0,0 +1,17 @@ +from testcontainers.community.cassandra import CassandraContainer + + +def test_docker_run_cassandra() -> None: + from cassandra.cluster import Cluster, DCAwareRoundRobinPolicy + + with ( + CassandraContainer("cassandra:4.1.4") as cassandra, + Cluster( + cassandra.get_contact_points(), + load_balancing_policy=DCAwareRoundRobinPolicy(cassandra.get_local_datacenter()), + protocol_version=5, + ) as cluster, + ): + session = cluster.connect() + result = session.execute("SELECT release_version FROM system.local;") + assert result.one().release_version == "4.1.4" diff --git a/modules/chroma/tests/test_chroma.py b/tests/community/chroma/test_chroma.py similarity index 65% rename from modules/chroma/tests/test_chroma.py rename to tests/community/chroma/test_chroma.py index fee55b788..ef2c95410 100644 --- a/modules/chroma/tests/test_chroma.py +++ b/tests/community/chroma/test_chroma.py @@ -1,9 +1,10 @@ -from testcontainers.chroma import ChromaContainer import chromadb +from testcontainers.community.chroma import ChromaContainer + def test_docker_run_chroma(): - with ChromaContainer(image="chromadb/chroma:0.4.24") as chroma: + with ChromaContainer(image="chromadb/chroma:1.0.0") as chroma: client = chromadb.HttpClient(host=chroma.get_config()["host"], port=chroma.get_config()["port"]) col = client.get_or_create_collection("test") assert col.name == "test" diff --git a/modules/clickhouse/tests/test_clickhouse.py b/tests/community/clickhouse/test_clickhouse.py similarity index 83% rename from modules/clickhouse/tests/test_clickhouse.py rename to tests/community/clickhouse/test_clickhouse.py index 23e5f4686..dfce769a7 100644 --- a/modules/clickhouse/tests/test_clickhouse.py +++ b/tests/community/clickhouse/test_clickhouse.py @@ -1,6 +1,6 @@ import clickhouse_driver -from testcontainers.clickhouse import ClickHouseContainer +from testcontainers.community.clickhouse import ClickHouseContainer def test_docker_run_clickhouse(): diff --git a/modules/cockroachdb/tests/test_cockroachdb.py b/tests/community/cockroachdb/test_cockroachdb.py similarity index 86% rename from modules/cockroachdb/tests/test_cockroachdb.py rename to tests/community/cockroachdb/test_cockroachdb.py index af20fd580..e1cfc0837 100644 --- a/modules/cockroachdb/tests/test_cockroachdb.py +++ b/tests/community/cockroachdb/test_cockroachdb.py @@ -1,6 +1,6 @@ import sqlalchemy -from testcontainers.cockroachdb import CockroachDBContainer +from testcontainers.community.cockroachdb import CockroachDBContainer def test_docker_run_mysql(): diff --git a/modules/cosmosdb/tests/test_cosmosdb_emulator.py b/tests/community/cosmosdb/test_cosmosdb_emulator.py similarity index 72% rename from modules/cosmosdb/tests/test_cosmosdb_emulator.py rename to tests/community/cosmosdb/test_cosmosdb_emulator.py index 542ddd11c..62363a8f2 100644 --- a/modules/cosmosdb/tests/test_cosmosdb_emulator.py +++ b/tests/community/cosmosdb/test_cosmosdb_emulator.py @@ -1,5 +1,4 @@ -import pytest -from testcontainers.cosmosdb._emulator import CosmosDBEmulatorContainer +from testcontainers.community.cosmosdb._emulator import CosmosDBEmulatorContainer def test_runs(): diff --git a/modules/cosmosdb/tests/test_cosmosdb_mongodb.py b/tests/community/cosmosdb/test_cosmosdb_mongodb.py similarity index 88% rename from modules/cosmosdb/tests/test_cosmosdb_mongodb.py rename to tests/community/cosmosdb/test_cosmosdb_mongodb.py index a50ee82ea..4b07d43a9 100644 --- a/modules/cosmosdb/tests/test_cosmosdb_mongodb.py +++ b/tests/community/cosmosdb/test_cosmosdb_mongodb.py @@ -1,5 +1,6 @@ import pytest -from testcontainers.cosmosdb import CosmosDBMongoEndpointContainer + +from testcontainers.community.cosmosdb import CosmosDBMongoEndpointContainer def test_requires_a_version(): diff --git a/modules/cosmosdb/tests/test_cosmosdb_nosql.py b/tests/community/cosmosdb/test_cosmosdb_nosql.py similarity index 72% rename from modules/cosmosdb/tests/test_cosmosdb_nosql.py rename to tests/community/cosmosdb/test_cosmosdb_nosql.py index a9460a1b0..20684c9cd 100644 --- a/modules/cosmosdb/tests/test_cosmosdb_nosql.py +++ b/tests/community/cosmosdb/test_cosmosdb_nosql.py @@ -1,5 +1,4 @@ -import pytest -from testcontainers.cosmosdb import CosmosDBNoSQLEndpointContainer +from testcontainers.community.cosmosdb import CosmosDBNoSQLEndpointContainer def test_runs(): diff --git a/tests/community/cratedb/test_cratedb.py b/tests/community/cratedb/test_cratedb.py new file mode 100644 index 000000000..59132a4e2 --- /dev/null +++ b/tests/community/cratedb/test_cratedb.py @@ -0,0 +1,47 @@ +import urllib.parse + +import pytest +import sqlalchemy + +from testcontainers.community.cratedb import CrateDBContainer + + +@pytest.mark.parametrize("version", ["5.10", "latest"]) +def test_docker_run_cratedb(version: str): + with CrateDBContainer(f"crate:{version}") as cratedb: + engine = sqlalchemy.create_engine(cratedb.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("select 1 + 2 + 3 + 4 + 5")) + assert result.fetchone()[0] == 15 + + +def test_cratedb_connection_url(): + with CrateDBContainer("crate:latest", username="crate", password="crate") as cratedb: + url = urllib.parse.urlparse(cratedb.get_connection_url()) + assert url.scheme == "crate" + credentials, location = url.netloc.split("@") + assert credentials == "crate:crate" + host, port = location.split(":") + assert host == cratedb.get_container_host_ip() + assert int(port) == cratedb.get_exposed_port(cratedb.port) + + +@pytest.mark.parametrize( + "cmd_opts, expected", + [ + pytest.param( + [("indices.breaker.total.limit", "90%")], + "-Cdiscovery.type=single-node -Cindices.breaker.total.limit=90%", + id="add_cmd_option", + ), + pytest.param( + [("discovery.type", "zen"), ("indices.breaker.total.limit", "90%")], + "-Cdiscovery.type=zen -Cindices.breaker.total.limit=90%", + id="override_defaults", + ), + ], +) +def test_build_command(cmd_opts, expected): + # Pure unit test: the command line is assembled in __init__, no container is started. + cratedb = CrateDBContainer(cmd_opts=cmd_opts) + assert cratedb._command == expected diff --git a/modules/db2/tests/test_db2.py b/tests/community/db2/test_db2.py similarity index 93% rename from modules/db2/tests/test_db2.py rename to tests/community/db2/test_db2.py index 7b6ea844a..d5068b2d4 100644 --- a/modules/db2/tests/test_db2.py +++ b/tests/community/db2/test_db2.py @@ -1,10 +1,10 @@ -from unittest import mock - import pytest import sqlalchemy +from testcontainers.community.db2 import Db2Container from testcontainers.core.utils import is_arm -from testcontainers.db2 import Db2Container + +pytestmark = pytest.mark.xdist_group("db2") @pytest.mark.skipif(is_arm(), reason="db2 container not available for ARM") diff --git a/modules/elasticsearch/tests/test_elasticsearch.py b/tests/community/elasticsearch/test_elasticsearch.py similarity index 56% rename from modules/elasticsearch/tests/test_elasticsearch.py rename to tests/community/elasticsearch/test_elasticsearch.py index 661a550c6..b5e384e3d 100644 --- a/modules/elasticsearch/tests/test_elasticsearch.py +++ b/tests/community/elasticsearch/test_elasticsearch.py @@ -3,12 +3,12 @@ import pytest -from testcontainers.elasticsearch import ElasticSearchContainer +from testcontainers.community.elasticsearch import ElasticSearchContainer # The versions below should reflect the latest stable releases -@pytest.mark.parametrize("version", ["7.17.18", "8.12.2"]) +@pytest.mark.parametrize("version", ["7.17.18", "8.12.2", "9.1.4"]) def test_docker_run_elasticsearch(version): with ElasticSearchContainer(f"elasticsearch:{version}", mem_limit="3G") as es: - resp = urllib.request.urlopen(es.get_url()) + resp = urllib.request.urlopen(f"http://{es.get_container_host_ip()}:{es.get_exposed_port(es.port)}") assert json.loads(resp.read().decode())["version"]["number"] == version diff --git a/modules/generic/tests/conftest.py b/tests/community/generic/conftest.py similarity index 86% rename from modules/generic/tests/conftest.py rename to tests/community/generic/conftest.py index 4f69565f4..810ab426e 100644 --- a/modules/generic/tests/conftest.py +++ b/tests/community/generic/conftest.py @@ -1,5 +1,7 @@ -import pytest from typing import Callable + +import pytest + from testcontainers.core.container import DockerClient @@ -17,6 +19,6 @@ def _check_for_image(image_short_id: str, cleaned: bool) -> None: client = DockerClient() images = client.client.images.list() found = any(image.short_id.endswith(image_short_id) for image in images) - assert found is not cleaned, f'Image {image_short_id} was {"found" if cleaned else "not found"}' + assert found is not cleaned, f"Image {image_short_id} was {'found' if cleaned else 'not found'}" return _check_for_image diff --git a/modules/generic/tests/samples/advance_1/Dockerfile b/tests/community/generic/samples/advance_1/Dockerfile similarity index 100% rename from modules/generic/tests/samples/advance_1/Dockerfile rename to tests/community/generic/samples/advance_1/Dockerfile diff --git a/tests/community/generic/samples/advance_1/app/__init__.py b/tests/community/generic/samples/advance_1/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/generic/tests/samples/advance_1/app/main.py b/tests/community/generic/samples/advance_1/app/main.py similarity index 99% rename from modules/generic/tests/samples/advance_1/app/main.py rename to tests/community/generic/samples/advance_1/app/main.py index 27cc6bbec..dd69b6436 100644 --- a/modules/generic/tests/samples/advance_1/app/main.py +++ b/tests/community/generic/samples/advance_1/app/main.py @@ -1,11 +1,10 @@ # This app will use redis to store given key-value pairs. import os -import redis +import redis from fastapi import FastAPI - app = FastAPI() redis_host = os.getenv("REDIS_HOST") diff --git a/modules/generic/tests/samples/fastapi/Dockerfile b/tests/community/generic/samples/fastapi/Dockerfile similarity index 100% rename from modules/generic/tests/samples/fastapi/Dockerfile rename to tests/community/generic/samples/fastapi/Dockerfile diff --git a/tests/community/generic/samples/fastapi/app/__init__.py b/tests/community/generic/samples/fastapi/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/generic/tests/samples/fastapi/app/main.py b/tests/community/generic/samples/fastapi/app/main.py similarity index 100% rename from modules/generic/tests/samples/fastapi/app/main.py rename to tests/community/generic/samples/fastapi/app/main.py diff --git a/modules/generic/tests/samples/python_server/Dockerfile b/tests/community/generic/samples/python_server/Dockerfile similarity index 100% rename from modules/generic/tests/samples/python_server/Dockerfile rename to tests/community/generic/samples/python_server/Dockerfile diff --git a/modules/generic/tests/test_generic.py b/tests/community/generic/test_server.py similarity index 90% rename from modules/generic/tests/test_generic.py rename to tests/community/generic/test_server.py index 5943b4a4d..4657b4f00 100644 --- a/modules/generic/tests/test_generic.py +++ b/tests/community/generic/test_server.py @@ -5,16 +5,17 @@ import pytest from httpx import get -from testcontainers.core.waiting_utils import wait_for_logs +from testcontainers.community.generic import ServerContainer from testcontainers.core.image import DockerImage -from testcontainers.generic import ServerContainer +from testcontainers.core.waiting_utils import wait_for_logs TEST_DIR = Path(__file__).parent @pytest.mark.parametrize("test_image_cleanup", [True, False]) @pytest.mark.parametrize("test_image_tag", [None, "custom-image:test"]) -def test_server_container(test_image_tag: Optional[str], test_image_cleanup: bool, check_for_image, port=9000): +def test_server_container(test_image_tag: Optional[str], test_image_cleanup: bool, check_for_image): + port = 9000 with ( DockerImage( path=TEST_DIR / "samples/python_server", @@ -28,7 +29,7 @@ def test_server_container(test_image_tag: Optional[str], test_image_cleanup: boo image_build_logs = docker_image.get_logs() # check if dict is in any of the logs assert {"stream": f"Step 2/3 : EXPOSE {port}"} in image_build_logs, "Image logs mismatch" - assert (port, None) in srv.ports.items(), "Port mismatch" + assert (str(port), None) in srv.ports.items(), "Port mismatch" with pytest.raises(NotImplementedError): srv.get_api_url() test_url = srv._create_connection_url() diff --git a/tests/community/generic/test_sql.py b/tests/community/generic/test_sql.py new file mode 100644 index 000000000..5db3010e9 --- /dev/null +++ b/tests/community/generic/test_sql.py @@ -0,0 +1,239 @@ +from unittest.mock import patch + +import pytest + +from testcontainers.community.generic.providers.sql_connection_wait_strategy import SqlAlchemyConnectWaitStrategy +from testcontainers.community.generic.sql import SqlContainer +from testcontainers.core.exceptions import ContainerStartException + + +class SimpleSqlContainer(SqlContainer): + """Simple concrete implementation for testing.""" + + def __init__(self, image: str = "postgres:13"): + super().__init__(image, wait_strategy=SqlAlchemyConnectWaitStrategy()) + self.username = "testuser" + self.password = "testpass" + self.dbname = "testdb" + self.port = 5432 + + def get_connection_url(self) -> str: + return self._create_connection_url( + dialect="postgresql", username=self.username, password=self.password, port=self.port, dbname=self.dbname + ) + + def _configure(self) -> None: + self.with_env("POSTGRES_USER", self.username) + self.with_env("POSTGRES_PASSWORD", self.password) + self.with_env("POSTGRES_DB", self.dbname) + self.with_exposed_ports(self.port) + + +class TestSqlContainer: + def test_abstract_methods_raise_not_implemented(self): + container = SqlContainer("test:latest", SqlAlchemyConnectWaitStrategy()) + + with pytest.raises(NotImplementedError): + container.get_connection_url() + + with pytest.raises(NotImplementedError): + container._configure() + + def test_transfer_seed_default_behavior(self): + container = SqlContainer("test:latest", SqlAlchemyConnectWaitStrategy()) + # Should not raise an exception + container._transfer_seed() + + def test_connection_url_creation_basic(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() # Simple mock + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: port + + url = container._create_connection_url(dialect="postgresql", username="user", password="pass", port=5432) + + assert url == "postgresql://user:pass@localhost:5432" + + def test_connection_url_with_database_name(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: port + + url = container._create_connection_url( + dialect="postgresql", username="user", password="pass", port=5432, dbname="mydb" + ) + + assert url == "postgresql://user:pass@localhost:5432/mydb" + + def test_connection_url_with_special_characters(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: port + + url = container._create_connection_url( + dialect="postgresql", username="user@domain", password="p@ss/word", port=5432 + ) + + # Check that special characters are URL encoded + assert "user%40domain" in url + assert "p%40ss%2Fword" in url + + def test_connection_url_with_query_params(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: port + + url = container._create_connection_url( + dialect="postgresql", + username="user", + password="pass", + port=5432, + query_params={"ssl": "require", "timeout": "30"}, + ) + + assert "?" in url + assert "ssl=require" in url + assert "timeout=30" in url + + def test_connection_url_type_errors(self): + """Test that _create_connection_url raises TypeError with invalid types""" + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {"id": "test-id"})() + + # Mock get_exposed_port to simulate what happens with None port + with patch.object(container, "get_exposed_port") as mock_get_port: + # Simulate the TypeError that would occur when int(None) is called + mock_get_port.side_effect = TypeError( + "int() argument must be a string, a bytes-like object or a real number, not 'NoneType'" + ) + + with pytest.raises(TypeError, match="int\\(\\) argument must be a string"): + container._create_connection_url("postgresql", "user", "pass", port=None) + + def test_connection_url_container_not_started(self): + container = SimpleSqlContainer() + container._container = None + + with pytest.raises(ContainerStartException, match="Container has not been started"): + container._create_connection_url("postgresql", "user", "pass", port=5432) + + def test_container_configuration(self): + container = SimpleSqlContainer("postgres:13") + + # Test that configuration sets up environment + container._configure() + + assert container.env["POSTGRES_USER"] == "testuser" + assert container.env["POSTGRES_PASSWORD"] == "testpass" + assert container.env["POSTGRES_DB"] == "testdb" + + def test_concrete_container_connection_url(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: 5432 + + url = container.get_connection_url() + + assert url.startswith("postgresql://") + assert "testuser" in url + assert "testpass" in url + assert "testdb" in url + assert "localhost:5432" in url + + def test_container_inheritance(self): + container = SimpleSqlContainer() + + assert isinstance(container, SqlContainer) + assert hasattr(container, "get_connection_url") + assert hasattr(container, "_configure") + assert hasattr(container, "_transfer_seed") + assert hasattr(container, "start") + + def test_additional_transient_errors_list(self): + from testcontainers.community.generic.providers.sql_connection_wait_strategy import ADDITIONAL_TRANSIENT_ERRORS + + assert isinstance(ADDITIONAL_TRANSIENT_ERRORS, list) + # List may be empty if SQLAlchemy not available, or contain DBAPIError if it is + + def test_empty_password_handling(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: port + + url = container._create_connection_url(dialect="postgresql", username="user", password="", port=5432) + + assert url == "postgresql://user:@localhost:5432" + + def test_unicode_characters_in_credentials(self): + container = SimpleSqlContainer() + container._container = type("MockContainer", (), {})() + container.get_container_host_ip = lambda: "localhost" + container.get_exposed_port = lambda port: port + + url = container._create_connection_url( + dialect="postgresql", username="usér", password="päss", port=5432, dbname="tëstdb" + ) + + assert "us%C3%A9r" in url + assert "p%C3%A4ss" in url + assert "t%C3%ABstdb" in url + + def test_start_postgres_container_integration(self): + """Integration test that actually starts a PostgreSQL container.""" + container = SimpleSqlContainer() + + # This will start the container and test the connection + container.start() + + # Verify the container is running + assert container._container is not None + + # Test that we can get a connection URL + url = container.get_connection_url() + assert url.startswith("postgresql://") + assert "testuser" in url + assert "testdb" in url + + # Verify environment variables are set + assert container.env["POSTGRES_USER"] == "testuser" + assert container.env["POSTGRES_PASSWORD"] == "testpass" + assert container.env["POSTGRES_DB"] == "testdb" + + # check logs + logs = container.get_logs() + assert "database system is ready to accept connections" in logs[0].decode("utf-8").lower() + + def test_sql_postgres_container_integration(self): + """Integration test for SqlContainer with PostgreSQL.""" + container = SimpleSqlContainer() + + # This will start the container and test the connection + container.start() + + # Verify the container is running + assert container._container is not None + + # Test that we can get a connection URL + url = container.get_connection_url() + + # check sql operations + import sqlalchemy + + engine = sqlalchemy.create_engine(url) + with engine.connect() as conn: + # Create a test table + conn.execute( + sqlalchemy.text("CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(50));") + ) + # Insert a test record + conn.execute(sqlalchemy.text("INSERT INTO test_table (name) VALUES ('test_name');")) + # Query the test record + result = conn.execute(sqlalchemy.text("SELECT name FROM test_table WHERE name='test_name';")) + fetched = result.fetchone() + assert fetched is not None + assert fetched[0] == "test_name" diff --git a/modules/google/tests/test_google.py b/tests/community/google/test_google.py similarity index 91% rename from modules/google/tests/test_google.py rename to tests/community/google/test_google.py index 0c412d706..a4c43cb8b 100644 --- a/modules/google/tests/test_google.py +++ b/tests/community/google/test_google.py @@ -1,8 +1,9 @@ from queue import Queue + from google.cloud.datastore import Entity +from testcontainers.community.google import DatastoreContainer, PubSubContainer from testcontainers.core.waiting_utils import wait_for_logs -from testcontainers.google import PubSubContainer, DatastoreContainer def test_pubsub_container(): @@ -68,9 +69,9 @@ def test_datastore_container_isolation(): # Create a second container and try to fetch the entity to makesure its a different container with DatastoreContainer() as datastore2: - assert ( - datastore.get_datastore_emulator_host() != datastore2.get_datastore_emulator_host() - ), "Datastore containers use the same port." + assert datastore.get_datastore_emulator_host() != datastore2.get_datastore_emulator_host(), ( + "Datastore containers use the same port." + ) client2 = datastore2.get_datastore_client() fetched_entity2 = client2.get(key) assert fetched_entity2 is None, "Entity was found in the datastore." diff --git a/tests/community/influxdb/__init__.py b/tests/community/influxdb/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modules/influxdb/tests/test_influxdb.py b/tests/community/influxdb/test_influxdb.py similarity index 91% rename from modules/influxdb/tests/test_influxdb.py rename to tests/community/influxdb/test_influxdb.py index 62144a3c3..cecbbc98e 100644 --- a/modules/influxdb/tests/test_influxdb.py +++ b/tests/community/influxdb/test_influxdb.py @@ -12,33 +12,30 @@ # under the License. from datetime import datetime -from typing import Type +import pytest from influxdb.resultset import ResultSet from influxdb_client import Bucket from influxdb_client.client.write_api import SYNCHRONOUS -from pytest import mark -from testcontainers.influxdb import InfluxDbContainer -from testcontainers.influxdb1 import InfluxDb1Container -from testcontainers.influxdb2 import InfluxDb2Container +from testcontainers.community.influxdb import InfluxDb1Container, InfluxDb2Container, InfluxDbContainer -@mark.parametrize( - ["image", "influxdb_container_class", "exposed_port"], +@pytest.mark.parametrize( + "image, influxdb_container_class, exposed_port", [ ("influxdb:2.7", InfluxDb1Container, 8086), ("influxdb:1.8", InfluxDb2Container, 8086), ], ) -def test_influxdbcontainer_get_url(image: str, influxdb_container_class: Type[InfluxDbContainer], exposed_port: int): +def test_influxdbcontainer_get_url(image: str, influxdb_container_class: type[InfluxDbContainer], exposed_port: int): with influxdb_container_class(image, host_port=exposed_port) as influxdb_container: connection_url = influxdb_container.get_url() assert str(exposed_port) in connection_url -@mark.parametrize( - ["image", "influxdb_container_class", "expected_version"], +@pytest.mark.parametrize( + "image, influxdb_container_class, expected_version", [ ("influxdb:1.8", InfluxDb1Container, "1.8.10"), ("influxdb:1.8.10", InfluxDb1Container, "1.8.10"), @@ -47,7 +44,7 @@ def test_influxdbcontainer_get_url(image: str, influxdb_container_class: Type[In ], ) def test_influxdbcontainer_get_influxdb_version( - image: str, influxdb_container_class: Type[InfluxDbContainer], expected_version: str + image: str, influxdb_container_class: type[InfluxDbContainer], expected_version: str ): with influxdb_container_class(image) as influxdb_container: assert influxdb_container.get_influxdb_version().startswith(expected_version) diff --git a/modules/k3s/tests/test_k3s.py b/tests/community/k3s/test_k3s.py similarity index 88% rename from modules/k3s/tests/test_k3s.py rename to tests/community/k3s/test_k3s.py index edff1c6d8..d56338809 100644 --- a/modules/k3s/tests/test_k3s.py +++ b/tests/community/k3s/test_k3s.py @@ -2,7 +2,7 @@ import yaml from kubernetes import client, config -from testcontainers.k3s import K3SContainer +from testcontainers.community.k3s import K3SContainer def test_docker_run_k3s(): diff --git a/modules/kafka/tests/test_kafka.py b/tests/community/kafka/test_kafka.py similarity index 96% rename from modules/kafka/tests/test_kafka.py rename to tests/community/kafka/test_kafka.py index 901f3f0c3..4bec6950f 100644 --- a/modules/kafka/tests/test_kafka.py +++ b/tests/community/kafka/test_kafka.py @@ -1,8 +1,8 @@ import pytest from kafka import KafkaAdminClient, KafkaConsumer, KafkaProducer, TopicPartition +from testcontainers.community.kafka import KafkaContainer, kafka_config from testcontainers.core.network import Network -from testcontainers.kafka import KafkaContainer, kafka_config def test_kafka_producer_consumer(): diff --git a/modules/kafka/tests/test_redpanda.py b/tests/community/kafka/test_redpanda.py similarity index 87% rename from modules/kafka/tests/test_redpanda.py rename to tests/community/kafka/test_redpanda.py index 7cee9fa8a..385f98713 100644 --- a/modules/kafka/tests/test_redpanda.py +++ b/tests/community/kafka/test_redpanda.py @@ -1,11 +1,11 @@ -import pytest -from requests import post, get from json import dumps -from kafka import KafkaConsumer, KafkaProducer, TopicPartition, KafkaAdminClient +import pytest +from kafka import KafkaAdminClient, KafkaConsumer, KafkaProducer, TopicPartition from kafka.admin import NewTopic +from requests import get, post -from testcontainers.kafka import RedpandaContainer +from testcontainers.community.kafka import RedpandaContainer def test_redpanda_producer_consumer(): @@ -13,7 +13,7 @@ def test_redpanda_producer_consumer(): produce_and_consume_message(container) -@pytest.mark.parametrize("version", ["v23.1.13", "v23.3.10"]) +@pytest.mark.parametrize("version", ["v23.1.13", "v25.3.6"]) def test_redpanda_confluent_version(version): with RedpandaContainer(image=f"docker.redpanda.com/redpandadata/redpanda:{version}") as container: produce_and_consume_message(container) diff --git a/tests/community/keycloak/test_keycloak.py b/tests/community/keycloak/test_keycloak.py new file mode 100644 index 000000000..5aaf44ac5 --- /dev/null +++ b/tests/community/keycloak/test_keycloak.py @@ -0,0 +1,16 @@ +import pytest + +from testcontainers.community.keycloak import KeycloakContainer + + +@pytest.mark.parametrize("image_version", ["26.4.0", "26.0.0", "25.0", "24.0.1", "18.0"]) +def test_docker_run_keycloak(image_version: str): + with KeycloakContainer(f"quay.io/keycloak/keycloak:{image_version}") as keycloak_admin: + assert keycloak_admin.get_client().users_count() == 1 + + +def test_docker_run_keycloak_with_management_relative_path(): + with KeycloakContainer("quay.io/keycloak/keycloak:26.4.0").with_env( + "KC_HTTP_MANAGEMENT_RELATIVE_PATH", "/some/deeply/nested/path" + ) as keycloak_admin: + assert keycloak_admin.get_client().users_count() == 1 diff --git a/modules/localstack/tests/test_localstack.py b/tests/community/localstack/test_localstack.py similarity index 84% rename from modules/localstack/tests/test_localstack.py rename to tests/community/localstack/test_localstack.py index 6801aefdb..0243e4292 100644 --- a/modules/localstack/tests/test_localstack.py +++ b/tests/community/localstack/test_localstack.py @@ -1,7 +1,7 @@ import json import urllib -from testcontainers.localstack import LocalStackContainer +from testcontainers.community.localstack import LocalStackContainer def test_docker_run_localstack(): @@ -16,7 +16,7 @@ def test_docker_run_localstack(): def test_localstack_boto3(): - from testcontainers.localstack import LocalStackContainer + from testcontainers.community.localstack import LocalStackContainer with LocalStackContainer(image="localstack/localstack:2.0.1") as localstack: dynamo_client = localstack.get_client("dynamodb") diff --git a/modules/mailpit/tests/test_mailpit.py b/tests/community/mailpit/test_mailpit.py similarity index 98% rename from modules/mailpit/tests/test_mailpit.py rename to tests/community/mailpit/test_mailpit.py index 53247f49d..88e82f3ae 100644 --- a/modules/mailpit/tests/test_mailpit.py +++ b/tests/community/mailpit/test_mailpit.py @@ -1,10 +1,10 @@ import smtplib -from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText import pytest -from testcontainers.mailpit import MailpitContainer, MailpitUser +from testcontainers.community.mailpit import MailpitContainer, MailpitUser _sender = "from@example.com" _receivers = ["to@example.com"] diff --git a/modules/memcached/tests/test_memcached.py b/tests/community/memcached/test_memcached.py similarity index 93% rename from modules/memcached/tests/test_memcached.py rename to tests/community/memcached/test_memcached.py index 853ede40a..4bcb60f55 100644 --- a/modules/memcached/tests/test_memcached.py +++ b/tests/community/memcached/test_memcached.py @@ -1,9 +1,9 @@ import socket -from testcontainers.memcached import MemcachedContainer - import pytest +from testcontainers.community.memcached import MemcachedContainer + def test_memcached_host_and_exposed_port(): with MemcachedContainer("memcached:1.6-alpine") as memcached: diff --git a/modules/milvus/tests/test_milvus.py b/tests/community/milvus/test_milvus.py similarity index 91% rename from modules/milvus/tests/test_milvus.py rename to tests/community/milvus/test_milvus.py index 12887a49b..3d045338d 100644 --- a/modules/milvus/tests/test_milvus.py +++ b/tests/community/milvus/test_milvus.py @@ -1,7 +1,7 @@ import pytest from pymilvus import MilvusClient -from testcontainers.milvus import MilvusContainer +from testcontainers.community.milvus import MilvusContainer VERSIONS = ["v2.4.0", "v2.4.4"] @@ -21,7 +21,8 @@ def test_run_milvus_success(version: str): exposed_port = milvus_container.get_exposed_port(milvus_container.port) url = milvus_container.get_connection_url() - assert url and exposed_port in url + assert url + assert str(exposed_port) in url @pytest.mark.parametrize("version", VERSIONS) diff --git a/modules/minio/tests/test_minio.py b/tests/community/minio/test_minio.py similarity index 91% rename from modules/minio/tests/test_minio.py rename to tests/community/minio/test_minio.py index 99eed4380..5fa25ee90 100644 --- a/modules/minio/tests/test_minio.py +++ b/tests/community/minio/test_minio.py @@ -1,6 +1,6 @@ import io -from testcontainers.minio import MinioContainer +from testcontainers.community.minio import MinioContainer def test_docker_run_minio(): diff --git a/tests/community/mongodb/test_mongodb.py b/tests/community/mongodb/test_mongodb.py new file mode 100644 index 000000000..62f2342c3 --- /dev/null +++ b/tests/community/mongodb/test_mongodb.py @@ -0,0 +1,175 @@ +import time +from urllib.parse import parse_qs, urlsplit + +import pytest + +from testcontainers.community.mongodb import MongoDBAtlasLocalContainer, MongoDbContainer, MongoDbReplicaSetContainer + + +@pytest.mark.parametrize("version", ["7.0.7", "6.0.14", "5.0.26"]) +def test_docker_run_mongodb(version: str): + with MongoDbContainer(f"mongo:{version}") as mongo: + db = mongo.get_connection_client().test + doc = { + "address": { + "street": "2 Avenue", + "zipcode": "10075", + "building": "1480", + "coord": [-73.9557413, 40.7720266], + }, + "borough": "Manhattan", + "cuisine": "Italian", + "name": "Vella", + "restaurant_id": "41704620", + } + result = db.restaurants.insert_one(doc) + assert result.inserted_id + + cursor = db.restaurants.find({"borough": "Manhattan"}) + assert cursor.next()["restaurant_id"] == doc["restaurant_id"] + + +@pytest.mark.parametrize("version", ["7.0.7", "6.0.14", "5.0.26"]) +def test_docker_run_mongodb_replica_set_without_authentication(version: str): + with MongoDbReplicaSetContainer(f"mongo:{version}", replica_set="docker-rs", auth_enabled=False) as mongo: + connection_url = urlsplit(mongo.get_connection_url()) + assert connection_url.username is None + assert parse_qs(connection_url.query) == { + "directConnection": ["true"], + "replicaSet": ["docker-rs"], + } + + client = mongo.get_connection_client() + + hello = client.admin.command("hello") + assert hello["setName"] == "docker-rs" + assert hello["isWritablePrimary"] is True + + collection = client.test.transactions + with client.start_session() as session, session.start_transaction(): + result = collection.insert_one({"message": "replica sets support transactions"}, session=session) + + assert collection.find_one({"_id": result.inserted_id}) is not None + + +@pytest.mark.parametrize("version", ["7.0.7", "6.0.14", "5.0.26"]) +def test_docker_run_authenticated_mongodb_replica_set(version: str): + container = MongoDbReplicaSetContainer( + f"mongo:{version}", + username="replica-user", + password="replica-password", + replica_set="secure-rs", + command=["--profile", "1"], + ).with_kwargs(labels={"testcontainers.mongodb.replica-set": "true"}) + + with container as mongo: + connection_url = urlsplit(mongo.get_connection_url()) + assert connection_url.username == "replica-user" + assert connection_url.password == "replica-password" + assert parse_qs(connection_url.query) == { + "directConnection": ["true"], + "replicaSet": ["secure-rs"], + } + + client = mongo.get_connection_client() + + hello = client.admin.command("hello") + assert hello["setName"] == "secure-rs" + assert hello["isWritablePrimary"] is True + + result = client.test.documents.insert_one({"authenticated": True}) + assert client.test.documents.find_one({"_id": result.inserted_id}) == { + "_id": result.inserted_id, + "authenticated": True, + } + + +@pytest.mark.parametrize("username,password", [("user", None), (None, "password")]) +def test_mongodb_rejects_credentials_when_authentication_is_disabled( + username: str | None, + password: str | None, +): + with pytest.raises(ValueError, match="authentication is disabled"): + MongoDbReplicaSetContainer(auth_enabled=False, username=username, password=password) + + +def test_authenticated_mongodb_replica_set_rejects_non_root_container_user(): + with pytest.raises(ValueError, match="root container user"): + MongoDbReplicaSetContainer(replica_set="secure-rs", user="mongodb") + + +def test_mongodb_replica_set_rejects_non_mongod_command(): + with pytest.raises(ValueError, match="mongod options"): + MongoDbReplicaSetContainer(replica_set="secure-rs", command=["bash", "-lc", "exec mongod"]) + + +@pytest.mark.parametrize("version", ["8.0.13", "7.0.23"]) +def test_docker_run_mongodb_atlas_local(version: str): + with MongoDBAtlasLocalContainer(f"mongodb/mongodb-atlas-local:{version}") as mongo_atlas: + db = mongo_atlas.get_connection_client().test + index_doc = { + "definition": { + "mappings": { + "dynamic": False, + "fields": { + "borough": {"analyzer": "lucene.keyword", "type": "string"}, + }, + }, + }, + "name": "test", + } + + db.create_collection("restaurants") + + db.restaurants.create_search_index(index_doc) + + doc = { + "address": { + "street": "2 Avenue", + "zipcode": "10075", + "building": "1480", + "coord": [-73.9557413, 40.7720266], + }, + "borough": "Manhattan", + "cuisine": "Italian", + "name": "Vella", + "restaurant_id": "41704620", + } + result = db.restaurants.insert_one(doc) + assert result.inserted_id + + # Wait for index to catch up + indexes = db.restaurants.list_search_indexes() + while indexes.next()["status"] != "READY": + time.sleep(0.1) + indexes = db.restaurants.list_search_indexes() + + cursor = db.restaurants.aggregate( + [{"$search": {"index": "test", "text": {"query": "Manhattan", "path": "borough"}}}] + ) + assert cursor.next()["restaurant_id"] == doc["restaurant_id"] + + +# This is a feature in the generic DbContainer class +# but it can't be tested on its own +# so is tested in various database modules: +# - mysql / mariadb +# - postgresql +# - sqlserver +# - mongodb +# - db2 +def test_quoted_password(): + user = "root" + password = "p@$%25+0&%rd :/!=?" + quoted_password = "p%40%24%2525+0%26%25rd %3A%2F%21%3D%3F" + # driver = "pymongo" + kwargs = { + "username": user, + "password": password, + } + with MongoDbContainer("mongo:7.0.7", **kwargs) as container: + host = container.get_container_host_ip() + port = container.get_exposed_port(27017) + expected_url = f"mongodb://{user}:{quoted_password}@{host}:{port}" + url = container.get_connection_url() + assert url == expected_url diff --git a/modules/mqtt/tests/test_mosquitto.py b/tests/community/mqtt/test_mosquitto.py similarity index 82% rename from modules/mqtt/tests/test_mosquitto.py rename to tests/community/mqtt/test_mosquitto.py index 63ce7fcd9..da5ad3e8e 100644 --- a/modules/mqtt/tests/test_mosquitto.py +++ b/tests/community/mqtt/test_mosquitto.py @@ -1,8 +1,8 @@ import pytest -from testcontainers.mqtt import MosquittoContainer +from testcontainers.community.mqtt import MosquittoContainer -VERSIONS = ["1.6.15", "2.0.18"] +VERSIONS = ["1.6.15", "2.0.18", "2.1.2-alpine"] @pytest.mark.parametrize("version", VERSIONS) diff --git a/modules/mssql/tests/test_mssql.py b/tests/community/mssql/test_mssql.py similarity index 94% rename from modules/mssql/tests/test_mssql.py rename to tests/community/mssql/test_mssql.py index c8c6d5e1a..8afdd7f4d 100644 --- a/modules/mssql/tests/test_mssql.py +++ b/tests/community/mssql/test_mssql.py @@ -1,12 +1,14 @@ import pytest import sqlalchemy +from testcontainers.community.mssql import SqlServerContainer from testcontainers.core.utils import is_arm -from testcontainers.mssql import SqlServerContainer + +pytestmark = pytest.mark.xdist_group("mssql") @pytest.mark.skipif(is_arm(), reason="mssql container not available for ARM") -@pytest.mark.parametrize("version", ["2022-CU12-ubuntu-22.04", "2019-CU25-ubuntu-20.04"]) +@pytest.mark.parametrize("version", ["2022-CU12-ubuntu-22.04", "2019-CU25-ubuntu-20.04", "2025-latest"]) def test_docker_run_mssql(version: str): with SqlServerContainer(f"mcr.microsoft.com/mssql/server:{version}", password="1Secure*Password2") as mssql: engine = sqlalchemy.create_engine(mssql.get_connection_url()) diff --git a/modules/mysql/tests/seeds/01-schema.sql b/tests/community/mysql/seeds/01-schema.sql similarity index 100% rename from modules/mysql/tests/seeds/01-schema.sql rename to tests/community/mysql/seeds/01-schema.sql diff --git a/modules/mysql/tests/seeds/02-seeds.sql b/tests/community/mysql/seeds/02-seeds.sql similarity index 100% rename from modules/mysql/tests/seeds/02-seeds.sql rename to tests/community/mysql/seeds/02-seeds.sql diff --git a/modules/mysql/tests/test_mysql.py b/tests/community/mysql/test_mysql.py similarity index 97% rename from modules/mysql/tests/test_mysql.py rename to tests/community/mysql/test_mysql.py index a2d2c2ec9..61086c981 100644 --- a/modules/mysql/tests/test_mysql.py +++ b/tests/community/mysql/test_mysql.py @@ -1,12 +1,12 @@ -from pathlib import Path import re +from pathlib import Path from unittest import mock import pytest import sqlalchemy +from testcontainers.community.mysql import MySqlContainer from testcontainers.core.utils import is_arm -from testcontainers.mysql import MySqlContainer @pytest.mark.inside_docker_check @@ -77,7 +77,7 @@ def test_docker_env_variables(): ], ) def test_mysql_dialect_expecting_error_on_mysql_prefix(dialect: str): - match = f"Please remove *.* prefix from dialect parameter" + match = "Please remove *.* prefix from dialect parameter" with pytest.raises(ValueError, match=match): _ = MySqlContainer("mariadb:10.6.5", dialect=dialect) diff --git a/modules/nats/tests/test_nats.py b/tests/community/nats/test_nats.py similarity index 95% rename from modules/nats/tests/test_nats.py rename to tests/community/nats/test_nats.py index 7b72ea81b..624c2b0e2 100644 --- a/modules/nats/tests/test_nats.py +++ b/tests/community/nats/test_nats.py @@ -1,10 +1,11 @@ -from testcontainers.nats import NatsContainer from uuid import uuid4 -import pytest +import pytest from nats import connect as nats_connect from nats.aio.client import Client as NATSClient +from testcontainers.community.nats import NatsContainer + async def get_client(container: NatsContainer) -> "NATSClient": """ @@ -22,7 +23,7 @@ def test_basic_container_ops(): with NatsContainer() as container: # Not sure how to get type information without doing this container: NatsContainer = container - h, p = container.nats_host_and_port() + h, _p = container.nats_host_and_port() assert h == "localhost" uri = container.nats_uri() management_uri = container.nats_management_uri() diff --git a/tests/community/nats/test_nats_jetstream.py b/tests/community/nats/test_nats_jetstream.py new file mode 100644 index 000000000..f9a00e101 --- /dev/null +++ b/tests/community/nats/test_nats_jetstream.py @@ -0,0 +1,33 @@ +from uuid import uuid4 + +import pytest +from nats import connect as nats_connect +from nats.aio.client import Client as NATSClient + +from testcontainers.community.nats import NatsContainer + + +async def get_client(container: NatsContainer) -> "NATSClient": + """ + Get a nats client. + + Returns: + client: Nats client to connect to the container. + """ + conn_string = container.nats_uri() + client = await nats_connect(conn_string) + return client + + +@pytest.mark.asyncio +async def test_jetstream_add_stream(anyio_backend): + with NatsContainer(jetstream=True) as container: + nc: NATSClient = await get_client(container) + + topic = str(uuid4()) + + js = nc.jetstream() + + await js.add_stream(name="test-stream", subjects=[topic]) + + await nc.close() diff --git a/modules/neo4j/tests/test_neo4j.py b/tests/community/neo4j/test_neo4j.py similarity index 88% rename from modules/neo4j/tests/test_neo4j.py rename to tests/community/neo4j/test_neo4j.py index 6058d34c2..43192ae19 100644 --- a/modules/neo4j/tests/test_neo4j.py +++ b/tests/community/neo4j/test_neo4j.py @@ -1,4 +1,4 @@ -from testcontainers.neo4j import Neo4jContainer +from testcontainers.community.neo4j import Neo4jContainer def test_docker_run_neo4j_latest(): diff --git a/modules/nginx/tests/test_nginx.py b/tests/community/nginx/test_nginx.py similarity index 85% rename from modules/nginx/tests/test_nginx.py rename to tests/community/nginx/test_nginx.py index 39fba5e97..79c781756 100644 --- a/modules/nginx/tests/test_nginx.py +++ b/tests/community/nginx/test_nginx.py @@ -1,6 +1,6 @@ import requests -from testcontainers.nginx import NginxContainer +from testcontainers.community.nginx import NginxContainer def test_docker_run_nginx(): diff --git a/modules/ollama/tests/test_ollama.py b/tests/community/ollama/test_ollama.py similarity index 97% rename from modules/ollama/tests/test_ollama.py rename to tests/community/ollama/test_ollama.py index 980dac00b..99836d139 100644 --- a/modules/ollama/tests/test_ollama.py +++ b/tests/community/ollama/test_ollama.py @@ -3,7 +3,8 @@ from pathlib import Path import requests -from testcontainers.ollama import OllamaContainer + +from testcontainers.community.ollama import OllamaContainer def random_string(length=6): diff --git a/tests/community/openfga/test_openfga.py b/tests/community/openfga/test_openfga.py new file mode 100644 index 000000000..031c14f49 --- /dev/null +++ b/tests/community/openfga/test_openfga.py @@ -0,0 +1,20 @@ +from sys import version_info + +import pytest + +from testcontainers.community.openfga import OpenFGAContainer + + +def test_openfga(): + if version_info < (3, 10): + with pytest.raises(NotImplementedError): + _test_openfga() + else: + _test_openfga() + + +def _test_openfga(): + with OpenFGAContainer("openfga/openfga:v1.8.4") as openfga: + client = openfga.get_client() + assert client + assert client.list_stores() diff --git a/modules/opensearch/tests/test_opensearch.py b/tests/community/opensearch/test_opensearch.py similarity index 96% rename from modules/opensearch/tests/test_opensearch.py rename to tests/community/opensearch/test_opensearch.py index 8a14b0b14..b5e140064 100644 --- a/modules/opensearch/tests/test_opensearch.py +++ b/tests/community/opensearch/test_opensearch.py @@ -1,7 +1,7 @@ -from testcontainers.opensearch import OpenSearchContainer - import pytest +from testcontainers.community.opensearch import OpenSearchContainer + @pytest.fixture(autouse=True) def disable_logging(): diff --git a/modules/oracle-free/tests/test_oracle.py b/tests/community/oracle/test_oracle.py similarity index 83% rename from modules/oracle-free/tests/test_oracle.py rename to tests/community/oracle/test_oracle.py index 0c6d8998e..5323f3c25 100644 --- a/modules/oracle-free/tests/test_oracle.py +++ b/tests/community/oracle/test_oracle.py @@ -1,8 +1,8 @@ import pytest import sqlalchemy +from testcontainers.community.oracle import OracleDbContainer from testcontainers.core.utils import is_arm -from testcontainers.oracle import OracleDbContainer @pytest.mark.skipif(is_arm(), reason="oracle-free container not available for ARM") @@ -11,7 +11,7 @@ def test_docker_run_oracle_with_system_password(): engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: test_val = 1 - result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + result = connection.execute(sqlalchemy.text(f"SELECT {test_val} FROM dual")) for row in result: assert row[0] == test_val @@ -22,7 +22,7 @@ def test_docker_run_oracle_with_username_password(): engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: test_val = 1 - result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + result = connection.execute(sqlalchemy.text(f"SELECT {test_val} FROM dual")) for row in result: assert row[0] == test_val @@ -33,7 +33,7 @@ def test_docker_run_oracle_with_custom_db_and_system_username_password(): engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: test_val = 1 - result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + result = connection.execute(sqlalchemy.text(f"SELECT {test_val} FROM dual")) for row in result: assert row[0] == test_val @@ -44,7 +44,7 @@ def test_docker_run_oracle_with_custom_db_and_app_username_password(): engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: test_val = 1 - result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + result = connection.execute(sqlalchemy.text(f"SELECT {test_val} FROM dual")) for row in result: assert row[0] == test_val @@ -55,7 +55,7 @@ def test_docker_run_oracle_with_default_db_and_app_username_password(): engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: test_val = 1 - result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + result = connection.execute(sqlalchemy.text(f"SELECT {test_val} FROM dual")) for row in result: assert row[0] == test_val @@ -66,7 +66,7 @@ def test_docker_run_oracle_with_cdb_and_system_username(): engine = sqlalchemy.create_engine(oracledb.get_connection_url()) with engine.begin() as connection: test_val = 1 - result = connection.execute(sqlalchemy.text("SELECT {} FROM dual".format(test_val))) + result = connection.execute(sqlalchemy.text(f"SELECT {test_val} FROM dual")) for row in result: assert row[0] == test_val diff --git a/modules/postgres/tests/fixtures/postgres_create_example_table.sql b/tests/community/postgres/fixtures/postgres_create_example_table.sql similarity index 100% rename from modules/postgres/tests/fixtures/postgres_create_example_table.sql rename to tests/community/postgres/fixtures/postgres_create_example_table.sql diff --git a/modules/postgres/tests/test_postgres.py b/tests/community/postgres/test_postgres.py similarity index 85% rename from modules/postgres/tests/test_postgres.py rename to tests/community/postgres/test_postgres.py index c2e7d9822..e591e6b38 100644 --- a/modules/postgres/tests/test_postgres.py +++ b/tests/community/postgres/test_postgres.py @@ -3,7 +3,7 @@ import pytest import sqlalchemy -from testcontainers.postgres import PostgresContainer +from testcontainers.community.postgres import PostgresContainer # https://www.postgresql.org/support/versioning/ @@ -133,3 +133,21 @@ def test_none_driver_urls(): url = container.get_connection_url(driver=None) assert url == expected_url + + +def test_psycopg_versions(): + """Test that both psycopg2 and psycopg (v2 and v3) work with the container.""" + + postgres_container = PostgresContainer("postgres:16-alpine", driver="psycopg2") + with postgres_container as postgres: + engine = sqlalchemy.create_engine(postgres.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("SELECT 1 as test")) + assert result.scalar() == 1 + + postgres_container = PostgresContainer("postgres:16-alpine", driver="psycopg") + with postgres_container as postgres: + engine = sqlalchemy.create_engine(postgres.get_connection_url()) + with engine.begin() as connection: + result = connection.execute(sqlalchemy.text("SELECT 1 as test")) + assert result.scalar() == 1 diff --git a/modules/qdrant/tests/test_config.yaml b/tests/community/qdrant/test_config.yaml similarity index 100% rename from modules/qdrant/tests/test_config.yaml rename to tests/community/qdrant/test_config.yaml diff --git a/modules/qdrant/tests/test_qdrant.py b/tests/community/qdrant/test_qdrant.py similarity index 97% rename from modules/qdrant/tests/test_qdrant.py rename to tests/community/qdrant/test_qdrant.py index d3b59e57c..61fabbad1 100644 --- a/modules/qdrant/tests/test_qdrant.py +++ b/tests/community/qdrant/test_qdrant.py @@ -1,10 +1,11 @@ -import pytest -from testcontainers.qdrant import QdrantContainer import uuid -from grpc import RpcError from pathlib import Path +import pytest import qdrant_client +from grpc import RpcError + +from testcontainers.community.qdrant import QdrantContainer def test_docker_run_qdrant(): diff --git a/modules/rabbitmq/tests/test_rabbitmq.py b/tests/community/rabbitmq/test_rabbitmq.py similarity index 78% rename from modules/rabbitmq/tests/test_rabbitmq.py rename to tests/community/rabbitmq/test_rabbitmq.py index c0c1894d8..8b283588e 100644 --- a/modules/rabbitmq/tests/test_rabbitmq.py +++ b/tests/community/rabbitmq/test_rabbitmq.py @@ -4,7 +4,7 @@ import pika import pytest -from testcontainers.rabbitmq import RabbitMqContainer +from testcontainers.community.rabbitmq import RabbitMqContainer QUEUE = "test-q" EXCHANGE = "test-exchange" @@ -13,12 +13,12 @@ @pytest.mark.parametrize( - argnames=["port", "username", "password", "vhost"], + argnames="port, username, password, vhost", argvalues=[ - [None, None, None, None], # use the defaults - [5673, None, None, None], # test with custom port - [None, "my_test_user", "my_secret_password", None], # test with custom credentials - [None, None, None, "vhost"], # test with custom vhost + (None, None, None, None), # use the defaults + (5673, None, None, None), # test with custom port + (None, "my_test_user", "my_secret_password", None), # test with custom credentials + (None, None, None, "vhost"), # test with custom vhost ], ) def test_docker_run_rabbitmq( @@ -44,7 +44,7 @@ def test_docker_run_rabbitmq( # create exchange and queue: channel = connection.channel() channel.exchange_declare(exchange=EXCHANGE, exchange_type="topic") - channel.queue_declare(QUEUE, arguments={}) + channel.queue_declare(QUEUE, durable=True, arguments={}) channel.queue_bind(QUEUE, EXCHANGE, ROUTING_KEY) # publish message: diff --git a/modules/redis/tests/test_redis.py b/tests/community/redis/test_redis.py similarity index 80% rename from modules/redis/tests/test_redis.py rename to tests/community/redis/test_redis.py index bd8e244c5..5185aa88d 100644 --- a/modules/redis/tests/test_redis.py +++ b/tests/community/redis/test_redis.py @@ -1,7 +1,9 @@ import time -from testcontainers.redis import RedisContainer, AsyncRedisContainer import pytest +import redis + +from testcontainers.community.redis import AsyncRedisContainer, RedisContainer def test_docker_run_redis(): @@ -24,6 +26,16 @@ def test_docker_run_redis_with_password(): assert client.get("hello") == "world" +def test_docker_run_start_fails(monkeypatch: pytest.MonkeyPatch): + # Patch config to speed up the test. + monkeypatch.setattr("testcontainers.core.config.testcontainers_config.max_tries", 0.3) + monkeypatch.setattr("testcontainers.core.config.testcontainers_config.sleep_time", 0.02) + # Use a bogus image to make the startup check fail. + config = RedisContainer(image="hello-world") + with pytest.raises(redis.exceptions.ConnectionError, match="Could not connect"): + config.start() + + pytest.mark.usefixtures("anyio_backend") diff --git a/modules/registry/tests/test_registry.py b/tests/community/registry/test_registry.py similarity index 83% rename from modules/registry/tests/test_registry.py rename to tests/community/registry/test_registry.py index 0aa568ee5..355bcce24 100644 --- a/modules/registry/tests/test_registry.py +++ b/tests/community/registry/test_registry.py @@ -1,13 +1,13 @@ from requests import Response, get from requests.auth import HTTPBasicAuth -from testcontainers.registry import DockerRegistryContainer +from testcontainers.community.registry import DockerRegistryContainer REGISTRY_USERNAME: str = "foo" REGISTRY_PASSWORD: str = "bar" -def test_registry(): +def test_registry() -> None: with DockerRegistryContainer().with_bind_ports(5000, 5000) as registry_container: url: str = f"http://{registry_container.get_registry()}/v2/_catalog" @@ -16,7 +16,7 @@ def test_registry(): assert response.status_code == 200 -def test_registry_with_authentication(): +def test_registry_with_authentication() -> None: with DockerRegistryContainer(username=REGISTRY_USERNAME, password=REGISTRY_PASSWORD).with_bind_ports( 5000, 5000 ) as registry_container: diff --git a/modules/scylla/tests/test_scylla.py b/tests/community/scylla/test_scylla.py similarity index 74% rename from modules/scylla/tests/test_scylla.py rename to tests/community/scylla/test_scylla.py index 3d1ecf44d..5209c3359 100644 --- a/modules/scylla/tests/test_scylla.py +++ b/tests/community/scylla/test_scylla.py @@ -1,4 +1,4 @@ -from testcontainers.scylla import ScyllaContainer +from testcontainers.community.scylla import ScyllaContainer def test_docker_run_scylla(): @@ -6,8 +6,7 @@ def test_docker_run_scylla(): cluster = scylla.get_cluster() with cluster.connect() as session: session.execute( - "CREATE KEYSPACE keyspace1 WITH replication = " - "{'class': 'SimpleStrategy', 'replication_factor': '1'};" + "CREATE KEYSPACE keyspace1 WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'};" ) session.execute("CREATE TABLE keyspace1.table1 (key1 int, key2 int, PRIMARY KEY (key1));") session.execute("INSERT INTO keyspace1.table1 (key1,key2) values (1,2);") diff --git a/modules/selenium/tests/test_selenium.py b/tests/community/selenium/test_selenium.py similarity index 95% rename from modules/selenium/tests/test_selenium.py rename to tests/community/selenium/test_selenium.py index ac243c27b..9d0ad0b2b 100644 --- a/modules/selenium/tests/test_selenium.py +++ b/tests/community/selenium/test_selenium.py @@ -6,8 +6,8 @@ from selenium.webdriver import DesiredCapabilities from selenium.webdriver.common.by import By +from testcontainers.community.selenium import BrowserWebDriverContainer from testcontainers.core.utils import is_arm -from testcontainers.selenium import BrowserWebDriverContainer @pytest.mark.parametrize("caps", [DesiredCapabilities.CHROME, DesiredCapabilities.FIREFOX]) diff --git a/modules/sftp/tests/test_sftp.py b/tests/community/sftp/test_sftp.py similarity index 94% rename from modules/sftp/tests/test_sftp.py rename to tests/community/sftp/test_sftp.py index e3dab2e3b..f9e46c442 100644 --- a/modules/sftp/tests/test_sftp.py +++ b/tests/community/sftp/test_sftp.py @@ -4,12 +4,11 @@ import paramiko import pytest -from testcontainers.sftp import SFTPContainer, SFTPUser +from testcontainers.community.sftp import SFTPContainer, SFTPUser def test_sftp_login_with_default_basic_auth(): with SFTPContainer() as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -19,12 +18,13 @@ def test_sftp_login_with_default_basic_auth(): port=host_port, username=sftp_container.users[0].name, password=sftp_container.users[0].password, + allow_agent=False, + look_for_keys=False, ) def test_sftp_login_with_default_keypair_auth(): with SFTPContainer() as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -40,7 +40,6 @@ def test_sftp_login_with_default_keypair_auth(): def test_sftp_login_with_custom_user_basic_auth(): user = SFTPUser(name="custom", password="custom_password") with SFTPContainer(users=[user]) as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -50,13 +49,14 @@ def test_sftp_login_with_custom_user_basic_auth(): port=host_port, username=user.name, password=user.password, + allow_agent=False, + look_for_keys=False, ) def test_sftp_login_with_custom_user_keypair_auth(): user = SFTPUser.with_keypair(name="custom") with SFTPContainer(users=[user]) as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -72,7 +72,6 @@ def test_sftp_login_with_custom_user_keypair_auth(): def test_sftp_login_with_custom_user_password_and_keypair_auth(): user = SFTPUser.with_keypair(name="custom", password="custom_password") with SFTPContainer(users=[user]) as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -88,7 +87,6 @@ def test_sftp_login_with_custom_user_password_and_keypair_auth(): def test_sftp_user_can_upload(): with SFTPContainer() as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -98,6 +96,8 @@ def test_sftp_user_can_upload(): port=host_port, username=sftp_container.users[0].name, password=sftp_container.users[0].password, + allow_agent=False, + look_for_keys=False, ) sftp = ssh.open_sftp() sftp.chdir("upload") @@ -119,7 +119,6 @@ def test_sftp_user_can_download_from_mounted(tmp_path: Path): temp_file.write_text("test") user = SFTPUser.with_keypair(name="custom", mount_dir=temp_dir.as_posix()) with SFTPContainer(users=[user]) as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -143,7 +142,6 @@ def test_sftp_user_cant_upload_to_root(tmp_path: Path): temp_file = temp_dir / "test.txt" temp_file.write_text("test") with SFTPContainer() as sftp_container: - sftp_container.start() host_ip = sftp_container.get_container_host_ip() host_port = sftp_container.get_exposed_sftp_port() ssh = paramiko.SSHClient() @@ -153,6 +151,8 @@ def test_sftp_user_cant_upload_to_root(tmp_path: Path): port=host_port, username=sftp_container.users[0].name, password=sftp_container.users[0].password, + allow_agent=False, + look_for_keys=False, ) sftp = ssh.open_sftp() with pytest.raises(PermissionError): diff --git a/tests/community/toxiproxy/test_toxiproxy.py b/tests/community/toxiproxy/test_toxiproxy.py new file mode 100644 index 000000000..5c006bcac --- /dev/null +++ b/tests/community/toxiproxy/test_toxiproxy.py @@ -0,0 +1,41 @@ +import time + +import requests + +from testcontainers.community.nginx import NginxContainer +from testcontainers.community.toxiproxy import ToxiproxyContainer +from testcontainers.core.network import Network + + +def test_toxiproxy_proxies_traffic_and_injects_latency(): + with Network() as network: + nginx = NginxContainer("nginx:alpine").with_network(network).with_network_aliases("nginx") + toxiproxy = ToxiproxyContainer().with_network(network) + with nginx, toxiproxy: + proxy = toxiproxy.create_proxy("nginx", "nginx:80") + url = f"http://{proxy.host}:{proxy.proxy_port}/" + + # Traffic flows through the proxy to the upstream nginx. + response = requests.get(url, timeout=10) + assert response.status_code == 200 + assert "nginx" in response.text.lower() + + # Injecting 1s of downstream latency slows the response down. + proxy.add_toxic("latency", {"latency": 1000}) + start = time.monotonic() + assert requests.get(url, timeout=10).status_code == 200 + assert time.monotonic() - start >= 1.0 + + +def test_create_proxy_runs_out_of_ports(): + toxiproxy = ToxiproxyContainer() + with toxiproxy: + # Exhaust the available proxy port range without a real upstream; the + # control API happily registers proxies pointing at an unused address. + for i in range(ToxiproxyContainer.FIRST_PROXY_PORT, ToxiproxyContainer.LAST_PROXY_PORT + 1): + toxiproxy.create_proxy(f"p{i}", "example:1234") + try: + toxiproxy.create_proxy("one-too-many", "example:1234") + raise AssertionError("expected RuntimeError when out of proxy ports") + except RuntimeError: + pass diff --git a/modules/trino/tests/test_trino.py b/tests/community/trino/test_trino.py similarity index 88% rename from modules/trino/tests/test_trino.py rename to tests/community/trino/test_trino.py index c1a70230b..0d593fc58 100644 --- a/modules/trino/tests/test_trino.py +++ b/tests/community/trino/test_trino.py @@ -1,6 +1,7 @@ -from testcontainers.trino import TrinoContainer from trino.dbapi import connect +from testcontainers.community.trino import TrinoContainer + def test_docker_run_trino(): container = TrinoContainer("trinodb/trino:451") diff --git a/tests/community/valkey/test_valkey.py b/tests/community/valkey/test_valkey.py new file mode 100644 index 000000000..3e5d70024 --- /dev/null +++ b/tests/community/valkey/test_valkey.py @@ -0,0 +1,84 @@ +import socket + +from testcontainers.community.valkey import ValkeyContainer + + +def test_docker_run_valkey(): + with ValkeyContainer() as valkey: + host = valkey.get_host() + port = valkey.get_exposed_port() + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((host, port)) + s.sendall(b"*1\r\n$4\r\nPING\r\n") + response = s.recv(1024) + assert b"+PONG" in response + + +def test_docker_run_valkey_with_password(): + with ValkeyContainer().with_password("mypass") as valkey: + host = valkey.get_host() + port = valkey.get_exposed_port() + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((host, port)) + # Authenticate + s.sendall(b"*2\r\n$4\r\nAUTH\r\n$6\r\nmypass\r\n") + auth_response = s.recv(1024) + assert b"+OK" in auth_response + + # Test SET command + s.sendall(b"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n") + set_response = s.recv(1024) + assert b"+OK" in set_response + + # Test GET command + s.sendall(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n") + get_response = s.recv(1024) + assert b"world" in get_response + + +def test_get_connection_url(): + with ValkeyContainer() as valkey: + url = valkey.get_connection_url() + assert url.startswith("valkey://") + assert str(valkey.get_exposed_port()) in url + + +def test_get_connection_url_with_password(): + with ValkeyContainer().with_password("secret") as valkey: + url = valkey.get_connection_url() + assert url.startswith("valkey://:secret@") + assert str(valkey.get_exposed_port()) in url + + +def test_with_image_tag(): + container = ValkeyContainer().with_image_tag("8.0") + assert container.image == "valkey/valkey:8.0" + + +def test_with_bundle(): + container = ValkeyContainer().with_bundle() + assert container.image == "valkey/valkey-bundle:latest" + + +def test_with_bundle_and_tag(): + container = ValkeyContainer().with_bundle().with_image_tag("9.0") + assert container.image == "valkey/valkey-bundle:9.0" + + +def test_with_tag_and_bundle(): + container = ValkeyContainer().with_image_tag("8.0").with_bundle() + assert container.image == "valkey/valkey-bundle:8.0" + + +def test_bundle_starts(): + with ValkeyContainer().with_bundle() as valkey: + host = valkey.get_host() + port = valkey.get_exposed_port() + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((host, port)) + s.sendall(b"*1\r\n$4\r\nPING\r\n") + response = s.recv(1024) + assert b"+PONG" in response diff --git a/modules/vault/tests/test_vault.py b/tests/community/vault/test_vault.py similarity index 95% rename from modules/vault/tests/test_vault.py rename to tests/community/vault/test_vault.py index 54017d2f9..c1006536d 100644 --- a/modules/vault/tests/test_vault.py +++ b/tests/community/vault/test_vault.py @@ -1,5 +1,6 @@ import hvac -from testcontainers.vault import VaultContainer + +from testcontainers.community.vault import VaultContainer def test_docker_run_vault(): diff --git a/modules/weaviate/tests/test_weaviate.py b/tests/community/weaviate/test_weaviate.py similarity index 96% rename from modules/weaviate/tests/test_weaviate.py rename to tests/community/weaviate/test_weaviate.py index 40728d4aa..d91e9da92 100644 --- a/modules/weaviate/tests/test_weaviate.py +++ b/tests/community/weaviate/test_weaviate.py @@ -1,6 +1,7 @@ -from testcontainers.weaviate import WeaviateContainer import weaviate +from testcontainers.community.weaviate import WeaviateContainer + def test_docker_run_weaviate(): with WeaviateContainer() as container: diff --git a/tests/core/__init__.py b/tests/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/core/_local_registry_container.py b/tests/core/_local_registry_container.py new file mode 100644 index 000000000..c547ee58c --- /dev/null +++ b/tests/core/_local_registry_container.py @@ -0,0 +1,99 @@ +"""Slim, test-private copy of ``DockerRegistryContainer``. + +``core/tests`` should not import from sibling modules (``testcontainers.registry`` in this case). +This keeps ``core`` self-contained as the package layout evolves into a separate ``core`` / ``community`` split. + +We keep the integration coverage by inlining only the bits needed to spin up an authenticated ``registry:2``. +That means: htpasswd creds setup, an HTTP readiness probe, and an accessor for the published address. + +This is intentional duplication of the production class. +Behavioural changes here are not expected to track ``testcontainers.registry`` and vice versa. +""" + +from __future__ import annotations + +import time +from io import BytesIO +from tarfile import TarFile, TarInfo +from typing import TYPE_CHECKING, Any, Optional + +import bcrypt +from requests import get +from requests.auth import HTTPBasicAuth +from requests.exceptions import ConnectionError as RequestsConnectionError +from requests.exceptions import ReadTimeout + +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import wait_container_is_ready + +if TYPE_CHECKING: + from requests import Response + + +class _LocalRegistryContainer(DockerContainer): + """Private ``registry:2`` fixture used by ``core/tests`` only. + + Mirrors the public ``DockerRegistryContainer`` API surface used by tests. + That is: ``__init__``, ``start``, and ``get_registry``. + """ + + credentials_path: str = "/htpasswd/credentials.txt" + + def __init__( + self, + image: str = "registry:2", + port: int = 5000, + username: Optional[str] = None, + password: Optional[str] = None, + **kwargs: Any, + ) -> None: + super().__init__(image=image, **kwargs) + self.port: int = port + self.username: Optional[str] = username + self.password: Optional[str] = password + self.with_exposed_ports(self.port) + + def _copy_credentials(self) -> None: + if self.password is None: + raise ValueError("Password cannot be None") + hashed_password: str = bcrypt.hashpw( + self.password.encode("utf-8"), + bcrypt.gensalt(rounds=12, prefix=b"2a"), + ).decode("utf-8") + content: bytes = f"{self.username}:{hashed_password}".encode() + + with BytesIO() as tar_archive_object, TarFile(fileobj=tar_archive_object, mode="w") as tmp_tarfile: + tarinfo: TarInfo = TarInfo(name=self.credentials_path) + tarinfo.size = len(content) + tarinfo.mtime = int(time.time()) + + tmp_tarfile.addfile(tarinfo, BytesIO(content)) + tar_archive_object.seek(0) + self.get_wrapped_container().put_archive("/", tar_archive_object) + + @wait_container_is_ready(RequestsConnectionError, ReadTimeout) + def _readiness_probe(self) -> None: + url: str = f"http://{self.get_registry()}/v2" + if self.username and self.password: + auth_response: Response = get(url, auth=HTTPBasicAuth(self.username, self.password), timeout=1) + auth_response.raise_for_status() + else: + response: Response = get(url, timeout=1) + response.raise_for_status() + + def start(self) -> _LocalRegistryContainer: + if self.username and self.password: + self.with_env("REGISTRY_AUTH_HTPASSWD_REALM", "local-registry") + self.with_env("REGISTRY_AUTH_HTPASSWD_PATH", self.credentials_path) + super().start() + self._copy_credentials() + else: + super().start() + + self._readiness_probe() + return self + + def get_registry(self) -> str: + host: str = self.get_container_host_ip() + port: int = self.get_exposed_port(self.port) + return f"{host}:{port}" diff --git a/core/tests/compose_fixtures/basic/docker-compose.yaml b/tests/core/compose_fixtures/basic/docker-compose.yaml similarity index 100% rename from core/tests/compose_fixtures/basic/docker-compose.yaml rename to tests/core/compose_fixtures/basic/docker-compose.yaml diff --git a/tests/core/compose_fixtures/basic/hello.yaml b/tests/core/compose_fixtures/basic/hello.yaml new file mode 100644 index 000000000..594a5110c --- /dev/null +++ b/tests/core/compose_fixtures/basic/hello.yaml @@ -0,0 +1,8 @@ +services: + hello: + image: alpine:latest + init: true + command: + - sh + - -c + - 'while true; do echo "Hello from Docker!"; sleep 1; done' diff --git a/core/tests/compose_fixtures/basic_multiple/docker-compose.yaml b/tests/core/compose_fixtures/basic_multiple/docker-compose.yaml similarity index 100% rename from core/tests/compose_fixtures/basic_multiple/docker-compose.yaml rename to tests/core/compose_fixtures/basic_multiple/docker-compose.yaml diff --git a/core/tests/compose_fixtures/basic_volume/docker-compose.yaml b/tests/core/compose_fixtures/basic_volume/docker-compose.yaml similarity index 100% rename from core/tests/compose_fixtures/basic_volume/docker-compose.yaml rename to tests/core/compose_fixtures/basic_volume/docker-compose.yaml diff --git a/core/tests/compose_fixtures/port_multiple/compose.yaml b/tests/core/compose_fixtures/port_multiple/compose.yaml similarity index 92% rename from core/tests/compose_fixtures/port_multiple/compose.yaml rename to tests/core/compose_fixtures/port_multiple/compose.yaml index e8e147bbd..21a4f5e8c 100644 --- a/core/tests/compose_fixtures/port_multiple/compose.yaml +++ b/tests/core/compose_fixtures/port_multiple/compose.yaml @@ -6,7 +6,7 @@ services: - '81' - '82' - target: 80 - host_ip: 127.0.0.1 + published: "5000-5999" protocol: tcp command: - sh @@ -18,7 +18,7 @@ services: init: true ports: - target: 80 - host_ip: 127.0.0.1 + published: "5000-5999" protocol: tcp command: - sh diff --git a/core/tests/compose_fixtures/port_single/compose.yaml b/tests/core/compose_fixtures/port_single/compose.yaml similarity index 87% rename from core/tests/compose_fixtures/port_single/compose.yaml rename to tests/core/compose_fixtures/port_single/compose.yaml index 88c19ab61..362a3c6b2 100644 --- a/core/tests/compose_fixtures/port_single/compose.yaml +++ b/tests/core/compose_fixtures/port_single/compose.yaml @@ -4,7 +4,6 @@ services: init: true ports: - target: 80 - host_ip: 127.0.0.1 protocol: tcp command: - sh diff --git a/core/tests/compose_fixtures/profile_support/compose.yaml b/tests/core/compose_fixtures/profile_support/compose.yaml similarity index 100% rename from core/tests/compose_fixtures/profile_support/compose.yaml rename to tests/core/compose_fixtures/profile_support/compose.yaml diff --git a/core/tests/conftest.py b/tests/core/conftest.py similarity index 70% rename from core/tests/conftest.py rename to tests/core/conftest.py index a86faa109..4f0f77a7e 100644 --- a/core/tests/conftest.py +++ b/tests/core/conftest.py @@ -1,9 +1,11 @@ +import sys from pathlib import Path +from pprint import pprint +from typing import Callable import pytest -from typing import Callable -from testcontainers.core.container import DockerClient -import sys + +from testcontainers.core.docker_client import DockerClient PROJECT_DIR = Path(__file__).parent.parent.parent.resolve() @@ -47,6 +49,19 @@ def _check_for_image(image_short_id: str, cleaned: bool) -> None: client = DockerClient() images = client.client.images.list() found = any(image.short_id.endswith(image_short_id) for image in images) - assert found is not cleaned, f'Image {image_short_id} was {"found" if cleaned else "not found"}' + assert found is not cleaned, f"Image {image_short_id} was {'found' if cleaned else 'not found'}" return _check_for_image + + +@pytest.fixture +def show_container_attributes() -> Callable[..., None]: + """Wrap the show_container_attributes function in a fixture""" + + def _show_container_attributes(container_id: str) -> None: + """Print the attributes of a container""" + client = DockerClient().client + data = client.containers.get(container_id).attrs + pprint(data) + + return _show_container_attributes diff --git a/core/tests/image_fixtures/busybox/Dockerfile b/tests/core/image_fixtures/busybox/Dockerfile similarity index 100% rename from core/tests/image_fixtures/busybox/Dockerfile rename to tests/core/image_fixtures/busybox/Dockerfile diff --git a/core/tests/image_fixtures/sample/Dockerfile b/tests/core/image_fixtures/sample/Dockerfile similarity index 100% rename from core/tests/image_fixtures/sample/Dockerfile rename to tests/core/image_fixtures/sample/Dockerfile diff --git a/core/tests/test_auth.py b/tests/core/test_auth.py similarity index 97% rename from core/tests/test_auth.py rename to tests/core/test_auth.py index 0e1639811..f518fff29 100644 --- a/core/tests/test_auth.py +++ b/tests/core/test_auth.py @@ -1,7 +1,8 @@ import json + import pytest -from testcontainers.core.auth import parse_docker_auth_config, DockerAuthInfo +from testcontainers.core.auth import DockerAuthInfo, parse_docker_auth_config def test_parse_docker_auth_config_encoded(): diff --git a/tests/core/test_community_definition.py b/tests/core/test_community_definition.py new file mode 100644 index 000000000..2f1138f02 --- /dev/null +++ b/tests/core/test_community_definition.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest + +try: + import tomllib # type: ignore[import-not-found] +except ImportError: + import tomli as tomllib # type: ignore[no-retyping, unused-ignore] + + +BASE_DIR = Path(__file__).parent.parent.parent.resolve() +PYPROJECT_TOML = BASE_DIR / "pyproject.toml" +SRC_DIR = BASE_DIR / "src" / "testcontainers" / "community" +DOCS_DIR = BASE_DIR / "docs" / "community" +TESTS_DIR = BASE_DIR / "tests" / "community" + +SRC_MODULES = frozenset({f.stem for f in SRC_DIR.iterdir() if f.is_dir()}) +DOCS_MODULES = frozenset({f.stem for f in DOCS_DIR.glob("*.rst")}) +TESTS_MODULES = frozenset({f.stem for f in TESTS_DIR.iterdir() if f.is_dir()}) + +ALL_MODULES = frozenset((SRC_MODULES | DOCS_MODULES | TESTS_MODULES) - {"__init__", "__pycache__"}) + + +@pytest.fixture(scope="session") +def pyproject_toml_extra() -> frozenset[str]: + with PYPROJECT_TOML.open("rb") as f: + pyproject = tomllib.load(f) + return frozenset(pyproject["project"]["optional-dependencies"].keys()) + + +@pytest.mark.parametrize("module", sorted(ALL_MODULES)) +def test_community_definition(module: str, pyproject_toml_extra: frozenset[str]) -> None: + """ + Check that community modules defined in docs, src, tests and pyproject.toml extras are in sync + """ + assert module in SRC_MODULES, f"{module} is missing in src/testcontainers/community" + assert module in DOCS_MODULES, f"{module} is missing in docs/community" + assert module in TESTS_MODULES, f"{module} is missing in tests/community" + assert module in pyproject_toml_extra, f"{module} is missing in pyproject.toml extras" diff --git a/core/tests/test_compose.py b/tests/core/test_compose.py similarity index 69% rename from core/tests/test_compose.py rename to tests/core/test_compose.py index 9279ce3f7..5f9fcf435 100644 --- a/core/tests/test_compose.py +++ b/tests/core/test_compose.py @@ -1,17 +1,25 @@ +import logging import subprocess from pathlib import Path from re import split from time import sleep -from typing import Union, Optional -from urllib.request import urlopen, Request +from typing import Optional, Union +from urllib.request import Request, urlopen import pytest from pytest_mock import MockerFixture -from testcontainers.compose import DockerCompose, ContainerIsNotRunning, NoSuchPortExposed +from testcontainers.compose import ComposeContainer, DockerCompose +from testcontainers.core.docker_client import is_podman +from testcontainers.core.exceptions import ContainerIsNotRunning, NoSuchPortExposed FIXTURES = Path(__file__).parent.joinpath("compose_fixtures") +_skip_if_podman_port_range = pytest.mark.skipif( + is_podman(), + reason="Podman does not support published port ranges (e.g. '5000-5999')", +) + def test_compose_no_file_name(): basic = DockerCompose(context=FIXTURES / "basic") @@ -146,10 +154,10 @@ def test_compose_logs(): # either the line is blank or the first column (|-separated) contains the service name # this is a safe way to split the string # docker changes the prefix between versions 24 and 25 - assert not line or container.Service in next(iter(line.split("|")), None) + assert not line or container.Service in next(iter(line.split("|"))) -def test_compose_volumes(): +def test_compose_volumes(caplog): _file_in_volume = "/var/lib/example/data/hello" volumes = DockerCompose(context=FIXTURES / "basic_volume", keep_volumes=True) with volumes: @@ -161,13 +169,16 @@ def test_compose_volumes(): # execute another time to confirm the file is still there, but we're not keeping the volumes this time volumes.keep_volumes = False with volumes: - stdout, stderr, exitcode = volumes.exec_in_container(["cat", _file_in_volume], "alpine") + stdout, _stderr, exitcode = volumes.exec_in_container(["cat", _file_in_volume], "alpine") assert exitcode == 0 assert "hello" in stdout # third time we expect the file to be missing - with volumes, pytest.raises(subprocess.CalledProcessError): - volumes.exec_in_container(["cat", _file_in_volume], "alpine") + with caplog.at_level( + logging.CRITICAL, logger="testcontainers.compose.compose" + ): # suppress expected error logs about missing volume + with volumes, pytest.raises(subprocess.CalledProcessError): + volumes.exec_in_container(["cat", _file_in_volume], "alpine") # noinspection HttpUrlsUsage @@ -184,6 +195,7 @@ def test_compose_ports(): # noinspection HttpUrlsUsage +@_skip_if_podman_port_range def test_compose_multiple_containers_and_ports(): """test for the logic encapsulated in 'one' function @@ -196,10 +208,11 @@ def test_compose_multiple_containers_and_ports(): e.match("get_container failed") e.match("not exactly 1 container") - assert multiple.get_container("alpine") - assert multiple.get_container("alpine2") + multiple.get_container("alpine") + multiple.get_container("alpine2") a2p = multiple.get_service_port("alpine2") + assert a2p is not None assert a2p > 0 # > 1024 with pytest.raises(NoSuchPortExposed) as e: @@ -215,7 +228,7 @@ def test_compose_multiple_containers_and_ports(): try: # this fails when ipv6 is enabled and docker is forwarding for both 4 + 6 multiple.get_container(service_name="alpine").get_publisher(by_port=81, prefer_ip_version="IPv6") - except: # noqa + except: # noqa: E722 pass ports = [ @@ -280,6 +293,7 @@ def test_exec_in_container(): # noinspection HttpUrlsUsage +@_skip_if_podman_port_range def test_exec_in_container_multiple(): """same as above, except we exec into a particular service""" multiple = DockerCompose(context=FIXTURES / "port_multiple") @@ -355,7 +369,7 @@ def fetch(req: Union[Request, str]): @pytest.mark.parametrize( - argnames=["profiles", "running", "not_running"], + argnames="profiles, running, not_running", argvalues=[ pytest.param(None, ["runs-always"], ["runs-profile-a", "runs-profile-b"], id="default"), pytest.param( @@ -376,3 +390,114 @@ def test_compose_profile_support(profiles: Optional[list[str]], running: list[st for service in not_running: with pytest.raises(ContainerIsNotRunning): compose.get_container(service) + + +@pytest.mark.parametrize( + "docker_host_env, url, expected_url", + [ + pytest.param("ssh://user@10.0.0.5", "0.0.0.0", "10.0.0.5", id="ssh_replaces_wildcard"), + pytest.param("ssh://user@10.0.0.5", "127.0.0.1", "10.0.0.5", id="ssh_replaces_loopback"), + pytest.param("ssh://user@10.0.0.5", "::", "10.0.0.5", id="ssh_replaces_ipv6_any"), + pytest.param("ssh://user@10.0.0.5", "", "10.0.0.5", id="ssh_replaces_empty"), + pytest.param("ssh://user@10.0.0.5", None, "10.0.0.5", id="ssh_replaces_none"), + pytest.param("tcp://localhost:2375", "0.0.0.0", "0.0.0.0", id="non_ssh_keeps_original"), + ], +) +def test_compose_normalize_rewrites_local_url_for_ssh_docker_host( + monkeypatch: pytest.MonkeyPatch, docker_host_env: str, url: str, expected_url: str +) -> None: + """When DOCKER_HOST is an SSH URL, normalize() should replace local addresses + with the remote hostname — exercising the real get_docker_host_hostname() path.""" + from testcontainers.compose.compose import PublishedPortModel + from testcontainers.core.config import testcontainers_config as tc_config + + monkeypatch.setenv("DOCKER_HOST", docker_host_env) + monkeypatch.setattr(tc_config, "tc_properties_get_tc_host", lambda: None) + + model = PublishedPortModel(URL=url, TargetPort=80, PublishedPort=9999, Protocol="tcp") + result = model.normalize() + assert result.URL == expected_url + assert result.PublishedPort == 9999 + + +@pytest.mark.parametrize( + "docker_on_path, podman_on_path, podman_detected, expected", + [ + pytest.param(True, True, True, "docker", id="docker_wins_over_podman"), + pytest.param(False, True, True, "podman", id="podman_when_no_docker_and_podman_daemon"), + pytest.param(False, False, True, "docker", id="fallback_to_docker_when_no_podman_binary"), + ], +) +def test_default_compose_binary( + monkeypatch: pytest.MonkeyPatch, + docker_on_path: bool, + podman_on_path: bool, + podman_detected: bool, + expected: str, +) -> None: + from testcontainers.compose import compose as compose_module + + paths = { + "docker": "/usr/bin/docker" if docker_on_path else None, + "podman": "/usr/bin/podman" if podman_on_path else None, + } + monkeypatch.setattr("testcontainers.compose.compose.shutil.which", lambda name: paths.get(name)) + monkeypatch.setattr(compose_module, "is_podman", lambda: podman_detected) + + assert compose_module._default_compose_binary() == expected + + +def test_container_info(): + """Test get_container_info functionality""" + basic = DockerCompose(context=FIXTURES / "basic") + with basic: + container = basic.get_container("alpine") + + info = container.get_container_info() + assert info is not None + assert info.Id is not None + assert info.Name is not None + assert info.Image is not None + + assert info.State is not None + assert info.State.Status == "running" + assert info.State.Running is True + assert info.State.Pid is not None + + assert info.Config is not None + assert info.Config.Image is not None + assert info.Config.Hostname is not None + + network_settings = info.get_network_settings() + assert network_settings is not None + assert network_settings.Networks is not None + + info2 = container.get_container_info() + assert info is info2 + + +def test_container_info_network_details(): + """Test network details in container info""" + single = DockerCompose(context=FIXTURES / "port_single") + with single: + container = single.get_container() + info = container.get_container_info() + assert info is not None + + network_settings = info.get_network_settings() + assert network_settings is not None + + if network_settings.Networks: + # Test first network + _network_name, network = next(iter(network_settings.Networks.items())) + assert network.IPAddress is not None + assert network.Gateway is not None + assert network.NetworkID is not None + + +def test_container_info_none_when_no_docker_compose(): + """Test get_container_info returns None when docker_compose reference is missing""" + + container = ComposeContainer() + info = container.get_container_info() + assert info is None diff --git a/tests/core/test_config.py b/tests/core/test_config.py new file mode 100644 index 000000000..da3e2495b --- /dev/null +++ b/tests/core/test_config.py @@ -0,0 +1,242 @@ +import logging +import tempfile +from unittest.mock import Mock + +import pytest + +from testcontainers.core.config import ( + TC_FILE, + TestcontainersConfiguration, + get_docker_socket, + get_user_overwritten_connection_mode, +) +from testcontainers.core.config import ( + TestcontainersConfiguration as TCC, +) + + +def test_read_tc_properties(monkeypatch: pytest.MonkeyPatch) -> None: + with tempfile.TemporaryDirectory() as tmpdirname: + file = f"{tmpdirname}/{TC_FILE}" + with open(file, "w") as f: + f.write("tc.host=some_value\n") + + monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file) + + config = TCC() + assert config.tc_properties == {"tc.host": "some_value"} + + +def test_hub_image_name_prefix(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Ensure that the hub_image_name_prefix configuration variable can be read from the environment + """ + monkeypatch.setenv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", "myregistry.local/") + config = TCC() + assert config.hub_image_name_prefix == "myregistry.local/" + + +def test_set_tc_properties(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Ensure the configuration file variables can be read if no environment variable is set + """ + with tempfile.TemporaryDirectory() as tmpdirname: + file = f"{tmpdirname}/{TC_FILE}" + with open(file, "w") as f: + f.write("ryuk.disabled=true\n") + f.write("ryuk.container.privileged=false\n") + + monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file) + + config = TCC() + + assert config.ryuk_disabled + assert not config.ryuk_privileged + + +def test_override_tc_properties_1(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Ensure that we can re-set the configuration variables programattically to override + testcontainers.properties + """ + with tempfile.TemporaryDirectory() as tmpdirname: + file = f"{tmpdirname}/{TC_FILE}" + with open(file, "w") as f: + f.write("ryuk.disabled=true\n") + f.write("ryuk.container.privileged=false\n") + + monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file) + + config = TCC() + config.ryuk_disabled = False + config.ryuk_privileged = True + + assert not config.ryuk_disabled + assert config.ryuk_privileged + + +def test_override_tc_properties_2(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Ensure that we can override the testcontainers.properties with environment variables + """ + with tempfile.TemporaryDirectory() as tmpdirname: + file = f"{tmpdirname}/{TC_FILE}" + with open(file, "w") as f: + f.write("ryuk.disabled=true\n") + f.write("ryuk.container.privileged=false\n") + + monkeypatch.setattr("testcontainers.core.config.TC_GLOBAL", file) + + import os + + os.environ["TESTCONTAINERS_RYUK_DISABLED"] = "false" + os.environ["TESTCONTAINERS_RYUK_PRIVILEGED"] = "true" + + config = TCC() + + assert config.ryuk_disabled is False + assert config.ryuk_privileged is True + + +@pytest.mark.parametrize("docker_auth_config_env", ["key=value", ""]) +@pytest.mark.parametrize("warning_dict", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}]) +@pytest.mark.parametrize("warning_dict_post", [{}, {"key": "value"}, {"DOCKER_AUTH_CONFIG": "TEST"}]) +def test_docker_auth_config( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, + docker_auth_config_env: str, + warning_dict: dict[str, str], + warning_dict_post: dict[str, str], +) -> None: + monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict) + monkeypatch.setenv("DOCKER_AUTH_CONFIG", docker_auth_config_env) + caplog.set_level(logging.WARNING) + + config = TCC() + if not docker_auth_config_env: + assert config.docker_auth_config == "" + assert caplog.text == "" + else: + assert config.docker_auth_config == docker_auth_config_env + + if "DOCKER_AUTH_CONFIG" in warning_dict: + assert warning_dict["DOCKER_AUTH_CONFIG"] in caplog.text + + if warning_dict == {}: + monkeypatch.setattr("testcontainers.core.config._WARNINGS", warning_dict_post) + + config.docker_auth_config = "new_value" + assert config.docker_auth_config == "new_value" + + +def test_tc_properties_get_tc_host() -> None: + config = TCC() + config.tc_properties = {"tc.host": "some_value"} + assert config.tc_properties_get_tc_host() == "some_value" + + +def test_timeout() -> None: + config = TCC() + config.max_tries = 2 + config.sleep_time = 3 + assert config.timeout == 6 + + +def test_invalid_connection_mode(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("TESTCONTAINERS_CONNECTION_MODE", "FOOBAR") + with pytest.raises(ValueError, match=r"Error parsing TESTCONTAINERS_CONNECTION_MODE.*FOOBAR.*"): + get_user_overwritten_connection_mode() + + +@pytest.mark.parametrize("mode, use_mapped", [("bridge_ip", False), ("gateway_ip", True), ("docker_host", True)]) +def test_valid_connection_mode(monkeypatch: pytest.MonkeyPatch, mode: str, use_mapped: bool) -> None: + monkeypatch.setenv("TESTCONTAINERS_CONNECTION_MODE", mode) + uo_cmo = get_user_overwritten_connection_mode() + assert uo_cmo + assert uo_cmo.use_mapped_port is use_mapped + cmo = TestcontainersConfiguration().connection_mode_override + assert cmo + assert cmo.use_mapped_port is use_mapped + + +def test_no_connection_mode_given(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("TESTCONTAINERS_CONNECTION_MODE", raising=False) + assert get_user_overwritten_connection_mode() is None + + +def test_get_docker_socket_uses_env(monkeypatch: pytest.MonkeyPatch) -> None: + """ + If TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE env var is given prefer it + """ + monkeypatch.setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/var/test.socket") + assert get_docker_socket() == "/var/test.socket" + + +@pytest.fixture +def mock_docker_client_connections(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Ensure the docker client does not make any actual network calls + """ + from docker.api.client import APIClient + from docker.transport.sshconn import SSHHTTPAdapter + + # ensure that no actual connection is tried + monkeypatch.setattr(SSHHTTPAdapter, "_connect", Mock()) + monkeypatch.setattr(SSHHTTPAdapter, "_create_paramiko_client", Mock()) + monkeypatch.setattr(APIClient, "_retrieve_server_version", Mock(return_value="1.47")) + + +@pytest.mark.usefixtures("mock_docker_client_connections") +def test_get_docker_host_default(monkeypatch: pytest.MonkeyPatch) -> None: + """ + If non socket docker-host is given return default + + Still ryuk will properly still not work but this is the historical default + + """ + monkeypatch.delenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", raising=False) + # Define Fake SSH Docker client + monkeypatch.setenv("DOCKER_HOST", "ssh://remote_host") + assert get_docker_socket() == "/var/run/docker.sock" + + +@pytest.mark.usefixtures("mock_docker_client_connections") +def test_get_docker_host_non_root(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Use the socket determined by the Docker API Adapter + """ + monkeypatch.delenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", raising=False) + # Define a Non-Root like Docker Client + monkeypatch.setenv("DOCKER_HOST", "unix://var/run/user/1000/docker.sock") + assert get_docker_socket() == "/var/run/user/1000/docker.sock" + + +@pytest.mark.usefixtures("mock_docker_client_connections") +def test_get_docker_host_root(monkeypatch: pytest.MonkeyPatch) -> None: + """ + Use the socket determined by the Docker API Adapter + """ + monkeypatch.delenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", raising=False) + # Define a Root like Docker Client + monkeypatch.setenv("DOCKER_HOST", "unix://") + assert get_docker_socket() == "/var/run/docker.sock" + + +def test_deprecated_settings() -> None: + """ + Getting deprecated settings raises a DepcrationWarning + """ + from testcontainers.core import config + + with pytest.warns(DeprecationWarning): + assert config.TIMEOUT + + +def test_attribut_error() -> None: + """ + Accessing a not existing attribute raises an AttributeError + """ + from testcontainers.core import config + + with pytest.raises(AttributeError): + config.missing # noqa: B018 diff --git a/tests/core/test_container.py b/tests/core/test_container.py new file mode 100644 index 000000000..accdc18cf --- /dev/null +++ b/tests/core/test_container.py @@ -0,0 +1,167 @@ +from typing import Any + +import pytest + +from testcontainers.core.config import ConnectionMode, testcontainers_config +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient + +FAKE_ID = "ABC123" + + +class FakeContainer: + def __init__(self) -> None: + self.attrs: dict[str, Any] = {} + + @property + def id(self) -> str: + return FAKE_ID + + +@pytest.fixture +def container(monkeypatch: pytest.MonkeyPatch) -> DockerContainer: + """ + Fake initialized container + """ + client = DockerClient() + container = DockerContainer("foobar") + monkeypatch.setattr(container, "_docker", client) + monkeypatch.setattr(container, "_container", FakeContainer()) + + return container + + +@pytest.mark.parametrize("mode", ["docker_host", "gateway_ip", "bridge_ip"]) +def test_get_container_host_ip(container: DockerContainer, monkeypatch: pytest.MonkeyPatch, mode: str) -> None: + """ + Depending on the connection mode the correct function is executed to the host_ip + """ + connection_mode = ConnectionMode(mode) + + def result_fake(result: str, require_container_id): + def fake_for_mode(*container_id: str): + if require_container_id: + assert len(container_id) == 1 + assert container_id[0] == FAKE_ID + else: + assert len(container_id) == 0 + return result + + return fake_for_mode + + client = container._docker + + monkeypatch.setattr(client, "get_connection_mode", lambda: connection_mode) + monkeypatch.setattr(client, "gateway_ip", result_fake("gateway_ip", True)) + monkeypatch.setattr(client, "bridge_ip", result_fake("bridge_ip", True)) + monkeypatch.setattr(client, "host", result_fake("docker_host", False)) + + assert container.get_container_host_ip() == mode + + +@pytest.mark.parametrize("mode", [ConnectionMode.gateway_ip, ConnectionMode.docker_host]) +def test_get_exposed_port_mapped( + container: DockerContainer, monkeypatch: pytest.MonkeyPatch, mode: ConnectionMode +) -> None: + def fake_mapped(container_id: str, port: int) -> int: + assert container_id == FAKE_ID + assert port == 8080 + return 45678 + + client = container._docker + monkeypatch.setattr(client, "port", fake_mapped) + monkeypatch.setattr(client, "get_connection_mode", lambda: mode) + + assert container._get_exposed_port(8080) == 45678 + + +def test_get_exposed_port_original(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + client = container._docker + monkeypatch.setattr(client, "get_connection_mode", lambda: ConnectionMode.bridge_ip) + + assert container._get_exposed_port(8080) == 8080 + + +@pytest.mark.parametrize( + "init_attr,init_value,class_attr,stored_value", + [ + ("command", "ps", "_command", "ps"), + ("env", {"e1": "v1"}, "env", {"e1": "v1"}), + ("name", "foo-bar", "_name", "foo-bar"), + ("ports", [22, 80], "ports", {"22": None, "80": None}), + ( + "volumes", + [("/tmp", "/tmp2", "ro")], + "volumes", + {"/tmp": {"bind": "/tmp2", "mode": "ro"}}, + ), + ], +) +def test_attribute(init_attr, init_value, class_attr, stored_value): + """Test that the attributes set through the __init__ function are properly stored.""" + with DockerContainer("ubuntu", **{init_attr: init_value}) as container: + assert getattr(container, class_attr) == stored_value + + +def test_image_prefix_applied(monkeypatch: pytest.MonkeyPatch) -> None: + """Test that the hub_image_name_prefix is properly applied to the image name.""" + + # Set a prefix + test_prefix = "myregistry.example.com/" + monkeypatch.setattr(testcontainers_config, "hub_image_name_prefix", test_prefix) + + # Create a container and verify the prefix is applied + container = DockerContainer("nginx:latest") + assert container.image == "myregistry.example.com/nginx:latest" + + +def test_image_no_prefix_applied_when_empty(monkeypatch: pytest.MonkeyPatch) -> None: + """Test that when hub_image_name_prefix is empty, no prefix is applied.""" + # Set an empty prefix + monkeypatch.setattr(testcontainers_config, "hub_image_name_prefix", "") + + # Create a container and verify no prefix is applied + container = DockerContainer("nginx:latest") + assert container.image == "nginx:latest" + + +def test_container_info(): + """Test get_container_info functionality with a real container.""" + with DockerContainer("alpine:latest").with_command("sleep 30") as container: + info = container.get_container_info() + assert info is not None + assert info.Id is not None + assert info.Name is not None + assert info.Image is not None + + assert info.State is not None + assert info.State.Status == "running" + assert info.State.Running is True + assert info.State.Pid is not None + + assert info.Config is not None + assert info.Config.Image is not None + assert info.Config.Hostname is not None + + network_settings = info.get_network_settings() + assert network_settings is not None + assert network_settings.Networks is not None + + info2 = container.get_container_info() + assert info is info2 + + +def test_container_info_network_details(): + """Test network details in container info.""" + with DockerContainer("nginx:alpine") as container: + info = container.get_container_info() + assert info is not None + + network_settings = info.get_network_settings() + assert network_settings is not None + + if network_settings.Networks: + _network_name, network = next(iter(network_settings.Networks.items())) + assert network.IPAddress is not None + assert network.Gateway is not None + assert network.NetworkID is not None diff --git a/core/tests/test_core.py b/tests/core/test_core.py similarity index 87% rename from core/tests/test_core.py rename to tests/core/test_core.py index 9312b0bca..4c0d5f2aa 100644 --- a/core/tests/test_core.py +++ b/tests/core/test_core.py @@ -19,7 +19,7 @@ def test_get_logs(): stdout, stderr = container.get_logs() assert isinstance(stdout, bytes) assert isinstance(stderr, bytes) - assert "Hello from Docker".encode() in stdout, "There should be something on stdout" + assert b"Hello from Docker" in stdout, "There should be something on stdout" def test_docker_container_with_env_file(): @@ -39,7 +39,9 @@ def test_docker_container_with_env_file(): container = DockerContainer("alpine").with_command("tail -f /dev/null") # Keep the container running container.with_env_file(env_file_path) # Load the environment variables from the file with container: - output = container.exec("env").output.decode("utf-8").strip() + exec_output = container.exec("env").output + assert isinstance(exec_output, bytes) + output = exec_output.decode("utf-8").strip() assert "TEST_ENV_VAR=hello" in output assert "NUMBER=123" in output assert "DOMAIN=example.org" in output diff --git a/tests/core/test_core_ports.py b/tests/core/test_core_ports.py new file mode 100644 index 000000000..1738d14a2 --- /dev/null +++ b/tests/core/test_core_ports.py @@ -0,0 +1,117 @@ +from typing import Any, Optional, Union + +import pytest +from docker.errors import APIError + +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import is_podman + + +@pytest.mark.parametrize( + "container_port, host_port, expected_container_port", + [ + ("8080", "8080", "8080/tcp"), + ("8125/udp", "8125/udp", "8125/udp"), + ("8092/udp", "8092/udp", "8092/udp"), + ("9000/tcp", "9000/tcp", "9000/tcp"), + ("8080", "8080/udp", "8080/udp"), + (8080, 8080, "8080/tcp"), + (9000, None, "9000/tcp"), + ("9009", None, "9009/tcp"), + ("9000", "", "9000/tcp"), + ("9000/udp", "", "9000/udp"), + ], +) +def test_docker_container_with_bind_ports( + container_port: Union[str, int], host_port: Optional[Union[str, int]], expected_container_port: str +): + container = DockerContainer("alpine:latest") + container.with_bind_ports(container_port, host_port) + container.start() + + # prepare to inspect container + c_c = container._container + assert c_c + container_id = c_c.id + assert container_id is not None + client = c_c.client + assert client is not None + + # assemble expected output to compare to container API + host_port = str(host_port or "") + expected = {expected_container_port: [{"HostIp": "", "HostPort": host_port.partition("/")[0]}]} + + # compare PortBindings to expected output + actual = client.containers.get(container_id).attrs["HostConfig"]["PortBindings"] + if is_podman(): + # Normalize Podman differences: + # - HostIp '0.0.0.0' vs '' (both mean all interfaces) + # - Empty host_port: Podman stores the assigned port, Docker stores '' + for bindings in actual.values(): + for binding in bindings: + if binding.get("HostIp") == "0.0.0.0": + binding["HostIp"] = "" + if not host_port and binding.get("HostPort", "").isdigit(): + binding["HostPort"] = "" + assert actual == expected + container.stop() + + +@pytest.mark.parametrize( + "container_port, host_port", + [ + ("0", "8080"), + ("8080", "abc"), + (0, 0), + (-1, 8080), + (None, 8080), + ("8080/tcp", "8080/udp"), + ], +) +def test_error_docker_container_with_bind_ports(container_port: Union[str, int], host_port: Optional[Union[str, int]]): + with pytest.raises(APIError): + container = DockerContainer("alpine:latest") + container.with_bind_ports(container_port, host_port) + container.start() + + +@pytest.mark.parametrize( + "ports, expected", + [ + (("8125/udp",), {"8125/udp": {}}), + (("8092/udp", "9000/tcp"), {"8092/udp": {}, "9000/tcp": {}}), + (("8080", "8080/udp"), {"8080/tcp": {}, "8080/udp": {}}), + ((9000,), {"9000/tcp": {}}), + ((8080, 8080), {"8080/tcp": {}}), + (("9001", 9002), {"9001/tcp": {}, "9002/tcp": {}}), + (("9001", 9002, "9003/udp", 9004), {"9001/tcp": {}, "9002/tcp": {}, "9003/udp": {}, "9004/tcp": {}}), + ], +) +def test_docker_container_with_exposed_ports(ports: tuple[Union[str, int], ...], expected: dict[str, Any]): + container = DockerContainer("alpine:latest") + container.with_exposed_ports(*ports) + container.start() + + c_c = container._container + assert c_c + container_id = c_c.id + assert container_id is not None + client = c_c.client + assert client is not None + assert client.containers.get(container_id).attrs["Config"]["ExposedPorts"] == expected + container.stop() + + +@pytest.mark.parametrize( + "ports", + [ + ((9000, None)), + (("", 9000)), + ("tcp", ""), + ], +) +def test_error_docker_container_with_exposed_ports(ports: tuple[Union[str, int], ...]): + with pytest.raises(APIError): + container = DockerContainer("alpine:latest") + container.with_exposed_ports(*ports) + container.start() diff --git a/core/tests/test_core_registry.py b/tests/core/test_core_registry.py similarity index 56% rename from core/tests/test_core_registry.py rename to tests/core/test_core_registry.py index 384b06693..45807e02f 100644 --- a/core/tests/test_core_registry.py +++ b/tests/core/test_core_registry.py @@ -1,36 +1,45 @@ -"""Integration test using login to a private registry. +""" +Integration tests for private-registry support in ``DockerClient``. + +These tests spin up a real ``registry:2`` container and exercise the wiring that turns ``DOCKER_AUTH_CONFIG`` into a successful login + image pull. +The registry is provided by the local ``_LocalRegistryContainer`` helper, kept as a private copy in ``core/tests`` so ``core`` does not import from a sibling module. -Note: Using the testcontainers-python library to test the Docker registry. -This could be considered a bad practice as it is not recommended to use the same library to test itself. -However, it is a very good use case for DockerRegistryContainer and allows us to test it thoroughly. +Skipped where insecure HTTP registries cannot be reached without daemon reconfiguration. +That includes macOS, Podman, and SSH-based Docker hosts. """ -import json -import os import base64 -import pytest +import json +import pytest from docker.errors import NotFound from testcontainers.core.config import testcontainers_config from testcontainers.core.container import DockerContainer -from testcontainers.core.docker_client import DockerClient -from testcontainers.core.waiting_utils import wait_container_is_ready +from testcontainers.core.docker_client import DockerClient, is_podman, is_ssh_docker_host +from testcontainers.core.utils import is_mac +from testcontainers.core.waiting_utils import wait_for_logs -from testcontainers.registry import DockerRegistryContainer +from ._local_registry_container import _LocalRegistryContainer + +_skip_insecure_registry = pytest.mark.skipif( + is_mac() or is_podman() or is_ssh_docker_host(), + reason="Insecure HTTP registries are not supported without daemon reconfiguration on macOS, Podman, or SSH-based Docker hosts", +) +@_skip_insecure_registry def test_missing_on_private_registry(monkeypatch): username = "user" password = "pass" image = "hello-world" tag = "test" - with DockerRegistryContainer(username=username, password=password) as registry: + with _LocalRegistryContainer(username=username, password=password) as registry: registry_url = registry.get_registry() # prepare auth config - creds: bytes = base64.b64encode(f"{username}:{password}".encode("utf-8")) + creds: bytes = base64.b64encode(f"{username}:{password}".encode()) config = {"auths": {f"{registry_url}": {"auth": creds.decode("utf-8")}}} monkeypatch.setattr(testcontainers_config, name="docker_auth_config", value=json.dumps(config)) assert testcontainers_config.docker_auth_config, "docker_auth_config not set" @@ -38,21 +47,22 @@ def test_missing_on_private_registry(monkeypatch): with pytest.raises(NotFound): # Test a container with image from private registry with DockerContainer(f"{registry_url}/{image}:{tag}") as test_container: - wait_container_is_ready(test_container) + wait_for_logs(test_container, "Hello from Docker!") +@_skip_insecure_registry @pytest.mark.parametrize( - "image,tag,username,password", + "image,tag,username,password,expected_output", [ - ("nginx", "test", "user", "pass"), - ("hello-world", "latest", "new_user", "new_pass"), - ("alpine", "3.12", None, None), + ("nginx", "test", "user", "pass", "start worker processes"), + ("hello-world", "latest", "new_user", "new_pass", "Hello from Docker!"), + ("alpine", "3.12", None, None, ""), ], ) -def test_with_private_registry(image, tag, username, password, monkeypatch): +def test_with_private_registry(image, tag, username, password, expected_output, monkeypatch): client = DockerClient().client - with DockerRegistryContainer(username=username, password=password) as registry: + with _LocalRegistryContainer(username=username, password=password) as registry: registry_url = registry.get_registry() # prepare image @@ -69,14 +79,14 @@ def test_with_private_registry(image, tag, username, password, monkeypatch): client.images.remove(f"{registry_url}/{image}:{tag}") # prepare auth config - creds: bytes = base64.b64encode(f"{username}:{password}".encode("utf-8")) + creds: bytes = base64.b64encode(f"{username}:{password}".encode()) config = {"auths": {f"{registry_url}": {"auth": creds.decode("utf-8")}}} monkeypatch.setattr(testcontainers_config, name="docker_auth_config", value=json.dumps(config)) assert testcontainers_config.docker_auth_config, "docker_auth_config not set" # Test a container with image from private registry with DockerContainer(f"{registry_url}/{image}:{tag}") as test_container: - wait_container_is_ready(test_container) + wait_for_logs(test_container, expected_output) # cleanup client.images.remove(f"{registry_url}/{image}:{tag}") diff --git a/core/tests/test_docker_client.py b/tests/core/test_docker_client.py similarity index 54% rename from core/tests/test_docker_client.py rename to tests/core/test_docker_client.py index 1de293ef0..293c03a29 100644 --- a/core/tests/test_docker_client.py +++ b/tests/core/test_docker_client.py @@ -1,5 +1,5 @@ -import os import json +import os from collections import namedtuple from typing import Any from unittest import mock @@ -7,17 +7,25 @@ import docker import pytest +from docker.models.networks import Network -from testcontainers.core.config import testcontainers_config as c, ConnectionMode -from testcontainers.core.container import DockerContainer -from testcontainers.core.docker_client import DockerClient +from testcontainers.core import utils from testcontainers.core.auth import parse_docker_auth_config +from testcontainers.core.config import ConnectionMode +from testcontainers.core.config import testcontainers_config as c +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient, is_ssh_docker_host from testcontainers.core.image import DockerImage -from testcontainers.core import utils -from pytest import mark -from docker.models.networks import Network +def _expected_from_env_kwargs(**kwargs: Any) -> dict[str, Any]: + """Build the kwargs we expect ``docker.from_env`` to be called with. + + When DOCKER_HOST is SSH-based, ``use_ssh_client=True`` is added automatically. + """ + if is_ssh_docker_host(): + kwargs.setdefault("use_ssh_client", True) + return kwargs def test_docker_client_from_env(): @@ -26,7 +34,7 @@ def test_docker_client_from_env(): with patch("testcontainers.core.docker_client.docker", mock_docker): DockerClient(**test_kwargs) - mock_docker.from_env.assert_called_with(**test_kwargs) + mock_docker.from_env.assert_called_with(**_expected_from_env_kwargs(**test_kwargs)) def test_docker_client_login_no_login(): @@ -43,8 +51,8 @@ def test_docker_client_login(): mock_parse_docker_auth_config = MagicMock(spec=parse_docker_auth_config) mock_utils = MagicMock() mock_utils.parse_docker_auth_config = mock_parse_docker_auth_config - TestAuth = namedtuple("Auth", "value") - mock_parse_docker_auth_config.return_value = [TestAuth("test")] + Auth = namedtuple("Auth", "value") + mock_parse_docker_auth_config.return_value = [Auth("test")] with ( mock.patch.object(c, "_docker_auth_config", "test"), @@ -89,7 +97,7 @@ def test_docker_client_login_empty_parse_docker_auth_config(): # This is used to make sure we don't fail (nor try to login) when we have unsupported auth config -@mark.parametrize("auth_config_sample", [{"credHelpers": {"test": "login"}}, {"credsStore": "login"}]) +@pytest.mark.parametrize("auth_config_sample", [{"credHelpers": {"test": "login"}}, {"credsStore": "login"}]) def test_docker_client_login_unsupported_auth_config(auth_config_sample): mock_docker = MagicMock(spec=docker) mock_get_docker_auth_config = MagicMock() @@ -111,7 +119,7 @@ def test_container_docker_client_kw(): with patch("testcontainers.core.docker_client.docker", mock_docker): DockerContainer(image="", docker_client_kw=test_kwargs) - mock_docker.from_env.assert_called_with(**test_kwargs) + mock_docker.from_env.assert_called_with(**_expected_from_env_kwargs(**test_kwargs)) def test_image_docker_client_kw(): @@ -120,7 +128,7 @@ def test_image_docker_client_kw(): with patch("testcontainers.core.docker_client.docker", mock_docker): DockerImage(name="", path="", docker_client_kw=test_kwargs) - mock_docker.from_env.assert_called_with(**test_kwargs) + mock_docker.from_env.assert_called_with(**_expected_from_env_kwargs(**test_kwargs)) def test_host_prefer_host_override(monkeypatch: pytest.MonkeyPatch) -> None: @@ -139,6 +147,8 @@ def test_host_prefer_host_override(monkeypatch: pytest.MonkeyPatch) -> None: ], ) def test_host(monkeypatch: pytest.MonkeyPatch, base_url: str, expected: str) -> None: + if is_ssh_docker_host(): + pytest.skip("base_url parsing is not exercised under SSH (host() returns SSH hostname)") client = DockerClient() monkeypatch.setattr(client.client.api, "base_url", base_url) monkeypatch.setattr(c, "tc_host_override", None) @@ -212,7 +222,7 @@ def test_find_host_network_found_by_docker_host(monkeypatch: pytest.MonkeyPatch) client = DockerClient() monkeypatch.setattr(client, "host", lambda: "172.22.0.1") - networks = [ + networks: list[dict[str, Any]] = [ # a network without IPAM {"Name": "host"}, # network with invalid subnet @@ -270,6 +280,8 @@ def test_run_uses_found_network(monkeypatch: pytest.MonkeyPatch) -> None: """ If a host network is found, use it """ + if is_ssh_docker_host(): + pytest.skip("Host network discovery is skipped when DOCKER_HOST is set") client = DockerClient() @@ -277,9 +289,9 @@ class ContainerRunFake: def __init__(self) -> None: self.calls: list[dict[str, Any]] = [] - def run(self, image: str, **kwargs: Any) -> str: + def run(self, image: str, **kwargs: Any) -> bytes: self.calls.append(kwargs) - return "CONTAINER" + return b"CONTAINER" class FakeClient: def __init__(self) -> None: @@ -290,6 +302,165 @@ def __init__(self) -> None: monkeypatch.setattr(client, "find_host_network", lambda: "new_bridge_network") monkeypatch.setattr(client, "client", fake_client) - assert client.run("test") == "CONTAINER" + assert client.run("test") == b"CONTAINER" assert fake_client.containers.calls[0]["network"] == "new_bridge_network" + + +@pytest.mark.parametrize( + "docker_host, expected", + [ + pytest.param("ssh://user@192.168.1.42", "ssh://user@192.168.1.42", id="no_path"), + pytest.param("ssh://user@host/", "ssh://user@host", id="trailing_slash"), + pytest.param("ssh://user@host/some/path", "ssh://user@host", id="strips_path"), + pytest.param("tcp://localhost:2375", "tcp://localhost:2375", id="tcp_unchanged"), + pytest.param("unix:///var/run/docker.sock", "unix:///var/run/docker.sock", id="unix_unchanged"), + ], +) +def test_sanitize_docker_host(docker_host: str, expected: str) -> None: + from testcontainers.core.docker_client import _sanitize_docker_host + + assert _sanitize_docker_host(docker_host) == expected + + +@pytest.mark.parametrize( + "docker_host, expected_hostname", + [ + pytest.param("ssh://user@192.168.1.42", "192.168.1.42", id="ssh_ip"), + pytest.param("ssh://user@myhost.example.com", "myhost.example.com", id="ssh_fqdn"), + pytest.param("tcp://localhost:2375", None, id="tcp_returns_none"), + pytest.param(None, None, id="unset_returns_none"), + ], +) +def test_get_docker_host_hostname(monkeypatch: pytest.MonkeyPatch, docker_host: str, expected_hostname) -> None: + from testcontainers.core.docker_client import get_docker_host_hostname + + monkeypatch.setattr(c, "tc_properties_get_tc_host", lambda: None) + monkeypatch.setattr("testcontainers.core.docker_client._get_docker_host_from_context", lambda: None) + if docker_host: + monkeypatch.setenv("DOCKER_HOST", docker_host) + else: + monkeypatch.delenv("DOCKER_HOST", raising=False) + assert get_docker_host_hostname() == expected_hostname + + +def test_ssh_docker_host(monkeypatch: pytest.MonkeyPatch) -> None: + """Verify SSH DOCKER_HOST sets use_ssh_client and host() returns the remote hostname.""" + monkeypatch.setenv("DOCKER_HOST", "ssh://user@10.0.0.1") + monkeypatch.setattr(c, "tc_properties_get_tc_host", lambda: None) + monkeypatch.setattr(c, "tc_host_override", None) + mock_docker = MagicMock(spec=docker) + with patch("testcontainers.core.docker_client.docker", mock_docker): + client = DockerClient() + mock_docker.from_env.assert_called_once_with(use_ssh_client=True) + assert client.host() == "10.0.0.1" + + +_PODMAN_DAEMON_ERROR = RuntimeError("daemon unreachable") + + +@pytest.mark.parametrize( + "version, expected", + [ + pytest.param({"Platform": {"Name": "Docker Engine - Community"}}, False, id="docker_platform"), + pytest.param({"Platform": {"Name": "Podman Engine"}}, True, id="podman_platform"), + pytest.param({"Platform": {}, "Components": [{"Name": "podman"}]}, True, id="podman_components_fallback"), + pytest.param({}, False, id="empty_version_no_match"), + pytest.param(_PODMAN_DAEMON_ERROR, False, id="daemon_error_swallowed"), + ], +) +def test_is_podman(monkeypatch: pytest.MonkeyPatch, version: object, expected: bool) -> None: + from testcontainers.core import docker_client as dc + + dc.is_podman.cache_clear() + # Force the no-host branch so the test is deterministic regardless of the + # docker context configured on the machine running it. + monkeypatch.setattr("testcontainers.core.docker_client.get_docker_host", lambda: None) + mock_client = MagicMock() + if isinstance(version, Exception): + monkeypatch.setattr("testcontainers.core.docker_client.docker.from_env", MagicMock(side_effect=version)) + else: + mock_client.version.return_value = version + monkeypatch.setattr("testcontainers.core.docker_client.docker.from_env", lambda: mock_client) + try: + # Call twice to also assert the lru_cache only hits the daemon once. + assert dc.is_podman() is expected + assert dc.is_podman() is expected + if not isinstance(version, Exception): + assert mock_client.version.call_count == 1 + finally: + dc.is_podman.cache_clear() + + +def test_is_podman_uses_resolved_host(monkeypatch: pytest.MonkeyPatch) -> None: + """When a host is resolved (e.g. from the docker context), is_podman must + query that host rather than falling back to docker.from_env defaults.""" + from testcontainers.core import docker_client as dc + + dc.is_podman.cache_clear() + monkeypatch.setattr("testcontainers.core.docker_client.get_docker_host", lambda: "ssh://root@remote-podman") + mock_client = MagicMock() + mock_client.version.return_value = {"Platform": {"Name": "Podman Engine"}} + mock_docker_client = MagicMock(return_value=mock_client) + monkeypatch.setattr("testcontainers.core.docker_client.docker.DockerClient", mock_docker_client) + monkeypatch.setattr( + "testcontainers.core.docker_client.docker.from_env", + MagicMock(side_effect=AssertionError("should not call from_env when host resolved")), + ) + try: + assert dc.is_podman() is True + mock_docker_client.assert_called_once_with(base_url="ssh://root@remote-podman", use_ssh_client=True) + finally: + dc.is_podman.cache_clear() + + +def _mock_docker_context(name: str, host: str) -> MagicMock: + context = MagicMock() + context.Name = name + context.Host = host + return context + + +@pytest.mark.parametrize( + "context, expected", + [ + pytest.param(_mock_docker_context("remote", "ssh://user@10.0.0.1"), "ssh://user@10.0.0.1", id="returns_host"), + pytest.param( + _mock_docker_context("default", "unix:///var/run/docker.sock"), None, id="default_context_skipped" + ), + pytest.param(_mock_docker_context("remote", ""), None, id="empty_host_returns_none"), + pytest.param(None, None, id="no_current_context"), + ], +) +def test_get_docker_host_from_context(monkeypatch: pytest.MonkeyPatch, context, expected) -> None: + from testcontainers.core.docker_client import _get_docker_host_from_context + + monkeypatch.setattr( + "testcontainers.core.docker_client.ContextAPI.get_current_context", + lambda: context, + ) + assert _get_docker_host_from_context() == expected + + +def test_get_docker_host_from_context_swallows_errors(monkeypatch: pytest.MonkeyPatch) -> None: + """A malformed docker config should not crash; we fall through to None.""" + from testcontainers.core.docker_client import _get_docker_host_from_context + + def _raise() -> None: + raise RuntimeError("broken docker config") + + monkeypatch.setattr("testcontainers.core.docker_client.ContextAPI.get_current_context", _raise) + assert _get_docker_host_from_context() is None + + +def test_get_docker_host_falls_back_to_context(monkeypatch: pytest.MonkeyPatch) -> None: + """When tc.host and DOCKER_HOST are unset, the current docker context wins.""" + from testcontainers.core.docker_client import get_docker_host + + monkeypatch.setattr(c, "tc_properties_get_tc_host", lambda: None) + monkeypatch.delenv("DOCKER_HOST", raising=False) + monkeypatch.setattr( + "testcontainers.core.docker_client.ContextAPI.get_current_context", + lambda: _mock_docker_context("remote", "ssh://user@10.0.0.1"), + ) + assert get_docker_host() == "ssh://user@10.0.0.1" diff --git a/core/tests/test_docker_in_docker.py b/tests/core/test_docker_in_docker.py similarity index 79% rename from core/tests/test_docker_in_docker.py rename to tests/core/test_docker_in_docker.py index b07f80e9a..79bebe012 100644 --- a/core/tests/test_docker_in_docker.py +++ b/tests/core/test_docker_in_docker.py @@ -1,26 +1,46 @@ import contextlib import json import os -import time import socket +import sys +import time +from collections.abc import Generator from pathlib import Path -from typing import Final, Any +from typing import Any, Final import pytest +from docker.models.containers import Container from testcontainers.core import utils from testcontainers.core.config import testcontainers_config as tcc +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import LOGGER, DockerClient, is_ssh_docker_host from testcontainers.core.labels import SESSION_ID from testcontainers.core.network import Network -from testcontainers.core.container import DockerContainer -from testcontainers.core.docker_client import DockerClient, LOGGER -from testcontainers.core.utils import inside_container +from testcontainers.core.utils import inside_container, is_mac from testcontainers.core.waiting_utils import wait_for_logs +_DIND_PYTHON_VERSION = (3, 13) + + +SKIP_SSH_DOCKER = pytest.mark.skipif( + is_ssh_docker_host(), + reason="DinD/DooD tests require local Docker socket access, incompatible with SSH DOCKER_HOST", +) + +RUN_ONCE_IN_CI = pytest.mark.skipif( + bool(os.environ.get("CI")) and tuple([*sys.version_info][:2]) != _DIND_PYTHON_VERSION, + reason=( + f"To reduce load the DinD test are only executed " + f"in Python {'.'.join(map(str, _DIND_PYTHON_VERSION))} in the Pipeline." + ), +) + -def _wait_for_dind_return_ip(client, dind): +def _wait_for_dind_return_ip(client: DockerClient, dind: Container): # get ip address for DOCKER_HOST # avoiding DockerContainer class here to prevent code changes affecting the test + assert dind.id is not None docker_host_ip = client.bridge_ip(dind.id) # Wait for startup timeout = 10 @@ -31,11 +51,14 @@ def _wait_for_dind_return_ip(client, dind): break except ConnectionRefusedError: if time.perf_counter() - start_wait > timeout: - raise RuntimeError("Docker in docker took longer than 10 seconds to start") + raise RuntimeError("Docker in docker took longer than 10 seconds to start") from None time.sleep(0.01) return docker_host_ip +@pytest.mark.skipif(is_mac(), reason="Docker socket forwarding (socat) is unsupported on Docker Desktop for macOS") +@SKIP_SSH_DOCKER +@RUN_ONCE_IN_CI def test_wait_for_logs_docker_in_docker(): # real dind isn't possible (AFAIK) in CI # forwarding the socket to a container port is at least somewhat the same @@ -57,13 +80,19 @@ def test_wait_for_logs_docker_in_docker(): ) as container: assert container.get_container_host_ip() == docker_host_ip wait_for_logs(container, "Hello from Docker!") - stdout, stderr = container.get_logs() + stdout, _stderr = container.get_logs() assert stdout, "There should be something on stdout" not_really_dind.stop() not_really_dind.remove() +@pytest.mark.skipif( + is_mac(), + reason="Bridge networking and Docker socket forwarding are not supported on Docker Desktop for macOS", +) +@SKIP_SSH_DOCKER +@RUN_ONCE_IN_CI def test_dind_inherits_network(): client = DockerClient() try: @@ -88,11 +117,13 @@ def test_dind_inherits_network(): ) as container: assert container.get_container_host_ip() == docker_host_ip # Check the gateways are the same, so they can talk to each other - assert container.get_docker_client().gateway_ip(container.get_wrapped_container().id) == client.gateway_ip( - not_really_dind.id - ) + wrapped_id = container.get_wrapped_container().id + assert wrapped_id is not None + dind_id = not_really_dind.id + assert dind_id is not None + assert container.get_docker_client().gateway_ip(wrapped_id) == client.gateway_ip(dind_id) wait_for_logs(container, "Hello from Docker!") - stdout, stderr = container.get_logs() + stdout, _stderr = container.get_logs() assert stdout, "There should be something on stdout" not_really_dind.stop() @@ -101,7 +132,7 @@ def test_dind_inherits_network(): @contextlib.contextmanager -def print_surround_header(what: str, header_len: int = 80) -> None: +def print_surround_header(what: str, header_len: int = 80) -> Generator[None, None, None]: """ Helper to visually mark a block with headers """ @@ -144,9 +175,10 @@ def get_docker_info() -> dict[str, Any]: # see https://forums.docker.com/t/get-a-containers-full-id-from-inside-of-itself -@pytest.mark.xfail(reason="Does not work in rootles docker i.e. github actions") +@pytest.mark.xfail(reason="Does not work in rootless docker i.e. github actions") @pytest.mark.inside_docker_check @pytest.mark.skipif(not os.environ.get(EXPECTED_NETWORK_VAR), reason="No expected network given") +@SKIP_SSH_DOCKER def test_find_host_network_in_dood() -> None: """ Check that the correct host network is found for DooD @@ -158,7 +190,14 @@ def test_find_host_network_in_dood() -> None: assert DockerClient().find_host_network() == os.environ[EXPECTED_NETWORK_VAR] +@pytest.mark.long_running +@pytest.mark.skipif( + is_mac(), + reason="Docker socket mounting and container networking do not work reliably on Docker Desktop for macOS", +) @pytest.mark.skipif(not Path(tcc.ryuk_docker_socket).exists(), reason="No docker socket available") +@SKIP_SSH_DOCKER +@RUN_ONCE_IN_CI def test_dood(python_testcontainer_image: str) -> None: """ Run tests marked as inside_docker_check inside docker out of docker @@ -170,7 +209,7 @@ def test_dood(python_testcontainer_image: str) -> None: DockerContainer( image=python_testcontainer_image, ) - .with_command("poetry run pytest -m inside_docker_check") + .with_command("uv run pytest -m inside_docker_check") .with_volume_mapping(docker_sock, docker_sock, "rw") # test also that the correct network was found # but only do this if not already inside a container @@ -193,6 +232,13 @@ def test_dood(python_testcontainer_image: str) -> None: assert status["StatusCode"] == 0 +@pytest.mark.long_running +@pytest.mark.skipif( + is_mac(), + reason="Docker socket mounting and container networking do not work reliably on Docker Desktop for macOS", +) +@SKIP_SSH_DOCKER +@RUN_ONCE_IN_CI def test_dind(python_testcontainer_image: str, tmp_path: Path) -> None: """ Run selected tests in Docker in Docker @@ -216,7 +262,7 @@ def test_dind(python_testcontainer_image: str, tmp_path: Path) -> None: try: with ( DockerContainer(image=python_testcontainer_image) - .with_command("poetry run pytest -m inside_docker_check") + .with_command("uv run pytest -m inside_docker_check") .with_volume_mapping(str(cert_dir), "/certs") # for some reason the docker client does not respect # DOCKER_TLS_CERTDIR and looks in /root/.docker instead diff --git a/tests/core/test_exec.py b/tests/core/test_exec.py new file mode 100644 index 000000000..949fdfac4 --- /dev/null +++ b/tests/core/test_exec.py @@ -0,0 +1,216 @@ +"""Behavioral tests for ``DockerContainer.exec``. + +The tests in this module are split in two: + +* The functions below pin the *current* behavior of ``.exec()`` as it forwards + ``str`` and ``list[str]`` commands to ``docker-py``'s ``exec_run``. They exist + to make any future change in interpretation a visible, intentional diff. +* In particular, they nail down a sharp edge: ``docker-py`` tokenizes a ``str`` + command with ``shlex.split`` and never wraps it in a shell. Pipes, redirects, + and variable expansion are therefore *not* interpreted -- a fact that is easy + to assume otherwise and was previously untested. +""" + +from collections.abc import Iterator +from dataclasses import FrozenInstanceError, replace +from pathlib import Path +from typing import TYPE_CHECKING +from unittest.mock import MagicMock + +import pytest +from docker.models.containers import ExecResult + +from testcontainers.core.container import DockerContainer, ExecConfig +from testcontainers.core.exceptions import ContainerStartException + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +@pytest.fixture(scope="module") +def running_container() -> Iterator[DockerContainer]: + """A long-lived alpine container to exec against (exec is read-only here).""" + container = DockerContainer("alpine").with_command("tail -f /dev/null") + with container: + yield container + + +def test_exec_str_command(running_container: DockerContainer) -> None: + result = running_container.exec("echo hello") + assert result.exit_code == 0 + assert isinstance(result.output, bytes) + assert result.output.strip() == b"hello" + + +def test_exec_list_command(running_container: DockerContainer) -> None: + result = running_container.exec(["echo", "hello"]) + assert result.exit_code == 0 + assert result.output.strip() == b"hello" + + +def test_exec_nonzero_exit_code_is_propagated(running_container: DockerContainer) -> None: + result = running_container.exec(["sh", "-c", "exit 3"]) + assert result.exit_code == 3 + + +def test_str_command_is_tokenized_not_shell_interpreted(running_container: DockerContainer) -> None: + """A ``str`` command is split with ``shlex``, not run through a shell. + + ``echo a | wc -l`` becomes the argv ``["echo", "a", "|", "wc", "-l"]``, so + ``echo`` prints the pipe and ``wc`` literally instead of counting lines. + """ + result = running_container.exec("echo a | wc -l") + assert result.exit_code == 0 + assert result.output.strip() == b"a | wc -l" + + +def test_str_command_does_not_redirect(running_container: DockerContainer) -> None: + result = running_container.exec("echo hi > /tmp/frob") + assert result.exit_code == 0 + assert result.output.strip() == b"hi > /tmp/frob" + + +def test_str_command_does_not_expand_variables(running_container: DockerContainer) -> None: + """No shell means no parameter expansion: ``$HOME`` reaches ``echo`` verbatim.""" + result = running_container.exec("echo $HOME") + assert result.exit_code == 0 + assert result.output.strip() == b"$HOME" + + +# --- Pure unit tests for the config -> exec_run kwargs seam (no Docker needed) --- + + +def test_exec_config_requires_only_command() -> None: + config = ExecConfig(command=["echo", "hi"]) + assert config.command == ["echo", "hi"] + assert config.user is None + assert config.environment is None + assert config.workdir is None + assert config.privileged is False + + +def test_exec_config_is_frozen() -> None: + config = ExecConfig(command="true") + with pytest.raises(FrozenInstanceError): + config.user = "frob" # type: ignore[misc] + + +def test_exec_config_supports_dataclasses_replace() -> None: + base = ExecConfig(command="true") + derived = replace(base, workdir="/tmp", user="frob") + assert base.workdir is None # original untouched + assert base.user is None + assert derived.command == "true" + assert derived.workdir == "/tmp" + assert derived.user == "frob" + + +def test_kwargs_defaults_collapse_user_to_docker_sentinel() -> None: + kwargs = ExecConfig(command=["whoami"]).to_exec_run_kwargs() + assert kwargs == { + "cmd": ["whoami"], + "user": "", # docker-py's empty-string sentinel, not our None + "environment": None, + "workdir": None, + "privileged": False, + } + + +def test_kwargs_forward_str_command_verbatim() -> None: + # docker-py does its own shlex tokenization; we must not pre-split. + assert ExecConfig(command="echo a | wc -l").to_exec_run_kwargs()["cmd"] == "echo a | wc -l" + + +def test_kwargs_pass_through_user_environment_and_privileged() -> None: + kwargs = ExecConfig(command=["env"], user="frob", environment={"FROB": "243"}, privileged=True).to_exec_run_kwargs() + assert kwargs["user"] == "frob" + assert kwargs["environment"] == {"FROB": "243"} + assert kwargs["privileged"] is True + + +def test_kwargs_stringify_pathlike_workdir() -> None: + kwargs = ExecConfig(command=["pwd"], workdir=Path("/tmp/xyzzy")).to_exec_run_kwargs() + assert kwargs["workdir"] == "/tmp/xyzzy" + assert isinstance(kwargs["workdir"], str) + + +def test_kwargs_leave_unset_workdir_as_none() -> None: + assert ExecConfig(command=["pwd"]).to_exec_run_kwargs()["workdir"] is None + + +@pytest.fixture +def offline_container(mocker: "MockerFixture") -> DockerContainer: + """A DockerContainer whose client is mocked away, so exec() can be exercised + without a running daemon. ``_container`` is wired with a stub ``exec_run``.""" + mocker.patch("testcontainers.core.container.DockerClient") + return DockerContainer("alpine") + + +def test_exec_wraps_str_into_config_and_forwards_kwargs(offline_container: DockerContainer) -> None: + offline_container._container = MagicMock() + offline_container._container.exec_run.return_value = ExecResult(exit_code=0, output=b"hi") + result = offline_container.exec("echo hi") + offline_container._container.exec_run.assert_called_once_with( + cmd="echo hi", user="", environment=None, workdir=None, privileged=False + ) + assert result.exit_code == 0 + assert result.output == b"hi" + + +def test_exec_accepts_exec_config_directly(offline_container: DockerContainer) -> None: + offline_container._container = MagicMock() + offline_container._container.exec_run.return_value = ExecResult(exit_code=0, output=b"hi") + offline_container.exec(ExecConfig(command=["pwd"], workdir=Path("/tmp"), user="frob")) + offline_container._container.exec_run.assert_called_once_with( + cmd=["pwd"], user="frob", environment=None, workdir="/tmp", privileged=False + ) + + +def test_exec_before_start_raises(mocker: "MockerFixture") -> None: + mocker.patch("testcontainers.core.container.DockerClient") + with pytest.raises(ContainerStartException): + DockerContainer("alpine").exec("true") + + +# --- Integration tests for the new ExecConfig fields (require a Docker daemon) --- + + +def test_exec_config_workdir_str(running_container: DockerContainer) -> None: + result = running_container.exec(ExecConfig(command=["pwd"], workdir="/tmp")) + assert result.exit_code == 0 + assert result.output.strip() == b"/tmp" + + +def test_exec_config_workdir_accepts_pathlib_path(running_container: DockerContainer) -> None: + result = running_container.exec(ExecConfig(command=["pwd"], workdir=Path("/tmp"))) + assert result.exit_code == 0 + assert result.output.strip() == b"/tmp" + + +def test_exec_config_environment(running_container: DockerContainer) -> None: + result = running_container.exec(ExecConfig(command=["env"], environment={"FROB": "243"})) + assert result.exit_code == 0 + assert b"FROB=243" in result.output + + +def test_exec_config_user(running_container: DockerContainer) -> None: + as_default = running_container.exec(ExecConfig(command=["whoami"])) + as_nobody = running_container.exec(ExecConfig(command=["whoami"], user="nobody")) + assert as_default.output.strip() == b"root" + assert as_nobody.output.strip() == b"nobody" + + +def test_exec_config_privileged_grants_more_capabilities(running_container: DockerContainer) -> None: + """A privileged exec receives the full capability set, even though the + container itself is unprivileged -- so its CapEff strictly exceeds the + default exec's. We read the effective set straight from procfs.""" + + def cap_eff(config: ExecConfig) -> int: + output = running_container.exec(config).output.decode() + # /proc/self/status line looks like: "CapEff:\t00000000a80425fb" + line = next(ln for ln in output.splitlines() if ln.startswith("CapEff:")) + return int(line.split()[1], 16) + + default_caps = cap_eff(ExecConfig(command=["grep", "CapEff", "/proc/self/status"])) + privileged_caps = cap_eff(ExecConfig(command=["grep", "CapEff", "/proc/self/status"], privileged=True)) + assert privileged_caps > default_caps diff --git a/core/tests/test_image.py b/tests/core/test_image.py similarity index 55% rename from core/tests/test_image.py rename to tests/core/test_image.py index da35eda07..38efe7709 100644 --- a/core/tests/test_image.py +++ b/tests/core/test_image.py @@ -1,10 +1,10 @@ -import pytest -import tempfile -import random import os - +import random +import tempfile from pathlib import Path -from typing import Optional +from typing import Any, Optional + +import pytest from testcontainers.core.container import DockerContainer from testcontainers.core.image import DockerImage @@ -30,10 +30,22 @@ def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_f assert image.get_wrapped_image() is not None logs = image.get_logs() assert isinstance(logs, list), "Logs should be a list" - assert logs[0] == {"stream": "Step 1/2 : FROM alpine:latest"} - assert logs[3] == {"stream": f'Step 2/2 : CMD echo "{random_string}"'} + streams = [] + for entry in logs: + if isinstance(entry, dict): + stream = entry.get("stream", "") + if isinstance(stream, str): + streams.append(stream) + + assert any(s.upper().startswith("STEP 1/2") and "FROM ALPINE:LATEST" in s.upper() for s in streams) + assert any(s.upper().startswith("STEP 2/2") and random_string in s for s in streams), ( + f"Expected step 2 with '{random_string}' in logs: {streams}" + ) with DockerContainer(str(image)) as container: - assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch" + c_c = container._container + assert c_c + assert c_c.image is not None + assert c_c.image.short_id.endswith(image_short_id), "Image ID mismatch" assert container.get_logs() == ((random_string + "\n").encode(), b""), "Container logs mismatch" check_for_image(image_short_id, test_cleanup) @@ -43,6 +55,7 @@ def test_docker_image(test_image_tag: Optional[str], test_cleanup: bool, check_f def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path]) -> None: with tempfile.TemporaryDirectory() as temp_directory: temp_dir_path = Path(temp_directory) + dockerfile_kwargs: dict[str, Any] = {} if dockerfile_path: os.makedirs(temp_dir_path / dockerfile_path.parent, exist_ok=True) dockerfile_rel_path = dockerfile_path @@ -53,7 +66,7 @@ def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path with open(temp_dir_path / dockerfile_rel_path, "x") as f: f.write( - f""" + """ FROM alpine:latest CMD echo "Hello world!" """ @@ -62,5 +75,32 @@ def test_docker_image_with_custom_dockerfile_path(dockerfile_path: Optional[Path image_short_id = image.short_id assert image.get_wrapped_image() is not None with DockerContainer(str(image)) as container: - assert container._container.image.short_id.endswith(image_short_id), "Image ID mismatch" - assert container.get_logs() == (("Hello world!\n").encode(), b""), "Container logs mismatch" + c_c = container._container + assert c_c + assert c_c.image is not None + assert c_c.image.short_id.endswith(image_short_id), "Image ID mismatch" + assert container.get_logs() == ((b"Hello world!\n"), b""), "Container logs mismatch" + + +def test_docker_image_with_kwargs(): + with tempfile.TemporaryDirectory() as temp_directory: + with open(f"{temp_directory}/Dockerfile", "w") as f: + f.write( + """ + FROM alpine:latest + ARG TEST_ARG + ENV TEST_ARG $TEST_ARG + CMD echo $TEST_ARG + """ + ) + with DockerImage( + path=temp_directory, tag="test", clean_up=True, no_cache=True, buildargs={"TEST_ARG": "new_arg"} + ) as image: + image_short_id = image.short_id + assert image.get_wrapped_image() is not None + with DockerContainer(str(image)) as container: + c_c = container._container + assert c_c + assert c_c.image is not None + assert c_c.image.short_id.endswith(image_short_id), "Image ID mismatch" + assert container.get_logs() == ((b"new_arg\n"), b""), "Container logs mismatch" diff --git a/tests/core/test_inspect.py b/tests/core/test_inspect.py new file mode 100644 index 000000000..bd803347d --- /dev/null +++ b/tests/core/test_inspect.py @@ -0,0 +1,248 @@ +from typing import Any + +import pytest + +from testcontainers.core.container import DockerContainer +from testcontainers.core.docker_client import DockerClient +from testcontainers.core.inspect import ContainerInspectInfo + +FAKE_ID = "ABC123" + + +class FakeContainer: + def __init__(self) -> None: + self.attrs: dict[str, Any] = {} + + @property + def id(self) -> str: + return FAKE_ID + + +@pytest.fixture +def container(monkeypatch: pytest.MonkeyPatch) -> DockerContainer: + """ + Fake initialized container + """ + client = DockerClient() + container = DockerContainer("foobar") + monkeypatch.setattr(container, "_docker", client) + monkeypatch.setattr(container, "_container", FakeContainer()) + + return container + + +def test_get_container_info_returns_none_when_no_container( + container: DockerContainer, monkeypatch: pytest.MonkeyPatch +) -> None: + """Test get_container_info returns None when container is not started.""" + monkeypatch.setattr(container, "_container", None) + info = container.get_container_info() + assert info is None + + +def test_get_container_info_lazy_loading(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + """Test get_container_info lazy loading and caching.""" + fake_data = {"Id": "test123", "Name": "/test-container", "Image": "nginx:alpine"} + fake_info = ContainerInspectInfo.from_dict(fake_data) + + monkeypatch.setattr(container._docker, "get_container_inspect_info", lambda _: fake_info) + + info1 = container.get_container_info() + assert info1 is not None + assert info1.Id == "test123" + assert info1.Name == "/test-container" + assert info1.Image == "nginx:alpine" + + info2 = container.get_container_info() + assert info1 is info2 + + +def test_get_container_info_structure(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + """Test get_container_info returns properly structured data.""" + fake_data = { + "Id": "abc123def456", + "Name": "/my-test-container", + "Image": "sha256:nginx123", + "Created": "2023-01-01T00:00:00Z", + "State": { + "Status": "running", + "Running": True, + "Pid": 5678, + "ExitCode": 0, + "Health": {"Status": "healthy", "FailingStreak": 0, "Log": [{"Output": "healthy"}]}, + }, + "Config": { + "Image": "nginx:alpine", + "Hostname": "my-hostname", + "Env": ["PATH=/usr/bin", "HOME=/root"], + "Cmd": ["nginx", "-g", "daemon off;"], + "ExposedPorts": {"80/tcp": {}}, + }, + "NetworkSettings": { + "IPAddress": "172.17.0.3", + "Gateway": "172.17.0.1", + "Networks": { + "bridge": { + "IPAddress": "172.17.0.3", + "Gateway": "172.17.0.1", + "NetworkID": "net123", + "MacAddress": "02:42:ac:11:00:03", + "Aliases": ["container-alias"], + } + }, + }, + "HostConfig": {"Memory": 1073741824, "CpuShares": 1024, "NetworkMode": "bridge"}, + } + fake_info = ContainerInspectInfo.from_dict(fake_data) + + monkeypatch.setattr(container._docker, "get_container_inspect_info", lambda _: fake_info) + + info = container.get_container_info() + assert info is not None + + assert info.Id == "abc123def456" + assert info.Name == "/my-test-container" + assert info.Image == "sha256:nginx123" + assert info.Created == "2023-01-01T00:00:00Z" + + assert info.State is not None + assert info.State.Status == "running" + assert info.State.Running is True + assert info.State.Pid == 5678 + assert info.State.ExitCode == 0 + assert info.State.Health is not None + assert info.State.Health.Status == "healthy" + assert info.State.Health.FailingStreak == 0 + assert info.State.Health.Log is not None + assert len(info.State.Health.Log) == 1 + assert info.State.Health.Log[0].Output == "healthy" + + assert info.Config is not None + assert info.Config.Image == "nginx:alpine" + assert info.Config.Hostname == "my-hostname" + assert info.Config.Env == ["PATH=/usr/bin", "HOME=/root"] + assert info.Config.Cmd == ["nginx", "-g", "daemon off;"] + assert info.Config.ExposedPorts == {"80/tcp": {}} + + network_settings = info.get_network_settings() + assert network_settings is not None + assert network_settings.IPAddress == "172.17.0.3" + assert network_settings.Gateway == "172.17.0.1" + + assert network_settings.Networks is not None + assert "bridge" in network_settings.Networks + bridge_network = network_settings.Networks["bridge"] + assert bridge_network.IPAddress == "172.17.0.3" + assert bridge_network.Gateway == "172.17.0.1" + assert bridge_network.NetworkID == "net123" + assert bridge_network.MacAddress == "02:42:ac:11:00:03" + assert bridge_network.Aliases == ["container-alias"] + + assert info.HostConfig is not None + assert info.HostConfig.Memory == 1073741824 + assert info.HostConfig.CpuShares == 1024 + assert info.HostConfig.NetworkMode == "bridge" + + +def test_get_container_info_handles_exceptions(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + """Test get_container_info handles exceptions gracefully.""" + + def mock_exception(_): + raise Exception("Docker API error") + + monkeypatch.setattr(container._docker, "get_container_inspect_info", mock_exception) + + info = container.get_container_info() + assert info is None + + +def test_get_container_info_with_none_values(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + """Test get_container_info handles None values in HostConfig and NetworkSettings.""" + fake_data = { + "Id": "test-none-values", + "Name": "/test-none", + "Image": "nginx:alpine", + "NetworkSettings": {"IPAddress": "172.17.0.2", "Networks": None, "Ports": None}, + "HostConfig": {"Memory": 0, "NetworkMode": "bridge", "PortBindings": None}, + } + fake_info = ContainerInspectInfo.from_dict(fake_data) + + monkeypatch.setattr(container._docker, "get_container_inspect_info", lambda _: fake_info) + + info = container.get_container_info() + assert info is not None + assert info.Id == "test-none-values" + + network_settings = info.get_network_settings() + assert network_settings is not None + assert network_settings.IPAddress == "172.17.0.2" + assert network_settings.Networks is None + assert network_settings.Ports is None + + assert info.HostConfig is not None + assert info.HostConfig.Memory == 0 + assert info.HostConfig.NetworkMode == "bridge" + assert info.HostConfig.PortBindings is None + + +def test_get_container_info_with_port_bindings(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + """Test get_container_info handles port bindings correctly.""" + fake_data = { + "Id": "test-port-bindings", + "Name": "/test-ports", + "Image": "nginx:alpine", + "NetworkSettings": {"Ports": {"80/tcp": [{"HostPort": "8080"}], "443/tcp": None}}, + "HostConfig": {"NetworkMode": "bridge", "PortBindings": {"80/tcp": [{"HostPort": "8080"}], "443/tcp": None}}, + } + fake_info = ContainerInspectInfo.from_dict(fake_data) + + monkeypatch.setattr(container._docker, "get_container_inspect_info", lambda _: fake_info) + + info = container.get_container_info() + assert info is not None + + network_settings = info.get_network_settings() + assert network_settings is not None + assert network_settings.Ports is not None + assert "80/tcp" in network_settings.Ports + port_bindings = network_settings.Ports["80/tcp"] + assert port_bindings is not None + assert len(port_bindings) == 1 + assert port_bindings[0].HostPort == "8080" + assert network_settings.Ports["443/tcp"] is None + + assert info.HostConfig is not None + assert info.HostConfig.PortBindings is not None + assert "80/tcp" in info.HostConfig.PortBindings + host_port_bindings = info.HostConfig.PortBindings["80/tcp"] + assert host_port_bindings is not None + assert len(host_port_bindings) == 1 + assert host_port_bindings[0].HostPort == "8080" + assert info.HostConfig.PortBindings["443/tcp"] is None + + +def test_get_container_info_edge_cases_regression(container: DockerContainer, monkeypatch: pytest.MonkeyPatch) -> None: + """Regression test for None value handling.""" + fake_data = { + "Id": "regression-test", + "Name": "/regression-container", + "Image": "nginx:alpine", + "NetworkSettings": {"IPAddress": "172.17.0.2", "Networks": None, "Ports": None}, + "HostConfig": {"Memory": 0, "NetworkMode": "bridge", "PortBindings": None}, + } + fake_info = ContainerInspectInfo.from_dict(fake_data) + + monkeypatch.setattr(container._docker, "get_container_inspect_info", lambda _: fake_info) + + info = container.get_container_info() + assert info is not None + assert info.Id == "regression-test" + + network_settings = info.get_network_settings() + assert network_settings is not None + assert network_settings.Networks is None + assert network_settings.Ports is None + + host_config = info.HostConfig + assert host_config is not None + assert host_config.PortBindings is None diff --git a/core/tests/test_labels.py b/tests/core/test_labels.py similarity index 78% rename from core/tests/test_labels.py rename to tests/core/test_labels.py index 425aee7dd..8af9a8655 100644 --- a/core/tests/test_labels.py +++ b/tests/core/test_labels.py @@ -1,16 +1,17 @@ +import pytest + +from testcontainers.core.config import testcontainers_config as config from testcontainers.core.labels import ( LABEL_LANG, LABEL_SESSION_ID, LABEL_TESTCONTAINERS, LABEL_VERSION, - create_labels, TESTCONTAINERS_NAMESPACE, + create_labels, ) -import pytest -from testcontainers.core.config import RYUK_IMAGE -def assert_in_with_value(labels: dict, label: str, value: str, known_before_test_time: bool) -> None: +def assert_in_with_value(labels: dict[str, str], label: str, value: str, known_before_test_time: bool): assert label in labels if known_before_test_time: assert labels[label] == value @@ -43,7 +44,7 @@ def test_containers_respect_custom_labels_if_no_collision(): def test_if_ryuk_no_session(): - actual_labels = create_labels(RYUK_IMAGE, None) + actual_labels = create_labels(config.ryuk_image, None) assert LABEL_SESSION_ID not in actual_labels @@ -56,3 +57,10 @@ def test_session_are_module_import_scoped(): assert LABEL_SESSION_ID in first_labels assert LABEL_SESSION_ID in second_labels assert first_labels[LABEL_SESSION_ID] == second_labels[LABEL_SESSION_ID] + + +def test_create_no_side_effects(): + input_labels = {"key": "value"} + expected_labels = input_labels.copy() + create_labels("not-ryuk", input_labels) + assert input_labels == expected_labels, input_labels diff --git a/tests/core/test_legacy_module_imports.py b/tests/core/test_legacy_module_imports.py new file mode 100644 index 000000000..e67cec49f --- /dev/null +++ b/tests/core/test_legacy_module_imports.py @@ -0,0 +1,74 @@ +import importlib +import sys + +import pytest + +MODULES = { + "arangodb": ["ArangoDbContainer"], + "aws": ["AWSLambdaContainer"], + "azurite": ["AzuriteContainer", "ConnectionStringType"], + "cassandra": ["CassandraContainer"], + "chroma": ["ChromaContainer"], + "clickhouse": ["ClickHouseContainer"], + "cockroachdb": ["CockroachDBContainer"], + "cosmosdb": ["CosmosDBMongoEndpointContainer", "CosmosDBNoSQLEndpointContainer"], + "db2": ["Db2Container"], + "elasticsearch": ["ElasticSearchContainer"], + "generic": ["ServerContainer", "SqlContainer"], + "google": ["DatastoreContainer", "PubSubContainer"], + "influxdb": ["InfluxDbContainer"], + "influxdb1": ["InfluxDb1Container"], + "influxdb2": ["InfluxDb2Container"], + "k3s": ["K3SContainer"], + "kafka": ["KafkaContainer", "RedpandaContainer", "kafka_config"], + "keycloak": ["KeycloakContainer"], + "localstack": ["LocalStackContainer"], + "mailpit": ["MailpitContainer", "MailpitUser"], + "memcached": ["MemcachedContainer", "MemcachedNotReady"], + "milvus": ["MilvusContainer"], + "minio": ["MinioContainer"], + "mongodb": ["MongoDbContainer", "MongoDBAtlasLocalContainer"], + "mqtt": ["MosquittoContainer"], + "mssql": ["SqlServerContainer"], + "mysql": ["MySqlContainer"], + "nats": ["NatsContainer"], + "neo4j": ["Neo4jContainer"], + "nginx": ["NginxContainer"], + "ollama": ["OllamaContainer", "OllamaModel"], + "openfga": ["OpenFGAContainer"], + "opensearch": ["OpenSearchContainer"], + "oracle": ["OracleDbContainer"], + "postgres": ["PostgresContainer"], + "qdrant": ["QdrantContainer"], + "rabbitmq": ["RabbitMqContainer"], + "redis": ["AsyncRedisContainer", "PingWaitStrategy", "RedisContainer"], + "registry": ["DockerRegistryContainer"], + "scylla": ["ScyllaContainer"], + "selenium": ["BrowserWebDriverContainer", "SeleniumVideoContainer", "get_image_name"], + "sftp": ["SFTPContainer", "SFTPUser"], + "trino": ["TrinoContainer"], + "valkey": ["ValkeyContainer"], + "vault": ["VaultContainer"], + "weaviate": ["WeaviateContainer"], +} + + +def _fresh_import(mod: str): + full_name = f"testcontainers.{mod}" + sys.modules.pop(full_name, None) + return importlib.import_module(full_name) + + +@pytest.mark.parametrize("mod,symbols", MODULES.items(), ids=MODULES.keys()) +def test_deprecation_warning(mod, symbols): + with pytest.warns(DeprecationWarning, match=f"testcontainers.community.{mod}"): + _fresh_import(mod) + + +@pytest.mark.parametrize("mod,symbols", MODULES.items(), ids=MODULES.keys()) +def test_symbols_exported(mod, symbols): + imported = _fresh_import(mod) + assert hasattr(imported, "__all__") + for symbol in symbols: + assert symbol in imported.__all__, f"{symbol} missing from testcontainers.{mod}.__all__" + assert hasattr(imported, symbol), f"{symbol} not accessible on testcontainers.{mod}" diff --git a/core/tests/test_network.py b/tests/core/test_network.py similarity index 86% rename from core/tests/test_network.py rename to tests/core/test_network.py index 868032e26..c84b30762 100644 --- a/core/tests/test_network.py +++ b/tests/core/test_network.py @@ -1,12 +1,13 @@ from http import HTTPStatus + +import docker.errors +import pytest + from testcontainers.core.container import DockerContainer from testcontainers.core.docker_client import DockerClient from testcontainers.core.labels import LABEL_SESSION_ID from testcontainers.core.network import Network -import docker.errors -import pytest - NGINX_ALPINE_SLIM_IMAGE = "nginx:1.25.4-alpine-slim" @@ -39,8 +40,9 @@ def test_network_create_errors(): with pytest.raises(docker.errors.APIError) as excinfo: network.create() + assert excinfo.value.response is not None assert excinfo.value.response.status_code == HTTPStatus.CONFLICT - excinfo.match(f"network with name {network.name} already exists") + excinfo.match(f"network.*{network.name}.*already exists") network.remove() @@ -74,7 +76,7 @@ def test_containers_can_communicate_over_network(): def assert_can_ping(container: DockerContainer, remote_name: str): - status, output = container.exec("ping -c 1 %s" % remote_name) + status, output = container.exec(f"ping -c 1 {remote_name}") assert status == 0 assert "64 bytes" in str(output) @@ -82,6 +84,7 @@ def assert_can_ping(container: DockerContainer, remote_name: str): def assert_can_request(container: DockerContainer, remote_name: str): status, output = container.exec(f"wget -qO- http://{remote_name}") assert status == 0 + assert isinstance(output, bytes) assert "Welcome to nginx!" in output.decode() @@ -89,7 +92,10 @@ def test_network_has_labels(): network = Network() try: network.create() - network = network._docker.client.networks.get(network_id=network.id) - assert LABEL_SESSION_ID in network.attrs.get("Labels") + assert network.id is not None + docker_network = network._docker.client.networks.get(network_id=network.id) + labels = docker_network.attrs.get("Labels") + assert labels is not None + assert LABEL_SESSION_ID in labels finally: network.remove() diff --git a/core/tests/test_new_docker_api.py b/tests/core/test_new_docker_api.py similarity index 71% rename from core/tests/test_new_docker_api.py rename to tests/core/test_new_docker_api.py index 936efc82b..675ebab90 100644 --- a/core/tests/test_new_docker_api.py +++ b/tests/core/test_new_docker_api.py @@ -4,12 +4,11 @@ def test_docker_custom_image(): - container = DockerContainer("mysql:5.7.17") - container.with_exposed_ports(3306) - container.with_env("MYSQL_ROOT_PASSWORD", "root") + container = DockerContainer("nginx:alpine-slim") + container.with_exposed_ports(80) with container: - port = container.get_exposed_port(3306) + port = container.get_exposed_port(80) assert int(port) > 0 @@ -21,7 +20,9 @@ def test_docker_kwargs(): container_second = DockerContainer("nginx:latest") with container_first: - container_second.with_kwargs(volumes_from=[container_first._container.short_id]) + cf_c = container_first._container + assert cf_c is not None + container_second.with_kwargs(volumes_from=[cf_c.short_id]) with container_second: files_first = container_first.exec("ls /code").output.decode("utf-8").strip() files_second = container_second.exec("ls /code").output.decode("utf-8").strip() diff --git a/tests/core/test_protocol_compliance.py b/tests/core/test_protocol_compliance.py new file mode 100644 index 000000000..57f1642e1 --- /dev/null +++ b/tests/core/test_protocol_compliance.py @@ -0,0 +1,70 @@ +"""Test protocol compliance for wait strategy targets.""" + +from testcontainers.compose.compose import ComposeContainer +from testcontainers.core.container import DockerContainer +from testcontainers.core.waiting_utils import WaitStrategyTarget + + +def test_docker_container_implements_wait_strategy_target(): + """Test that DockerContainer implements all WaitStrategyTarget protocol methods.""" + container = DockerContainer("hello-world") + + # Check all required methods exist + assert hasattr(container, "get_container_host_ip") + assert hasattr(container, "get_exposed_port") + assert hasattr(container, "get_wrapped_container") + assert hasattr(container, "get_logs") + assert hasattr(container, "reload") + assert hasattr(container, "status") + + # Check method signatures are callable + assert callable(container.get_container_host_ip) + assert callable(container.get_exposed_port) + assert callable(container.get_wrapped_container) + assert callable(container.get_logs) + assert callable(container.reload) + + # Status should be a property + assert isinstance(container.__class__.status, property) + + +def test_compose_container_implements_wait_strategy_target(): + """Test that ComposeContainer implements all WaitStrategyTarget protocol methods.""" + container = ComposeContainer() + + # Check all required methods exist + assert hasattr(container, "get_container_host_ip") + assert hasattr(container, "get_exposed_port") + assert hasattr(container, "get_wrapped_container") + assert hasattr(container, "get_logs") + assert hasattr(container, "reload") + assert hasattr(container, "status") + + # Check method signatures are callable + assert callable(container.get_container_host_ip) + assert callable(container.get_exposed_port) + assert callable(container.get_wrapped_container) + assert callable(container.get_logs) + assert callable(container.reload) + + # Status should be a property + assert isinstance(container.__class__.status, property) + + +def test_protocol_typing_compatibility(): + """Test that both classes can be used where WaitStrategyTarget is expected.""" + + def function_expecting_protocol(target: WaitStrategyTarget) -> str: + """A function that expects a WaitStrategyTarget.""" + return "accepted" + + # These should work without type errors (structural typing) + docker_container = DockerContainer("hello-world") + compose_container = ComposeContainer() + + # If the classes properly implement the protocol, these should work + result1 = function_expecting_protocol(docker_container) + result2 = function_expecting_protocol(compose_container) + + assert result1 == "accepted" + assert result2 == "accepted" diff --git a/tests/core/test_ryuk.py b/tests/core/test_ryuk.py new file mode 100644 index 000000000..81bb736ca --- /dev/null +++ b/tests/core/test_ryuk.py @@ -0,0 +1,97 @@ +from time import perf_counter, sleep + +import pytest +from docker import DockerClient +from docker.errors import NotFound + +from testcontainers.core.config import testcontainers_config +from testcontainers.core.container import DockerContainer, Reaper +from testcontainers.core.utils import is_mac +from testcontainers.core.waiting_utils import wait_for_logs + + +def _wait_for_container_removed(client: DockerClient, container_id: str, timeout: float = 60) -> None: + """Poll until a container is fully removed (raises NotFound).""" + start = perf_counter() + while perf_counter() - start < timeout: + try: + client.containers.get(container_id) + except NotFound: + return + sleep(0.5) + + try: + c = client.containers.get(container_id) + name = c.name + status = c.status + started_at = c.attrs.get("State", {}).get("StartedAt", "unknown") + detail = f"name={name}, status={status}, started_at={started_at}" + except NotFound: + detail = "container disappeared just after timeout" + raise TimeoutError(f"Container {container_id} was not removed within {timeout}s ({detail})") + + +@pytest.mark.skipif( + is_mac(), + reason="Ryuk container reaping is unreliable on Docker Desktop for macOS due to VM-based container lifecycle handling", +) +@pytest.mark.inside_docker_check +def test_wait_for_reaper(monkeypatch: pytest.MonkeyPatch): + Reaper.delete_instance() + monkeypatch.setattr(testcontainers_config, "ryuk_reconnection_timeout", "0.1s") + container = DockerContainer("hello-world") + container.start() + + docker_client = container.get_docker_client().client + + container_id = container.get_wrapped_container().short_id + rc = Reaper._container + assert rc + reaper_id = rc.get_wrapped_container().short_id + + assert docker_client.containers.get(container_id) is not None + assert docker_client.containers.get(reaper_id) is not None + + wait_for_logs(container, "Hello from Docker!") + + rs = Reaper._socket + assert rs + rs.close() + + # Ryuk will reap containers then auto-remove itself. + # Wait for the reaper container to disappear and once it's gone, all labeled containers are guaranteed reaped. + _wait_for_container_removed(docker_client, reaper_id) + + # Verify both containers were reaped + with pytest.raises(NotFound): + docker_client.containers.get(container_id) + with pytest.raises(NotFound): + docker_client.containers.get(reaper_id) + + # Cleanup Ryuk class fields after manual Ryuk shutdown + Reaper.delete_instance() + + +@pytest.mark.skipif( + is_mac(), reason="Ryuk disabling behavior is unreliable on Docker Desktop for macOS due to Docker socket emulation" +) +@pytest.mark.inside_docker_check +def test_container_without_ryuk(monkeypatch: pytest.MonkeyPatch): + Reaper.delete_instance() + monkeypatch.setattr(testcontainers_config, "ryuk_disabled", True) + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + assert Reaper._instance is None + + +@pytest.mark.inside_docker_check +def test_ryuk_is_reused_in_same_process(): + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + reaper_instance = Reaper._instance + + assert reaper_instance is not None + + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + assert reaper_instance is Reaper._instance diff --git a/tests/core/test_socat.py b/tests/core/test_socat.py new file mode 100644 index 000000000..7852ed072 --- /dev/null +++ b/tests/core/test_socat.py @@ -0,0 +1,24 @@ +import requests + +from testcontainers.core.container import DockerContainer +from testcontainers.core.network import Network +from testcontainers.core.wait_strategies import HttpWaitStrategy +from testcontainers.socat.socat import SocatContainer + + +def test_socat_with_helloworld(): + with ( + Network() as network, + DockerContainer("testcontainers/helloworld:1.2.0") + .with_exposed_ports(8080) + .with_network(network) + .with_network_aliases("helloworld") + .waiting_for(HttpWaitStrategy(8080, "/ping")), + SocatContainer().with_network(network).with_target(8080, "helloworld") as socat, + ): + socat_url = f"http://{socat.get_container_host_ip()}:{socat.get_exposed_port(8080)}" + + response = requests.get(f"{socat_url}/ping") + + assert response.status_code == 200 + assert response.content == b"PONG" diff --git a/tests/core/test_transferable.py b/tests/core/test_transferable.py new file mode 100644 index 000000000..64d4da8be --- /dev/null +++ b/tests/core/test_transferable.py @@ -0,0 +1,166 @@ +import io +import tarfile +from pathlib import Path +from typing import Any + +import pytest + +from testcontainers.core.container import DockerContainer +from testcontainers.core.transferable import Transferable, TransferSpec, build_transfer_tar + + +def test_build_transfer_tar_from_bytes(): + data = b"hello world" + tar_bytes = build_transfer_tar(data, "/tmp/my_file") + + with tarfile.open(fileobj=io.BytesIO(tar_bytes)) as tar: + members = tar.getmembers() + assert len(members) == 1 + assert members[0].name == "/tmp/my_file" + assert members[0].size == len(data) + assert members[0].mode == 0o644 + extracted = tar.extractfile(members[0]) + assert extracted is not None + assert extracted.read() == data + + +def test_build_transfer_tar_from_file(tmp_path: Path): + my_file = tmp_path / "my_file" + my_file.write_bytes(b"file content") + + tar_bytes = build_transfer_tar(my_file, "/dest/my_file", mode=0o755) + + with tarfile.open(fileobj=io.BytesIO(tar_bytes)) as tar: + members = tar.getmembers() + assert len(members) == 1 + assert members[0].name == "/dest/my_file" + assert members[0].mode == 0o755 + extracted = tar.extractfile(members[0]) + assert extracted is not None + assert extracted.read() == b"file content" + + +def test_build_transfer_tar_from_directory(tmp_path: Path): + source_dir = tmp_path / "my_dir" + source_dir.mkdir() + (source_dir / "a.txt").write_bytes(b"aaa") + + tar_bytes = build_transfer_tar(source_dir, "/dest") + + with tarfile.open(fileobj=io.BytesIO(tar_bytes)) as tar: + names = tar.getnames() + assert any("my_dir" in n for n in names) + assert any("a.txt" in n for n in names) + + +def test_build_transfer_tar_rejects_invalid_type(): + with pytest.raises(TypeError, match="source must be bytes or Path"): + invalid: Any = 123 + build_transfer_tar(invalid, "/tmp/bad") + + +def test_build_transfer_tar_rejects_nonexistent_path(tmp_path: Path): + bad_path = tmp_path / "does_not_exist" + with pytest.raises(TypeError, match="neither a file nor directory"): + build_transfer_tar(bad_path, "/tmp/bad") + + +@pytest.fixture(name="transferable", params=(bytes, Path)) +def copy_sources_fixture(request, tmp_path: Path): + """ + Provide source argument for tests of copy_into_container + """ + raw_data = b"hello world" + if request.param is bytes: + return raw_data + elif request.param is Path: + my_file = tmp_path / "my_file" + my_file.write_bytes(raw_data) + return my_file + pytest.fail("Invalid type") + + +def test_copy_into_container_at_runtime(transferable: Transferable): + destination_in_container = "/tmp/my_file" + + with DockerContainer("bash", command="sleep infinity") as container: + container.copy_into_container(transferable, destination_in_container) + result = container.exec(f"cat {destination_in_container}") + + assert result.exit_code == 0 + assert result.output == b"hello world" + + +def test_copy_into_container_at_startup(transferable: Transferable): + destination_in_container = "/tmp/my_file" + + container = DockerContainer("bash", command="sleep infinity") + container.with_copy_into_container(transferable, destination_in_container) + + with container: + result = container.exec(f"cat {destination_in_container}") + + assert result.exit_code == 0 + assert result.output == b"hello world" + + +def test_copy_into_startup_file(transferable: Transferable): + destination_in_container = "/tmp/my_file" + + container = DockerContainer("bash", command=f"cat {destination_in_container}") + container.with_copy_into_container(transferable, destination_in_container) + + with container: + exit_code = container.wait() + stdout, _ = container.get_logs() + assert exit_code == 0 + assert stdout.decode() == "hello world" + + +def test_copy_into_container_via_initializer(transferable: Transferable): + destination_in_container = "/tmp/my_file" + transferables: list[TransferSpec] = [(transferable, destination_in_container, 0o644)] + + with DockerContainer("bash", command="sleep infinity", transferables=transferables) as container: + result = container.exec(f"cat {destination_in_container}") + + assert result.exit_code == 0 + assert result.output == b"hello world" + + +def test_copy_file_from_container(tmp_path: Path): + file_in_container = "/tmp/foo.txt" + destination_on_host = tmp_path / "foo.txt" + assert not destination_on_host.is_file() + + with DockerContainer("bash", command="sleep infinity") as container: + result = container.exec(f'bash -c "echo -n hello world > {file_in_container}"') + assert result.exit_code == 0 + container.copy_from_container(file_in_container, destination_on_host) + + assert destination_on_host.is_file() + assert destination_on_host.read_text() == "hello world" + + +def test_copy_directory_into_container(tmp_path: Path): + source_dir = tmp_path / "my_directory" + source_dir.mkdir() + my_file = source_dir / "my_file" + my_file.write_bytes(b"hello world") + + destination_in_container = "/tmp/my_destination_directory" + + with DockerContainer("bash", command="sleep infinity") as container: + container.copy_into_container(source_dir, destination_in_container) + result = container.exec(f"ls {destination_in_container}") + + assert result.exit_code == 0 + assert result.output == b"my_directory\n" + + result = container.exec(f"ls {destination_in_container}/my_directory") + assert result.exit_code == 0 + assert result.output == b"my_file\n" + + result = container.exec(f"cat {destination_in_container}/my_directory/my_file") + assert result.exit_code == 0 + assert result.output == b"hello world" diff --git a/core/tests/test_utils.py b/tests/core/test_utils.py similarity index 74% rename from core/tests/test_utils.py rename to tests/core/test_utils.py index 1923483ea..c4fdcfbc8 100644 --- a/core/tests/test_utils.py +++ b/tests/core/test_utils.py @@ -1,38 +1,40 @@ from pathlib import Path import pytest -from pytest import MonkeyPatch, raises, mark -from testcontainers.core import utils +import testcontainers.core.utils as utils def test_setup_logger() -> None: assert utils.setup_logger("test") is not None -@mark.parametrize("platform, expected", [("linux", "linux"), ("linux2", "linux"), ("darwin", "mac"), ("win32", "win")]) -def test_os_name(monkeypatch: MonkeyPatch, platform: str, expected: str) -> None: +@pytest.mark.parametrize( + "platform, expected", [("linux", "linux"), ("linux2", "linux"), ("darwin", "mac"), ("win32", "win")] +) +def test_os_name(monkeypatch: pytest.MonkeyPatch, platform: str, expected: str) -> None: assert utils.os_name() is not None monkeypatch.setattr("sys.platform", platform) assert utils.os_name() == expected -def test_is_mac(monkeypatch: MonkeyPatch) -> None: +def test_is_mac(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "mac") assert utils.is_mac() -def test_is_linux(monkeypatch: MonkeyPatch) -> None: +def test_is_linux(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "linux") assert utils.is_linux() -def test_is_windows(monkeypatch: MonkeyPatch) -> None: +def test_is_windows(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("testcontainers.core.utils.os_name", lambda: "win") assert utils.is_windows() -def test_is_arm(monkeypatch: MonkeyPatch) -> None: +def test_is_arm(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr("platform.machine", lambda: "x86_64") assert not utils.is_arm() monkeypatch.setattr("platform.machine", lambda: "arm64") assert utils.is_arm() @@ -40,7 +42,7 @@ def test_is_arm(monkeypatch: MonkeyPatch) -> None: assert utils.is_arm() -def test_inside_container(monkeypatch: MonkeyPatch) -> None: +def test_inside_container(monkeypatch: pytest.MonkeyPatch) -> None: assert not utils.inside_container() monkeypatch.setattr("os.path.exists", lambda _: True) assert utils.inside_container() @@ -50,7 +52,7 @@ def test_raise_for_deprecated_parameters() -> None: kwargs = {"key": "value"} current = "key" replacement = "new_key" - with raises(ValueError) as e: + with pytest.raises(ValueError) as e: result = utils.raise_for_deprecated_parameter(kwargs, current, replacement) assert str(e.value) == "Parameter 'deprecated' is deprecated and should be replaced by 'replacement'." assert result == {} @@ -66,7 +68,7 @@ def fake_cgroup(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path: def test_get_running_container_id_empty_or_missing(fake_cgroup: Path) -> None: # non existing does not fail but is only none assert utils.get_running_in_container_id() is None - fake_cgroup.write_text("12:devices:/system.slice/sshd.service\n" "13:cpuset:\n") + fake_cgroup.write_text("12:devices:/system.slice/sshd.service\n13:cpuset:\n") # missing docker does also not fail assert utils.get_running_in_container_id() is None diff --git a/core/tests/test_version.py b/tests/core/test_version.py similarity index 100% rename from core/tests/test_version.py rename to tests/core/test_version.py diff --git a/tests/core/test_wait_strategies.py b/tests/core/test_wait_strategies.py new file mode 100644 index 000000000..2f5f3d408 --- /dev/null +++ b/tests/core/test_wait_strategies.py @@ -0,0 +1,690 @@ +import itertools +import logging +import re +import time +from datetime import timedelta +from unittest.mock import Mock, patch + +import pytest + +from testcontainers.core.wait_strategies import ( + CompositeWaitStrategy, + FileExistsWaitStrategy, + HealthcheckWaitStrategy, + HttpWaitStrategy, + LogMessageWaitStrategy, + PortWaitStrategy, + WaitStrategy, + WaitStrategyTarget, +) + + +class ConcreteWaitStrategy(WaitStrategy): + """Concrete implementation for testing abstract base class.""" + + def wait_until_ready(self, container: "WaitStrategyTarget") -> None: + # Simple implementation that just waits a bit + time.sleep(0.1) + + +class TestWaitStrategy: + """Test the base WaitStrategy class.""" + + def test_wait_strategy_initialization(self): + strategy = ConcreteWaitStrategy() + assert strategy._startup_timeout > 0 + assert strategy._poll_interval > 0 + + @pytest.mark.parametrize( + "timeout_value,expected_seconds", + [ + (30, 30), + (timedelta(seconds=45), 45), + (60, 60), + (timedelta(minutes=2), 120), + ], + ids=[ + "timeout_int_30_seconds", + "timeout_timedelta_45_seconds", + "timeout_int_60_seconds", + "timeout_timedelta_2_minutes", + ], + ) + def test_with_startup_timeout(self, timeout_value, expected_seconds): + strategy = ConcreteWaitStrategy() + result = strategy.with_startup_timeout(timeout_value) + assert result is strategy + assert strategy._startup_timeout == expected_seconds + + @pytest.mark.parametrize( + "interval_value,expected_seconds", + [ + (2.5, 2.5), + (timedelta(seconds=3), 3.0), + (0.1, 0.1), + (timedelta(milliseconds=500), 0.5), + ], + ids=[ + "interval_float_2_5_seconds", + "interval_timedelta_3_seconds", + "interval_float_0_1_seconds", + "interval_timedelta_500_milliseconds", + ], + ) + def test_with_poll_interval(self, interval_value, expected_seconds): + strategy = ConcreteWaitStrategy() + result = strategy.with_poll_interval(interval_value) + assert result is strategy + assert strategy._poll_interval == expected_seconds + + def test_abstract_method(self): + # Test that abstract base class cannot be instantiated + with pytest.raises(TypeError, match="Can't instantiate abstract class"): + WaitStrategy() # type: ignore[abstract] + + +class TestLogMessageWaitStrategy: + """Test the LogMessageWaitStrategy class.""" + + @pytest.mark.parametrize( + "message,times,predicate_streams_and", + [ + ("test message", 1, False), + (re.compile(r"test\d+"), 1, False), + ("test", 3, False), + ("test", 1, True), + ("ready", 2, True), + ], + ids=[ + "simple_string_message", + "regex_pattern_message", + "message_with_times_3", + "message_with_predicate_streams_and_true", + "ready_message_with_times_and_predicate", + ], + ) + def test_log_message_wait_strategy_initialization(self, message, times, predicate_streams_and): + strategy = LogMessageWaitStrategy(message, times=times, predicate_streams_and=predicate_streams_and) + + if isinstance(message, str): + assert strategy._message.pattern == message + else: + assert strategy._message is message + + assert strategy._times == times + assert strategy._predicate_streams_and is predicate_streams_and + + @pytest.mark.parametrize( + "container_logs,expected_message,should_succeed", + [ + ((b"test message", b""), "test message", True), + ((b"", b"test message"), "test message", True), + ((b"no match", b""), "test message", False), + ((b"test123", b""), re.compile(r"test\d+"), True), + ((b"test", b""), re.compile(r"test\d+"), False), + ], + ids=[ + "stdout_contains_message_success", + "stderr_contains_message_success", + "no_message_match_failure", + "regex_pattern_match_success", + "regex_pattern_no_match_failure", + ], + ) + @patch("time.time") + @patch("time.sleep") + def test_wait_until_ready(self, mock_sleep, mock_time, container_logs, expected_message, should_succeed): + strategy = LogMessageWaitStrategy(expected_message) + mock_container = Mock() + mock_container.get_logs.return_value = container_logs + # Mock the wrapped container to simulate a running container + mock_wrapped = Mock() + mock_wrapped.status = "running" + mock_wrapped.reload.return_value = None + mock_container.get_wrapped_container.return_value = mock_wrapped + # Configure time mock to simulate timeout for failure cases + if should_succeed: + mock_time.side_effect = [0, 1] + else: + mock_time.side_effect = itertools.count(start=0, step=1) + if should_succeed: + strategy.wait_until_ready(mock_container) + mock_container.get_logs.assert_called_once() + else: + with pytest.raises(TimeoutError): + strategy.wait_until_ready(mock_container) + + +class TestHttpWaitStrategy: + """Test the HttpWaitStrategy class.""" + + @pytest.mark.parametrize( + "port,path,expected_port,expected_path", + [ + (8080, "/health", 8080, "/health"), + (80, None, 80, "/"), + (443, "/api/status", 443, "/api/status"), + (3000, "", 3000, "/"), + ], + ids=[ + "port_8080_health_path", + "port_80_default_path", + "port_443_api_status_path", + "port_3000_empty_path", + ], + ) + def test_http_wait_strategy_initialization(self, port, path, expected_port, expected_path): + strategy = HttpWaitStrategy(port, path) + assert strategy._port == expected_port + assert strategy._path == expected_path + assert strategy._status_codes == {200} + assert strategy._method == "GET" + + @pytest.mark.parametrize( + "status_codes", + [ + [404], + [200, 201], + [500, 502, 503], + [200, 404, 500], + ], + ids=[ + "single_status_code_404", + "multiple_status_codes_200_201", + "error_status_codes_500_502_503", + "mixed_status_codes_200_404_500", + ], + ) + def test_for_status_code(self, status_codes): + strategy = HttpWaitStrategy(8080) + + for code in status_codes: + result = strategy.for_status_code(code) + assert result is strategy + assert code in strategy._status_codes + + # Default 200 should still be there + assert 200 in strategy._status_codes + + @pytest.mark.parametrize( + "predicate_description,status_code_predicate,response_predicate", + [ + ("status_code_range", lambda code: 200 <= code < 300, None), + ("status_code_equals_200", lambda code: code == 200, None), + ("response_contains_ready", None, lambda body: "ready" in body), + ("response_json_valid", None, lambda body: "status" in body), + ("both_predicates", lambda code: code >= 200, lambda body: len(body) > 0), + ], + ids=[ + "status_code_range_200_to_300", + "status_code_equals_200", + "response_contains_ready", + "response_json_valid", + "both_status_and_response_predicates", + ], + ) + def test_predicates(self, predicate_description, status_code_predicate, response_predicate): + strategy = HttpWaitStrategy(8080) + + if status_code_predicate: + result = strategy.for_status_code_matching(status_code_predicate) + assert result is strategy + assert strategy._status_code_predicate is status_code_predicate + + if response_predicate: + result = strategy.for_response_predicate(response_predicate) + assert result is strategy + assert strategy._response_predicate is response_predicate + + @pytest.mark.parametrize( + "tls_config,expected_tls,expected_insecure", + [ + ({"insecure": True}, True, True), + ({"insecure": False}, True, False), + ({}, True, False), + ], + ids=[ + "tls_insecure_true", + "tls_insecure_false", + "tls_default_insecure_false", + ], + ) + def test_using_tls(self, tls_config, expected_tls, expected_insecure): + strategy = HttpWaitStrategy(8080) + result = strategy.using_tls(**tls_config) + assert result is strategy + assert strategy._tls is expected_tls + assert strategy._insecure_tls is expected_insecure + + @pytest.mark.parametrize( + "headers", + [ + {"Authorization": "Bearer token"}, + {"Content-Type": "application/json"}, + {"User-Agent": "test", "Accept": "text/html"}, + ], + ids=[ + "single_header_authorization", + "single_header_content_type", + "multiple_headers_user_agent_accept", + ], + ) + def test_with_header(self, headers): + strategy = HttpWaitStrategy(8080) + + for key, value in headers.items(): + result = strategy.with_header(key, value) + assert result is strategy + assert strategy._headers[key] == value + + @pytest.mark.parametrize( + "credentials", + [ + ("user", "pass"), + ("admin", "secret123"), + ("test", ""), + ], + ids=[ + "basic_credentials_user_pass", + "basic_credentials_admin_secret", + "basic_credentials_test_empty", + ], + ) + def test_with_basic_credentials(self, credentials): + strategy = HttpWaitStrategy(8080) + result = strategy.with_basic_credentials(*credentials) + assert result is strategy + assert strategy._basic_auth == credentials + + @pytest.mark.parametrize( + "method", + [ + "GET", + "POST", + "PUT", + "DELETE", + "HEAD", + ], + ids=[ + "method_get", + "method_post", + "method_put", + "method_delete", + "method_head", + ], + ) + def test_with_method(self, method): + strategy = HttpWaitStrategy(8080) + result = strategy.with_method(method) + assert result is strategy + assert strategy._method == method + + @pytest.mark.parametrize( + "body", + [ + '{"key": "value"}', + '{"status": "ready"}', + "data=test&format=json", + "", + ], + ids=[ + "json_body_key_value", + "json_body_status_ready", + "form_data_body", + "empty_body", + ], + ) + def test_with_body(self, body): + strategy = HttpWaitStrategy(8080) + result = strategy.with_body(body) + assert result is strategy + assert strategy._body == body + + @pytest.mark.parametrize( + "url,expected_port,expected_path,expected_tls", + [ + ("https://localhost:8080/api/health", 8080, "/api/health", True), + ("http://localhost:3000", 3000, "/", False), + ("https://example.com", 443, "/", True), + ("http://localhost:80/", 80, "/", False), + ], + ids=[ + "https_localhost_8080_api_health", + "http_localhost_3000_default_path", + "https_example_com_default_port", + "http_localhost_80_root_path", + ], + ) + def test_from_url(self, url, expected_port, expected_path, expected_tls): + strategy = HttpWaitStrategy.from_url(url) + assert strategy._port == expected_port + assert strategy._path == expected_path + assert strategy._tls is expected_tls + + +class TestHealthcheckWaitStrategy: + """Test the HealthcheckWaitStrategy class.""" + + def test_healthcheck_wait_strategy_initialization(self): + strategy = HealthcheckWaitStrategy() + # Should inherit from WaitStrategy + assert hasattr(strategy, "_startup_timeout") + assert hasattr(strategy, "_poll_interval") + + @pytest.mark.parametrize( + "health_status,health_log,should_succeed,expected_error", + [ + ("healthy", None, True, None), + ( + "unhealthy", + [{"ExitCode": 1, "Output": "Health check failed"}], + False, + "Container health check reported unhealthy", + ), + ("starting", None, False, "Container health check did not report healthy within 120.* seconds"), + (None, None, False, "No health check configured"), + ], + ids=[ + "healthy_status_success", + "unhealthy_status_failure", + "starting_status_failure", + "no_healthcheck_failure", + ], + ) + @patch("time.time") + @patch("time.sleep") + def test_wait_until_ready(self, mock_sleep, mock_time, health_status, health_log, should_succeed, expected_error): + strategy = HealthcheckWaitStrategy() + mock_container = Mock() + + # Mock the wrapped container + mock_wrapped = Mock() + mock_wrapped.status = "running" + mock_wrapped.reload.return_value = None + + # Mock health check data + health_data = {} + if health_status: + health_data = {"Status": health_status} + if health_log: + health_data["Log"] = health_log + + mock_wrapped.attrs = {"State": {"Health": health_data}} + + mock_container.get_wrapped_container.return_value = mock_wrapped + + # Configure time mock based on expected behavior + if should_succeed: + mock_time.side_effect = [0, 1] + else: + # For failure cases, we need more time values to handle the loop + mock_time.side_effect = itertools.count(start=0, step=1) # Provide enough values for the loop + + if should_succeed: + strategy.wait_until_ready(mock_container) + else: + with pytest.raises((RuntimeError, TimeoutError), match=expected_error): + strategy.wait_until_ready(mock_container) + + +class TestPortWaitStrategy: + """Test the PortWaitStrategy class.""" + + @pytest.mark.parametrize( + "port", + [ + 8080, + 80, + 443, + 22, + 3306, + ], + ids=[ + "port_8080", + "port_80", + "port_443", + "port_22", + "port_3306", + ], + ) + def test_port_wait_strategy_initialization(self, port): + strategy = PortWaitStrategy(port) + assert strategy._port == port + + @pytest.mark.parametrize( + "connection_success,expected_behavior", + [ + (True, "success"), + (False, "timeout"), + ], + ids=[ + "socket_connection_success", + "socket_connection_timeout", + ], + ) + @patch("socket.socket") + @patch("time.time") + @patch("time.sleep") + def test_wait_until_ready(self, mock_sleep, mock_time, mock_socket, connection_success, expected_behavior): + strategy = PortWaitStrategy(8080).with_startup_timeout(1) + mock_container = Mock() + mock_container.get_container_host_ip.return_value = "localhost" + mock_container.get_exposed_port.return_value = 8080 + + # Mock socket connection + mock_socket_instance = Mock() + if connection_success: + mock_socket.return_value.__enter__.return_value = mock_socket_instance + mock_time.side_effect = [0, 1] + else: + mock_socket_instance.connect.side_effect = ConnectionRefusedError() + mock_socket.return_value.__enter__.return_value = mock_socket_instance + mock_time.side_effect = [0, 2] # Exceed timeout + + if expected_behavior == "success": + strategy.wait_until_ready(mock_container) + mock_socket_instance.connect.assert_called_once_with(("localhost", 8080)) + else: + with pytest.raises(TimeoutError, match=r"Port 8080 not available within 1.0 seconds"): + strategy.wait_until_ready(mock_container) + + +class TestFileExistsWaitStrategy: + """Test the FileExistsWaitStrategy class.""" + + @pytest.mark.parametrize( + "file_path", + [ + "/tmp/test.txt", + "/var/log/app.log", + "/opt/app/config.yaml", + "relative/path/file.conf", + ], + ids=[ + "tmp_file_path", + "var_log_file_path", + "opt_config_file_path", + "relative_file_path", + ], + ) + def test_file_exists_wait_strategy_initialization(self, file_path): + strategy = FileExistsWaitStrategy(file_path) + # _file_path is stored as a Path object + assert str(strategy._file_path) == file_path + # Should inherit from WaitStrategy + assert hasattr(strategy, "_startup_timeout") + assert hasattr(strategy, "_poll_interval") + + @pytest.mark.parametrize( + "file_exists,expected_behavior", + [ + (True, "success"), + (False, "timeout"), + ], + ids=[ + "file_exists_success", + "file_not_exists_timeout", + ], + ) + @patch("pathlib.Path.is_file") + @patch("time.time") + @patch("time.sleep") + def test_wait_until_ready(self, mock_sleep, mock_time, mock_is_file, file_exists, expected_behavior, caplog): + strategy = FileExistsWaitStrategy("/tmp/test.txt").with_startup_timeout(1) + mock_container = Mock() + + # Configure mocks based on expected behavior + if file_exists: + mock_is_file.return_value = True + # Need multiple time values for debug logging + mock_time.side_effect = [0, 0.1, 0.2] # Start time, check time, logging time + else: + mock_is_file.return_value = False + # Need more time values for the loop and logging calls + mock_time.side_effect = itertools.count(start=0, step=0.6) # Exceed timeout after a few iterations + + if expected_behavior == "success": + strategy.wait_until_ready(mock_container) + mock_is_file.assert_called() + else: + with pytest.raises(TimeoutError, match=r"File.*did not exist within.*seconds"): + with caplog.at_level(logging.CRITICAL, logger="testcontainers.core.wait_strategies"): + strategy.wait_until_ready(mock_container) + + +class TestCompositeWaitStrategy: + """Test the CompositeWaitStrategy class.""" + + def test_composite_wait_strategy_initialization_single_strategy(self): + """Test initialization with a single strategy.""" + log_strategy = LogMessageWaitStrategy("ready") + composite = CompositeWaitStrategy(log_strategy) + assert composite._strategies == [log_strategy] + + def test_composite_wait_strategy_initialization_multiple_strategies(self): + """Test initialization with multiple strategies.""" + log_strategy = LogMessageWaitStrategy("ready") + port_strategy = PortWaitStrategy(8080) + file_strategy = FileExistsWaitStrategy("/tmp/test.txt") + + composite = CompositeWaitStrategy(log_strategy, port_strategy, file_strategy) + assert composite._strategies == [log_strategy, port_strategy, file_strategy] + + def test_composite_wait_strategy_initialization_empty(self): + """Test that empty initialization works (creates empty list).""" + composite = CompositeWaitStrategy() + assert composite._strategies == [] + + def test_with_startup_timeout_propagates_to_child_strategies(self): + """Test that timeout setting propagates to child strategies.""" + log_strategy = LogMessageWaitStrategy("ready") + composite = CompositeWaitStrategy(log_strategy) + result = composite.with_startup_timeout(30) + assert result is composite + assert composite._startup_timeout == 30 + # Should also propagate to child strategies + assert log_strategy._startup_timeout == 30 + + def test_with_poll_interval_propagates_to_child_strategies(self): + """Test that poll interval setting propagates to child strategies.""" + port_strategy = PortWaitStrategy(8080) + composite = CompositeWaitStrategy(port_strategy) + result = composite.with_poll_interval(2.0) + assert result is composite + assert composite._poll_interval == 2.0 + # Should also propagate to child strategies + assert port_strategy._poll_interval == 2.0 + + def test_wait_until_ready_all_strategies_succeed(self): + """Test that all strategies are executed when they all succeed.""" + # Create mock strategies + strategy1 = Mock() + strategy2 = Mock() + strategy3 = Mock() + + composite = CompositeWaitStrategy(strategy1, strategy2, strategy3) + mock_container = Mock() + + # All strategies should succeed + strategy1.wait_until_ready.return_value = None + strategy2.wait_until_ready.return_value = None + strategy3.wait_until_ready.return_value = None + + composite.wait_until_ready(mock_container) + + # Verify all strategies were called in order + strategy1.wait_until_ready.assert_called_once_with(mock_container) + strategy2.wait_until_ready.assert_called_once_with(mock_container) + strategy3.wait_until_ready.assert_called_once_with(mock_container) + + def test_wait_until_ready_first_strategy_fails(self, caplog): + """Test that execution stops when first strategy fails.""" + strategy1 = Mock() + strategy2 = Mock() + strategy3 = Mock() + + composite = CompositeWaitStrategy(strategy1, strategy2, strategy3) + mock_container = Mock() + + # First strategy fails + strategy1.wait_until_ready.side_effect = TimeoutError("First strategy failed") + + with pytest.raises(TimeoutError, match="First strategy failed"): + with caplog.at_level(logging.CRITICAL, logger="testcontainers.core.wait_strategies"): + composite.wait_until_ready(mock_container) + + # Only first strategy should be called + strategy1.wait_until_ready.assert_called_once_with(mock_container) + strategy2.wait_until_ready.assert_not_called() + strategy3.wait_until_ready.assert_not_called() + + def test_wait_until_ready_middle_strategy_fails(self): + """Test that execution stops when middle strategy fails.""" + strategy1 = Mock() + strategy2 = Mock() + strategy3 = Mock() + + composite = CompositeWaitStrategy(strategy1, strategy2, strategy3) + mock_container = Mock() + + # First succeeds, second fails + strategy1.wait_until_ready.return_value = None + strategy2.wait_until_ready.side_effect = RuntimeError("Second strategy failed") + + with pytest.raises(RuntimeError, match="Second strategy failed"): + composite.wait_until_ready(mock_container) + + # First two strategies should be called + strategy1.wait_until_ready.assert_called_once_with(mock_container) + strategy2.wait_until_ready.assert_called_once_with(mock_container) + strategy3.wait_until_ready.assert_not_called() + + @pytest.mark.parametrize( + "strategy_types,expected_count", + [ + (["log"], 1), + (["log", "port"], 2), + (["log", "port", "file"], 3), + (["file", "log", "port", "file"], 4), + ], + ids=[ + "single_log_strategy", + "log_and_port_strategies", + "three_different_strategies", + "four_strategies_with_duplicate_type", + ], + ) + def test_composite_strategy_count(self, strategy_types, expected_count): + """Test that composite strategy handles different numbers of strategies.""" + strategies: list[WaitStrategy] = [] + for strategy_type in strategy_types: + if strategy_type == "log": + strategies.append(LogMessageWaitStrategy("ready")) + elif strategy_type == "port": + strategies.append(PortWaitStrategy(8080)) + elif strategy_type == "file": + strategies.append(FileExistsWaitStrategy("/tmp/test.txt")) + + composite = CompositeWaitStrategy(*strategies) + assert len(composite._strategies) == expected_count + assert composite._strategies == strategies diff --git a/tests/core/test_wait_strategies_integration.py b/tests/core/test_wait_strategies_integration.py new file mode 100644 index 000000000..e54fc7c9b --- /dev/null +++ b/tests/core/test_wait_strategies_integration.py @@ -0,0 +1,85 @@ +from pathlib import Path + +import pytest + +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import LogMessageWaitStrategy + + +class TestRealDockerIntegration: + """Integration tests using real Docker containers.""" + + def test_log_message_wait_strategy_with_real_container(self): + """Test LogMessageWaitStrategy with a real container that outputs known logs.""" + strategy = LogMessageWaitStrategy("Hello from Docker!") + + with DockerContainer("hello-world").waiting_for(strategy) as container: + # If we get here, the strategy worked + assert container.get_wrapped_container() is not None + + def test_wait_strategy_timeout_with_real_container(self): + """Test that wait strategies properly timeout with real containers.""" + # Use a very short timeout with a condition that won't be met + strategy = LogMessageWaitStrategy("this_message_will_never_appear").with_startup_timeout(2) + + with pytest.raises(TimeoutError): + with DockerContainer("alpine:latest").with_command("sleep 10").waiting_for(strategy): + pass # Should not reach here + + +class TestDockerComposeIntegration: + """Integration tests for wait strategies with Docker Compose.""" + + def test_compose_service_wait_strategies(self): + """Test that wait strategies work with Docker Compose services.""" + from pathlib import Path + + from testcontainers.compose import DockerCompose + + # Use basic_multiple fixture with two alpine services that output logs + compose = DockerCompose( + context=Path(__file__).parent / "compose_fixtures" / "basic_multiple", + compose_file_name="docker-compose.yaml", + ) + + # Configure wait strategies for both services + # Wait for the date output that these containers produce + compose.waiting_for( + { + "alpine1": LogMessageWaitStrategy("202").with_startup_timeout(30), # Date includes year 202X + "alpine2": LogMessageWaitStrategy("202").with_startup_timeout(30), # Date includes year 202X + } + ) + + with compose: + # Verify both services are running + container1 = compose.get_container("alpine1") + container2 = compose.get_container("alpine2") + + assert container1.State == "running" + assert container2.State == "running" + + # Verify logs contain expected patterns + logs1 = container1.get_logs() + logs2 = container2.get_logs() + + # Both containers should have date output (which contains "202" for year 202X) + assert any(b"202" in log for log in logs1) + assert any(b"202" in log for log in logs2) + + def test_compose_wait_strategy_timeout(self): + """Test that compose wait strategies properly timeout.""" + from testcontainers.compose import DockerCompose + + compose = DockerCompose( + context=Path(__file__).parent / "compose_fixtures" / "basic", compose_file_name="docker-compose.yaml" + ) + + # Use a wait strategy that will never succeed with very short timeout + compose.waiting_for( + {"alpine": LogMessageWaitStrategy("this_message_will_never_appear").with_startup_timeout(2)} + ) + + with pytest.raises(TimeoutError): + with compose: + pass # Should not reach here diff --git a/tests/core/test_waiting_utils.py b/tests/core/test_waiting_utils.py new file mode 100644 index 000000000..b3cc43056 --- /dev/null +++ b/tests/core/test_waiting_utils.py @@ -0,0 +1,46 @@ +import pytest + +from testcontainers.core.container import DockerContainer +from testcontainers.core.wait_strategies import ContainerStatusWaitStrategy +from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs + + +def test_wait_for_logs() -> None: + with DockerContainer("hello-world") as container: + wait_for_logs(container, "Hello from Docker!") + + +def test_timeout_is_raised_when_waiting_for_logs() -> None: + with pytest.raises(TimeoutError), DockerContainer("alpine").with_command("sleep 2") as container: + wait_for_logs(container, "Hello from Docker!", timeout=1e-3) + + +def test_wait_container_is_ready_decorator_basic() -> None: + """Test the basic wait_container_is_ready decorator functionality.""" + + @wait_container_is_ready() + def simple_check() -> bool: + return True + + result = simple_check() + assert result is True + + +def test_wait_container_is_ready_decorator_with_container() -> None: + """Test wait_container_is_ready decorator with a real container.""" + + def check_container_logs(container: DockerContainer) -> bool: + # wait until it becomes running. + # if it is too late, it is actually fine in this case. + # we are happy with an exited (even crashed) container that has logs. + try: + ContainerStatusWaitStrategy().wait_until_ready(container) + except TimeoutError: + pass + + stdout, stderr = container.get_logs() + return b"Hello from Docker!" in stdout or b"Hello from Docker!" in stderr + + with DockerContainer("hello-world") as container: + result = check_container_logs(container) + assert result is True diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..e6c8190a5 --- /dev/null +++ b/uv.lock @@ -0,0 +1,6444 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/4d/4a99fb425c5e0cad715eea7bd190aff46f38b959a0a2dadb993705d34b26/aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b", size = 765848, upload-time = "2026-07-23T01:52:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/74/e8/43b85dc55b8e950dc644babe762add781319ea881b57b33d2cce12017d12/aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a", size = 517476, upload-time = "2026-07-23T01:52:10.846Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9e/73b582c4dbbc3c12ef4473822475effaabf1f934b56f14f5b03fe5d3a2af/aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5", size = 515334, upload-time = "2026-07-23T01:52:12.636Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/e98c3c9e05a5bdf97defe5ff9169baba4f0ec9a901f2d60e0f060c2f051e/aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f", size = 1708830, upload-time = "2026-07-23T01:52:14.538Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2c/26e60b694844dfd2176c57f913a22d0cd6a16f9ff202cbda7580d0328b98/aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43", size = 1674012, upload-time = "2026-07-23T01:52:16.486Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/672df92e3172cd876aacfa97a952ac560877eb169384b2991ac5b273de4c/aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9", size = 1767015, upload-time = "2026-07-23T01:52:18.28Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c5/228dec7bfec1c373cc2217cdeb47d6456dcd7a13a4c55144930a75ae3851/aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8", size = 1858700, upload-time = "2026-07-23T01:52:20.08Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ff/cb36724e8c8d17f90ada567a9ff3efe1d6e9b549fba697a242aece180f21/aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479", size = 1714075, upload-time = "2026-07-23T01:52:22.071Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3a/296a4135c6366376263aeef54b15caca1f07676c2ae0c525d7832f2f808a/aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b", size = 1588234, upload-time = "2026-07-23T01:52:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/7d/81/9d5d853ef892dc066d1eb6db0e87a47348b920c1c879aa554612fdbd9d79/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d", size = 1677300, upload-time = "2026-07-23T01:52:25.861Z" }, + { url = "https://files.pythonhosted.org/packages/68/96/021d386ae32d9b26d4b88df2e794546232ff56bb6be952bf6be227c0bbc7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d", size = 1691501, upload-time = "2026-07-23T01:52:28Z" }, + { url = "https://files.pythonhosted.org/packages/29/9f/af66adce26a14af135c003cbd0f44ccaa68cebd30ff8ac99ca47fb4958f7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2", size = 1735113, upload-time = "2026-07-23T01:52:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/28c390d4c9851effe52ac25b5a2e1d92246acd00728b4fc7975dafb67484/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48", size = 1577486, upload-time = "2026-07-23T01:52:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/db/c2/00e23a1bf2abb70dd353f6987db7e7f2491d0261f7363997738c71c98f95/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f", size = 1751353, upload-time = "2026-07-23T01:52:33.688Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7d/d51a706a8cbfa57f0611127daf61ab3ae02ab8420b0407412079227d1c65/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32", size = 1698681, upload-time = "2026-07-23T01:52:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b0/90bd5cd9fdd9787cb4211d284d1fb8401339a933cb0227a15b71e789232f/aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e", size = 456733, upload-time = "2026-07-23T01:52:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d8/15/fe5b8f6a71ae112bc677163d0b0701bda5dc15005249582258ede0eb88c7/aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c", size = 480460, upload-time = "2026-07-23T01:52:43.905Z" }, + { url = "https://files.pythonhosted.org/packages/54/00/45e98b6645cd7f00a4b78b749ebd309094b0eaeb2d2e96157eadbc0d0050/aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb", size = 453479, upload-time = "2026-07-23T01:52:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" }, + { url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" }, + { url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" }, + { url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549, upload-time = "2025-07-30T10:02:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539, upload-time = "2025-07-30T10:02:00.929Z" }, + { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467, upload-time = "2025-07-30T10:02:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355, upload-time = "2025-07-30T10:02:02.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187, upload-time = "2025-07-30T10:02:03.674Z" }, +] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" }, + { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "authlib" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "joserfc" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" }, +] + +[[package]] +name = "azure-core" +version = "1.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/f3/b416179e408990df5db0d516283022dde0f5d0111d98c1a848e41853e81c/azure_core-1.41.0.tar.gz", hash = "sha256:f46ff5dfcd230f25cf1c19e8a34b8dc08a337b2503e268bb600a16c00db8ad5a", size = 381042, upload-time = "2026-05-07T23:30:54.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/db/325c6d7312d2200251c52323878281045aaffcb5586612296484e4280eaa/azure_core-1.41.0-py3-none-any.whl", hash = "sha256:522b4011e8180b1a3dcd2024396a4e7fe9ac37fb8597db47163d230b5efe892d", size = 220920, upload-time = "2026-05-07T23:30:56.357Z" }, +] + +[[package]] +name = "azure-cosmos" +version = "4.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/2a/0f2bba256e56626ba2cec97ab81dd002ff47ead1329767760b619afd927a/azure_cosmos-4.16.1.tar.gz", hash = "sha256:fa15d13702b470265a67e2dd9c0794021e6b776856dac6c223dcacc4d8e1d8d1", size = 2377651, upload-time = "2026-06-02T01:08:07.656Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b4/9c984ad33ca9e5c378ea472fc9aa38e719c245a4fa58a99d6dabbe0f9a17/azure_cosmos-4.16.1-py3-none-any.whl", hash = "sha256:43717215ec1433c1ea0f0e3d465f9182fb5afff3ae2331e595367122297872f1", size = 499044, upload-time = "2026-06-02T01:08:10.111Z" }, +] + +[[package]] +name = "azure-storage-blob" +version = "12.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/48/84a820d898267f662b5c06f7cd76fdb8a9e272b44aa9376cef3ec0f6a294/azure_storage_blob-12.30.0.tar.gz", hash = "sha256:2cd74d4d5731e5eb6b8d5c5056ee115a5e88f8fdf22517b739836fda685018be", size = 618229, upload-time = "2026-06-08T11:45:35.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/0b/e106f0fd7fa785867d9ffcc47dc9e6237c0e58f51058473b777487a98edc/azure_storage_blob-12.30.0-py3-none-any.whl", hash = "sha256:d415ac50b67a8da6b3ae7e9f1014b1b55cd7aafa0b8d4ca9b380568dc7360423", size = 435610, upload-time = "2026-06-08T11:45:37.213Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, +] + +[[package]] +name = "backrefs" +version = "7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, + { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, +] + +[[package]] +name = "bcrypt" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" }, + { url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" }, + { url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" }, + { url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" }, + { url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" }, + { url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" }, + { url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" }, + { url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" }, + { url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" }, + { url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" }, + { url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" }, + { url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" }, + { url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" }, + { url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" }, + { url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" }, + { url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" }, + { url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/75/4aa9f5a4d40d762892066ba1046000b329c7cd58e888a6db878019b282dc/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534", size = 271180, upload-time = "2025-09-25T19:50:38.575Z" }, + { url = "https://files.pythonhosted.org/packages/54/79/875f9558179573d40a9cc743038ac2bf67dfb79cecb1e8b5d70e88c94c3d/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4", size = 273791, upload-time = "2025-09-25T19:50:39.913Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fe/975adb8c216174bf70fc17535f75e85ac06ed5252ea077be10d9cff5ce24/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911", size = 270746, upload-time = "2025-09-25T19:50:43.306Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f8/972c96f5a2b6c4b3deca57009d93e946bbdbe2241dca9806d502f29dd3ee/bcrypt-5.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4", size = 273375, upload-time = "2025-09-25T19:50:45.43Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/2c/e7f668f2d022b5c7dca881c7a9530aa7200445566c06f0f23daf4bf78084/boto3-1.43.50.tar.gz", hash = "sha256:a6865b9cd1d15511f6aad89cef964105acecff2160a3e29ae53c10d9821416af", size = 112731, upload-time = "2026-07-16T19:33:15.719Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/db/603de8ccaac4ffa7ddc2cea6cdeb9f8f7d997d892af73edd5191e99d0ead/boto3-1.43.50-py3-none-any.whl", hash = "sha256:779eb5cfa8bd1d615f36051465f8182860219eef78b4ab8ca2c666bb56702325", size = 140030, upload-time = "2026-07-16T19:33:14.233Z" }, +] + +[[package]] +name = "boto3-stubs-lite" +version = "1.43.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore-stubs" }, + { name = "types-s3transfer" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/63/5d0c2b6a7fbe812fcb84e8c216006610d9b3c7e9ecdea29c4ddd86681c46/boto3_stubs_lite-1.43.50.tar.gz", hash = "sha256:4ab78d86983b12c92156d1ba80a949913c199fd1770773d97b182df57d41f561", size = 74105, upload-time = "2026-07-16T19:56:39.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/00/904f83e26224391d0f77cce0f333268a8ceaa49960e837dcb0c87d7579c1/boto3_stubs_lite-1.43.50-py3-none-any.whl", hash = "sha256:458f32dc0b23acebd2771a7e4b207176cec2b1c50daeecb2b26a45bea05dffb9", size = 43272, upload-time = "2026-07-16T19:56:36.883Z" }, +] + +[package.optional-dependencies] +boto3 = [ + { name = "boto3" }, +] + +[[package]] +name = "botocore" +version = "1.43.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/3b/0a640eb595fb86e853c06301982393e60cbaba634cb0fc70bbdc119e1140/botocore-1.43.50.tar.gz", hash = "sha256:7b07c423c44b1ab3815af103f11fa28ea317e28dda1335718a02ecde371a25e9", size = 15709326, upload-time = "2026-07-16T19:33:05.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/32/405d01c0bcd9deab0d3744894a328ce12899824d284480f7b3f4c6cdfe40/botocore-1.43.50-py3-none-any.whl", hash = "sha256:4a57a1e0dd4a8be855c29183fc0e029c18e9ec84cd4ac172869f3e800f972528", size = 15394880, upload-time = "2026-07-16T19:33:02.097Z" }, +] + +[[package]] +name = "botocore-stubs" +version = "1.43.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-awscrt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/81/79693e833291c00dc89ee610e5e915381b6f08233912e28df50106840780/botocore_stubs-1.43.14.tar.gz", hash = "sha256:9e3bc1fdd51da7473f0df726c82747a1b0ae913449d629659765c247fecc2039", size = 42738, upload-time = "2026-05-25T06:06:37.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ca/f017727b11895908c5dedc829cf2ec35e0c4b2a26ba875db325fef2cefdf/botocore_stubs-1.43.14-py3-none-any.whl", hash = "sha256:fb98f1475c92fd718644e786b5c543a20f1b1f610e89e0a7191c3f1f429c75aa", size = 67093, upload-time = "2026-05-25T06:06:34.532Z" }, +] + +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + +[[package]] +name = "cachetools" +version = "7.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, +] + +[[package]] +name = "cassandra-driver" +version = "3.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "geomet" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/26/8806d0949422b560029a040e7a628d92addd612726468c3fb546354a43a4/cassandra_driver-3.30.0.tar.gz", hash = "sha256:7e4cfd6ec3023576ed0ffa34882d9778e4bacfd918048ae9139ccdd00628ed85", size = 4242039, upload-time = "2026-04-16T05:04:59.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/01/b13641332953702238b5cdf80340a117b384070fc1cc582030d7dffbde14/cassandra_driver-3.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2637644eac9274e46b0c2a7f729158bdf8582b6842dc48e18297211dd3ee1fec", size = 7612055, upload-time = "2026-04-16T05:01:56.997Z" }, + { url = "https://files.pythonhosted.org/packages/a6/52/1361729ce8e5ded444b11e00eee19df09e040d46236c7df7cded51913c7a/cassandra_driver-3.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a5c8982f2b9eb4e789fc12cdd930b1e1511b6d046dde31d0703f855745556a3", size = 7367328, upload-time = "2026-04-16T05:02:06.188Z" }, + { url = "https://files.pythonhosted.org/packages/96/c6/b4d5b01e10570dfb3394ebf7af40acbe435caaf246553992a459d42caca6/cassandra_driver-3.30.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a0679ebcfdcecb3763c690b5bc6a517e0c0803f7bc88e0a6c793e5e421b558a", size = 7587311, upload-time = "2026-04-16T05:02:16.909Z" }, + { url = "https://files.pythonhosted.org/packages/b3/83/d5868bc1ea2ffd392dea683589c4dd64363f619ba2d36de802f9ed38775f/cassandra_driver-3.30.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e12dfcd3f0074c16f4bfe650242edb406b935864373ae86160e09e3f5e437e84", size = 7837314, upload-time = "2026-04-16T05:02:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e9/ac4f81d96cc9df0ba457ca5f447b405f730de8f343422662f4719ae23619/cassandra_driver-3.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1d64cbdce764c33e284d339b9a749736d68971edf8b537888f2d13c4b0d1313f", size = 6955273, upload-time = "2026-04-16T05:02:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8b/f75fe284ee184b54ba55b2cd69d2ba156e6a8fc88b322e28e2ecbaa6065a/cassandra_driver-3.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5c6cbb396ad6fe456efc799d3b8b6bda360ffc06552c5be2ce1a88ac381a305c", size = 7415263, upload-time = "2026-04-16T05:02:38.441Z" }, + { url = "https://files.pythonhosted.org/packages/92/f6/67a0e6f937e85131c5e528d3c74e63b1fb041f0d6351eb7b7d5a403ae149/cassandra_driver-3.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d5e3575ec01d8c043b56ff25de6f61ff4c9ed5cb3ea4c3d9df98def71ba710c", size = 7252160, upload-time = "2026-04-16T05:02:45.14Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/48691b5bed702bbe4db3acb00c1a3cc585e18bd70dadb12a5cf0697c6159/cassandra_driver-3.30.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff2e9fbdc1be54c1d041ea3f7d09812442f334be14bb5ad7aede175544765d25", size = 7586952, upload-time = "2026-04-16T05:02:52.99Z" }, + { url = "https://files.pythonhosted.org/packages/61/56/74b01c48acf401de3f38054bdfcbb493770e13dd2ddbb4735783045f1e3b/cassandra_driver-3.30.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:385134eba72f048707cd800de0a61cf3c23246113edffe9bc6bc2eb86282d26b", size = 7832995, upload-time = "2026-04-16T05:03:01.344Z" }, + { url = "https://files.pythonhosted.org/packages/7b/da/25f3542b1b7221c12e323eae591015e2e816fd543e6538cfc29d2c6ff059/cassandra_driver-3.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:0c28a8e84917acebecbaed39844047c2f135739c3627dd7b9f8541af33e11df3", size = 6965120, upload-time = "2026-04-16T05:03:08.538Z" }, + { url = "https://files.pythonhosted.org/packages/89/46/8fc42a2c836ec525f9313e6bdffbada2911d8501ff008e5d22ebdbaa4895/cassandra_driver-3.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:212af4d8ff934c30538f4bdf7da61f14dc9a30349f6cac2161c8125e56fad928", size = 7751805, upload-time = "2026-04-16T05:03:15.913Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e3/8c3c02380334697c629de2db560d67e44461401175887e7a36374e30bcad/cassandra_driver-3.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61d7eeb17d8f76d5b4a9b1239145250f2a9f7bf949c30e2cc36196b5a0523ce1", size = 7576768, upload-time = "2026-04-16T05:03:23.624Z" }, + { url = "https://files.pythonhosted.org/packages/8f/5d/ead64522236077f4a63974ed4f193f26e35f85132751154f9aa839680dc5/cassandra_driver-3.30.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83a9148d408a3dbb48ea1802d643d60fa53cd69dc7b9a244511ecf5b917e4f53", size = 7330139, upload-time = "2026-04-16T05:03:30.567Z" }, + { url = "https://files.pythonhosted.org/packages/13/fe/bbed363953eee1774842806527e377a79f0cbfb52887b6379c856ff8fedf/cassandra_driver-3.30.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f4225082a11d9529416c223553ab38a29c4e65da6646b40159c554480dc002c", size = 7581659, upload-time = "2026-04-16T05:03:37.811Z" }, + { url = "https://files.pythonhosted.org/packages/a4/60/c776d0d9d6b92bd2f15720db51313af8a194272f725d3fda8827258b728b/cassandra_driver-3.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:6d449f49ce866ac20a1c3d80b1f9245ecdfd1e67b843dccd3d6eccdfe519c02e", size = 6795314, upload-time = "2026-04-16T05:03:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/10/e8/32c6063738ce418403750575a1a1f73609c313766849cb930699d607fc8d/cassandra_driver-3.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d2f9e00127f70dff42d4ef932df8a6b81170c2861d4e75c8b13f4b4816b4450c", size = 7738805, upload-time = "2026-04-16T05:03:51.875Z" }, + { url = "https://files.pythonhosted.org/packages/48/2c/b88c3747c63eeb36b1ebb46a50734b2490eb05f87944ec907563f861bb76/cassandra_driver-3.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c4acd28791854c23ca68be50a7a750c9413ba80fec0ca5c27c2be05f6f3fe0a", size = 7568531, upload-time = "2026-04-16T05:03:59.122Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c0/f44f3fdb7c40cddad7b58cc8102387ae8ee4da793ed5899d4a231989fbea/cassandra_driver-3.30.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:289e86c81be2543cb9055600c0819850db921e6e138a84e5c88ec160662c7207", size = 7324591, upload-time = "2026-04-16T05:04:06.501Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/54466eff67b69653c83d5bcf3972e42a52b2e881f9a3ee7ca8b996943caf/cassandra_driver-3.30.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d73c0429813045ba86b92fc033fbcfd495aa10e9d4a40fe30b6e9dfe8b5d3ab4", size = 7575795, upload-time = "2026-04-16T05:04:13.645Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0d/ff571cb27e56d8112bc3f4a5e807da54c8b875264e038e791e06ddcc1817/cassandra_driver-3.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:17fb53587c9fc6a27b5c4a89b4f3d9169be43fc572d6f3f67494aa74708be936", size = 6798091, upload-time = "2026-04-16T05:04:20.145Z" }, + { url = "https://files.pythonhosted.org/packages/73/c0/a8b52c18959d96d313450cdfa97f66d4b67327814671df65a7a2e7fca64f/cassandra_driver-3.30.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c64e20bf46b49f8ef64569208d4a395b0928c27d5960559922a2d13471924d0d", size = 7736773, upload-time = "2026-04-16T05:04:27.315Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/a7aae33ef0a74616210b2cff18a5efba3db99f1c5bb7e2a5a31aeabb8558/cassandra_driver-3.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:923a6e1c3fa5f98f846a028b1a7207ec9e7d8cfa54ea47a507d41122efa2f54f", size = 7562347, upload-time = "2026-04-16T05:04:34.5Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/bed3930f4a726b3783fea179a88cd16b95d968ec8fc1af9d5193d26cfc39/cassandra_driver-3.30.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1b4aa6c7706dec839134adb6a2094d90c5f6f35efa08028ed6aae6e67c8643e", size = 7382080, upload-time = "2026-04-16T05:04:41.48Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/8cd9c03474bac374b0cda4124c7c4d2f6b2b5f7942b028f82222daf54fcb/cassandra_driver-3.30.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:137498e2a9b6f578d1902e1af8a988e50b8fe134c76a176f1b8a774e906bc66c", size = 7590242, upload-time = "2026-04-16T05:04:48.477Z" }, + { url = "https://files.pythonhosted.org/packages/c4/29/aed0ee68f724b9fecc9cebfcae44f331b5dd2e010415f4be2d6e64d77226/cassandra_driver-3.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:136b46437b9902673264e101cdaab309d3e40607bff34430bda86b785badc6e4", size = 6787116, upload-time = "2026-04-16T05:04:55.349Z" }, +] + +[[package]] +name = "certifi" +version = "2026.5.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "chromadb-client" +version = "1.5.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "jsonschema" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-sdk" }, + { name = "orjson" }, + { name = "overrides" }, + { name = "pybase64" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/3b/47c02df4d56883f1dee3548d03a52e75ab1d1c282030ffaf32031bb46569/chromadb_client-1.5.9.tar.gz", hash = "sha256:5e481d51ce6634fe54230b0411bf1ff257607ace222fc64c669e27874c12175b", size = 22112301, upload-time = "2026-05-05T05:36:19.069Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/0c/4ae7985586638bcaf5459f6cad5e81704317df1946419c8a16fb8f9c1fbc/chromadb_client-1.5.9-py3-none-any.whl", hash = "sha256:3200b9f79389974f9a35bbcdd0657289f5352da3a7d0d37c63194283e02d0605", size = 798976, upload-time = "2026-05-05T05:36:15.966Z" }, +] + +[[package]] +name = "click" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, +] + +[[package]] +name = "clickhouse-driver" +version = "0.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz" }, + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/9e/d8e40b29b6269a84552441a553fc64dff28f2d7e2d92e81c6be84fe12b4c/clickhouse_driver-0.2.10.tar.gz", hash = "sha256:925fc6ecda1e5314e3f03bcb493955c068b070cdba221fb8ce27329ee8a7f71b", size = 409448, upload-time = "2025-11-10T22:49:58.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/82/a7ddf5d34abb71fd82c3dec4da75e5b4d1c91a3ba7a407686e9bb43f2bcc/clickhouse_driver-0.2.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1dc2b570b3c02baf1ba759f1db3cc5d49b4f968f32dd56710ca4f892700079c1", size = 212722, upload-time = "2025-11-10T22:47:07.407Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8f/67d3a1b81f8eb4ace6b18d0cb930a4a1cf1537c281fc9ee083adf16d0c77/clickhouse_driver-0.2.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5be143e33e330e856e65d40cdfdd38c0f215ba5140807aee5e923fd0c8cf04c8", size = 208490, upload-time = "2025-11-10T22:47:09.514Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/c5aed06221c229108f4e6774eda4425e1db9fc6b4cc3a16dbb328f7017f8/clickhouse_driver-0.2.10-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27397d41661b8aa29b304c70e06ba820c8158e8c6708beac4edb360aa56e7755", size = 952844, upload-time = "2025-11-10T22:47:12.502Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/2e15d7ff029637a31d9549f17cd2ec87153f09b7e29d7e95b8fd47ed9379/clickhouse_driver-0.2.10-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca517354471a43e35852da6943c9a005f420e20e75c6444f8d640277d2eb026a", size = 1007221, upload-time = "2025-11-10T22:47:15.306Z" }, + { url = "https://files.pythonhosted.org/packages/02/eb/611132def5e1fa1c466655fa60342f58fdba8c80e70ad3dd171ab0802cc4/clickhouse_driver-0.2.10-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea3a0c852f227d0217bf72f536b8056804c9fc932ea8a48845d68f68e473b242", size = 1017177, upload-time = "2025-11-10T22:47:18.003Z" }, + { url = "https://files.pythonhosted.org/packages/86/df/cf6e9059f289c4a779ace15e7988fb63ca09560c5128dcc8881e3fd151f1/clickhouse_driver-0.2.10-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea94c2f6d42263395d0d86ce21788c167009451044046b825ecffb6746803b67", size = 943615, upload-time = "2025-11-10T22:47:20.242Z" }, + { url = "https://files.pythonhosted.org/packages/36/34/477ce6b3e23f0610f07fef59de11316527b7cca2875a21628b1ec3b18d7d/clickhouse_driver-0.2.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:46aebe903aff48452ed64de81c6a218c37f9617c9a1ea1eb84ed4c410899c620", size = 922447, upload-time = "2025-11-10T22:47:22.254Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d4/1553fead669cb4ef2c0cc3be19d884b3ac0128e5248181c8a7a383e60d7e/clickhouse_driver-0.2.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:908b69d2f2c51cf0603a5464be51df5f0e3e670b75f1d17a3b51a4bb8481620a", size = 971509, upload-time = "2025-11-10T22:47:24.722Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c4/c44f339e9e11ff79d77547d18a4d61f1da79ba6802262b418ceee0d0233b/clickhouse_driver-0.2.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4a61dac61cbed12789ad0bced043f6215e0253e0a41a2e8ee419d0be84f2acfb", size = 960931, upload-time = "2025-11-10T22:47:27.182Z" }, + { url = "https://files.pythonhosted.org/packages/1a/6c/04b7de8132e38e4a459f32b5266b24ec3325164f8dc901eabe569b1ee0e0/clickhouse_driver-0.2.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b97c257b90cc23a0ce64ecabc79ec627bbc51b5fb129607e79974139f62eb86e", size = 921846, upload-time = "2025-11-10T22:47:29.684Z" }, + { url = "https://files.pythonhosted.org/packages/a8/45/123351a3c95a2a3200f5dcae5d156458c2d9d28e3d258b8b24fce42932ff/clickhouse_driver-0.2.10-cp310-cp310-win32.whl", hash = "sha256:deb2463626345e36bfa59747f2fcc016f963a4d46e949d1e0a570a9f4a0dabfc", size = 190525, upload-time = "2025-11-10T22:47:31.174Z" }, + { url = "https://files.pythonhosted.org/packages/ab/7d/29249f47803ac3b051dbb041cab26863c5057801d2dbee2da20687022927/clickhouse_driver-0.2.10-cp310-cp310-win_amd64.whl", hash = "sha256:b1641dcef0af756e34e77d96761abbeacbe490e750ed048f35ca6e939b230067", size = 203074, upload-time = "2025-11-10T22:47:32.365Z" }, + { url = "https://files.pythonhosted.org/packages/13/9d/bb9ee6df5a8fb60b56f9ca76cb9b22beb6d47d45ce561dca4ef9a63c1d4a/clickhouse_driver-0.2.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946daf834f021d69acbebee7abde87bd4ad5aa46fa7b7643805abc7ff487a91f", size = 212466, upload-time = "2025-11-10T22:47:33.591Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e0/1ae285f4d5bb61bb62016deb38dc175a2b8cbe578dffdad5e1a5a02a176c/clickhouse_driver-0.2.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3572e74cd65828f72284bf607de259c059178b44b19a93cb67766b7e7458cf8e", size = 208477, upload-time = "2025-11-10T22:47:34.925Z" }, + { url = "https://files.pythonhosted.org/packages/28/04/e2fb47a4aaf9653c9ed872e1505e997d42f834b0891351ef54169e100c5c/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:767af2b2d2e02fb7abd8fc9619f8aa2de65be010d3024029d68bc9b1be564466", size = 1006131, upload-time = "2025-11-10T22:47:36.386Z" }, + { url = "https://files.pythonhosted.org/packages/e3/52/626cf3a908dde51638213a6c414e86bc66c47e384cb379c4a0533930a329/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b00005a89f0e40ec0bc313f7e131d958aa17e6af5c3260dbb5c7daf5370c5443", size = 1059838, upload-time = "2025-11-10T22:47:38.266Z" }, + { url = "https://files.pythonhosted.org/packages/12/57/a5917930760e4032e98017916bd6770308e146d44c58e450da6fa87f2d4b/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a62ad120dab6bcc68b6413b7ef0dbaef75ab5ca985d490a9e1ec13d93bf33dc3", size = 1069504, upload-time = "2025-11-10T22:47:40.681Z" }, + { url = "https://files.pythonhosted.org/packages/f5/08/4419ce43b27b6349fd14af0d8f5d8594d270b9bb24cbaca575bacfec630e/clickhouse_driver-0.2.10-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9f65e71c99f0c8a64afa348977793967f897c4f731984ed54fed4eca8d375a0", size = 998284, upload-time = "2025-11-10T22:47:42.713Z" }, + { url = "https://files.pythonhosted.org/packages/36/10/edbe55be3554e2cea7c68ed1761aaa2bea0153474d81a467a0ac862b3478/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c120b182ea7e9713b119ba2b518dd75503c18f26cb001a6e436326765fddd123", size = 971989, upload-time = "2025-11-10T22:47:44.642Z" }, + { url = "https://files.pythonhosted.org/packages/2f/18/d0b883af04067c70e99c22dbab1f085062ae764a063f22d4e144e833048d/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3da6f6d05f14780f3183f8b7b23ed9826d1e0f2f73c2471037d5335b474782e", size = 1022107, upload-time = "2025-11-10T22:47:46.148Z" }, + { url = "https://files.pythonhosted.org/packages/fe/40/11446c52c5330123354f2f88151c620e1b38ca6d5131b2cc71786ec3c067/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9617c6ef154e58c6693be6e1169000957238f83d21fe20d57bb492412cc6128d", size = 1015750, upload-time = "2025-11-10T22:47:47.702Z" }, + { url = "https://files.pythonhosted.org/packages/36/9b/32abe3c76fe8494ad1642febbe4c59dfd46477e27c401d2ac8cc8a0a6117/clickhouse_driver-0.2.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8c2dbee2083295c6d9789a10cb0c967c4e123e6315e3192e4ad935cd55967c3", size = 975901, upload-time = "2025-11-10T22:47:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/5f/50/0cbbe783bf14381add11a241b4ba35349f5512d1765aabde63d7175c1f0b/clickhouse_driver-0.2.10-cp311-cp311-win32.whl", hash = "sha256:293f47bd36a5b69ede39638ea8f7c1eb3fa7e55f35778a3feb9a3615749100c4", size = 190097, upload-time = "2025-11-10T22:47:50.463Z" }, + { url = "https://files.pythonhosted.org/packages/1c/12/7bb65617527b2b9a2b4ca8cc0d1ea2d4bdbd6d05a4d9d141cef0083342db/clickhouse_driver-0.2.10-cp311-cp311-win_amd64.whl", hash = "sha256:c9aeebbb0c159173de37deef494b28a4423a767cd7c4b2d596556f7a072f90a6", size = 203591, upload-time = "2025-11-10T22:47:51.562Z" }, + { url = "https://files.pythonhosted.org/packages/a9/54/0a82bcfc66bbab7a36c37b4bd8b8d088d9001da93661e278b7f6c87a3791/clickhouse_driver-0.2.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1851f5f8b84ead0be6373ef001e8f09d48d45ec0628377e199a223e70417cd40", size = 213107, upload-time = "2025-11-10T22:47:53.314Z" }, + { url = "https://files.pythonhosted.org/packages/32/7b/8e526f6ffb9983c0c6d082e358df4b20fe1a9e95f453e704bc7a25ef4aab/clickhouse_driver-0.2.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:188775d38ff7cb36e7045441aabf3a6a8751127d8b37b6eb1b1518494eaac5bd", size = 207193, upload-time = "2025-11-10T22:47:55.146Z" }, + { url = "https://files.pythonhosted.org/packages/65/96/40f274896abf287c378575f025c602fa4e834278930dd63574ff548815c4/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ff5cba860df61845d6ae12f31d4a70ff4ae3be4e6a8a876e68af8aa4b0e45bc", size = 1046187, upload-time = "2025-11-10T22:47:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/0c/80/7b6e110c3b803fa8b3f8cdba0e08553a62c5f64e5ad57e56de3ea95cd9e1/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fad865009d96de44d548f1691ed92adee971f72c001cf4466b3ba2ac7d9db47b", size = 1088806, upload-time = "2025-11-10T22:47:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/7f77bd00fc01df9db2e573de21bbc1f66083549d004864b892877dee8a76/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cf0fe791e7c2adc0ab41d4770953c00f8a88bdd7e3ee83bb849a661a6c93d4ef", size = 1109839, upload-time = "2025-11-10T22:48:01.405Z" }, + { url = "https://files.pythonhosted.org/packages/55/f7/57a80ff9cc44a333021e2caf8d35fc23da6ec7b602bbc3bf8dfac0253a6e/clickhouse_driver-0.2.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5744daafdd0ff7520c6ae95a78211a0ff5c2cfb3513a20f5602d2bc7eed580d", size = 1049773, upload-time = "2025-11-10T22:48:03.089Z" }, + { url = "https://files.pythonhosted.org/packages/f6/3e/fcf8e9cb9edc717ce6c467a9ec7c96b4495d5f8ec4859175952149fbdaa8/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f02f6c9f71ae5c06e3b760d3d9f4f758b32acf6f71504b6d90bacca9abbfec18", size = 1006817, upload-time = "2025-11-10T22:48:05.038Z" }, + { url = "https://files.pythonhosted.org/packages/95/ab/1bc25a385012c03595b91311d8341205a5790375207d80425e2285055d42/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6df571410f149e16e0a0e5529f1c2a9e41bb62b9357a3c8b0bd0647d6bb0fd1e", size = 1051047, upload-time = "2025-11-10T22:48:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/9dd7331d08495beacf4291a6fbe5514fd0f6f8d53014121a8d70d8bd6c1e/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1e891162226a44fa169bdc996efd49b22bcf59372c35118ec5785e936fe97178", size = 1052014, upload-time = "2025-11-10T22:48:08.608Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e9/af10e0ddbbd90c4ead933effff1b8914bc687bd52a70d244404db4c91529/clickhouse_driver-0.2.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3a261947ba0cf0034d044c30563ad151d1cf8156a5ff419b017c423b4235e0ac", size = 1020937, upload-time = "2025-11-10T22:48:10.993Z" }, + { url = "https://files.pythonhosted.org/packages/f8/78/dbb93037939a7f9424451d10feeb0daa0d5a76163ef2e62407f4d18705ea/clickhouse_driver-0.2.10-cp312-cp312-win32.whl", hash = "sha256:d94f4db3c338ed41b28379931756852586920c5cbac1f07fcc8b7f109b3e69db", size = 190729, upload-time = "2025-11-10T22:48:12.742Z" }, + { url = "https://files.pythonhosted.org/packages/b6/86/74bddbfaa3c116da34d8c762f728c59d9842a65f202b0b746f5d9a597869/clickhouse_driver-0.2.10-cp312-cp312-win_amd64.whl", hash = "sha256:3ce051f55bdb33396f76a77702936bfa973dfd98f7cf00a72817a1e01c9b7406", size = 204141, upload-time = "2025-11-10T22:48:14.452Z" }, + { url = "https://files.pythonhosted.org/packages/76/7f/ee5d539611b782a04845545ba391208634124f67c4d57a32d4a46eadfb58/clickhouse_driver-0.2.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e76c1189a6273c79d906ee8274b9d3c7bf8df597ee6199cb75449ee2702deb8a", size = 210645, upload-time = "2025-11-10T22:48:15.995Z" }, + { url = "https://files.pythonhosted.org/packages/34/92/ee5a2d7a812b65d9690e46222218f33064c4bd44f3535b1ba564fb4b528b/clickhouse_driver-0.2.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8be64c77d58d4a33b3c957cdb7c5a4deeac56bf93f4188dbfb5c5454eb04c985", size = 205158, upload-time = "2025-11-10T22:48:17.745Z" }, + { url = "https://files.pythonhosted.org/packages/03/00/6c532a0aea89e3d09dd4150b1df0b92e787a306b8711d54d003d18fd1ddd/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23abafd0c883ccc1baea527c1d05a6bc0c59aae6c29ae65e1b84d498b265f8c0", size = 1033476, upload-time = "2025-11-10T22:48:19.239Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9b/137ea1ff9539da77cd022331ec4fa079cbefbd4ebbcb5c51bdd7dcd0bca0/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:77ffe2063469c637c5e57bf0713ca1b617b612d55a8392799f97e34c353e6908", size = 1079495, upload-time = "2025-11-10T22:48:20.744Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1c/e13766af7e4e174c6f17b1fbc5a078b28584f53adc91f103caacc73f569b/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df2d77779fcc1ddb68614b75bf45b8db61cf63f42a03d5624ce6922a305e609f", size = 1100658, upload-time = "2025-11-10T22:48:22.277Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/0686ad3ef1b594c16e8b13394c73ee4860fd025d70211a360f797dd7a28a/clickhouse_driver-0.2.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e46e31e4b14626571819e669341a3017376ce935d25b2cc0bfea9343b1b562", size = 1034175, upload-time = "2025-11-10T22:48:24.117Z" }, + { url = "https://files.pythonhosted.org/packages/d8/32/fea4e971297b50e5af3318fd90d400269ae1c74ad4d83a9453b89f578d3c/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7435d1ff2bc577aeedf8f01d94b5777af382484f8973a9c5018d5afd0dd175c", size = 995963, upload-time = "2025-11-10T22:48:25.824Z" }, + { url = "https://files.pythonhosted.org/packages/02/c4/d42f2b69ab5903e5bc9119b179f55c9aef79fe667f77cab4d8ae90492dcd/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b60c7e3321214eec4568811bcd953836671fa078c57f6607f236414447636de2", size = 1044626, upload-time = "2025-11-10T22:48:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/78/36/043b6b2d967396172a60f10bf26de2c83248857f9a1e75b481f02218d1d7/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13fdf6571e20ac79992605ad65058296ac0f2437c1e7428a98dd6d173753119e", size = 1045772, upload-time = "2025-11-10T22:48:29.439Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cf/bc5c807cbe68ce9eeac6a1997b937c81774ca86b2ab593c6efb9121a9f08/clickhouse_driver-0.2.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06b6b683af086f9049d0c5e7e660fb76013439efa640e6c8ff6673622c3838fa", size = 1006716, upload-time = "2025-11-10T22:48:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/d9/21/9b0e4814aa6c78d717c22cf51e3a5bd73b4f09c4078fc3f274f8a17c3658/clickhouse_driver-0.2.10-cp313-cp313-win32.whl", hash = "sha256:8fc51b0991dc9b89ea2379a0bc0995a9ed24428aad834f6af7841cc94488b82f", size = 189789, upload-time = "2025-11-10T22:48:33.039Z" }, + { url = "https://files.pythonhosted.org/packages/85/7d/46195e09d4b43f59bc9053f81f6d0c0f25e44c8ef94218fe3c972067cdf4/clickhouse_driver-0.2.10-cp313-cp313-win_amd64.whl", hash = "sha256:fbfe244d2f06824e175492a54033d8282f0ec5644db8e7e3805f7a9d076f5fcf", size = 203020, upload-time = "2025-11-10T22:48:34.874Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9f/513e661a80b0db9b6be5ce525d6cc86339c04dfc8d75f577b775e5ac5cf1/clickhouse_driver-0.2.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:19dd1de015d98dd1559d14ef05de2b6a0560c98b77ef160b3b99632a87fc8212", size = 211779, upload-time = "2025-11-10T22:48:36.873Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6e/842610028ca39c826865402d46b7d7f499a9a249de219edfce889c0e025d/clickhouse_driver-0.2.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b6f35a9ee7f7a8ce3483764c3bc2d23c8be1c5ce3aef537b1a3cebe59fdb0c4d", size = 205709, upload-time = "2025-11-10T22:48:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/c6/94/ddbcda962f7ca36cbcf7ded105f68f99b261dc29b9c28ca2eaec70382f66/clickhouse_driver-0.2.10-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:888ab59b2cccda24680cfbaeb723fd8922c6148b9a43ad4cf067fef55959f19f", size = 1032732, upload-time = "2025-11-10T22:48:40.047Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1d/9d378af127ca1d35b734c965a9ec26318253f07d2a7eaa4a5643210fe327/clickhouse_driver-0.2.10-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fbf5d692ee4df241e82d200a195dc2ddbe7fbf490ebd185fbd9da32478245399", size = 1085199, upload-time = "2025-11-10T22:48:41.784Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/d65aedbe40e08a085fe3c2fcdcfcc0cec084d2e838e653c1ed4408771d9a/clickhouse_driver-0.2.10-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f1d42ffcdc5b77346403e7ac49639a48c2fb58767a3e55657bd40e36290e9f0", size = 1089153, upload-time = "2025-11-10T22:48:43.283Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/e60fbcd5ec1458da9fe313984527e9ce5f95636c178bd2d08d6257c42c30/clickhouse_driver-0.2.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74b36dfb79311bcf1ba6edde926908a46778a5e9db6302f126a799550f1fb807", size = 1023508, upload-time = "2025-11-10T22:48:44.83Z" }, + { url = "https://files.pythonhosted.org/packages/9b/80/345be27d8011bcbd2fd3437d15b0e387369e2a8a8b73a7704af25bb6e90b/clickhouse_driver-0.2.10-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8c49d4b6619ce9237817185a41db6645f1557f7a77f2a822029ded1041179d55", size = 995994, upload-time = "2025-11-10T22:48:46.35Z" }, + { url = "https://files.pythonhosted.org/packages/0c/76/de011c4460a2b80e517c9a5f1b80a9fe3937e2987989e9776a79dc0a1e99/clickhouse_driver-0.2.10-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:503bbcbfedd65840489dadf3d0a2ac391888828b8f82579e6ec08efb1f362659", size = 1043865, upload-time = "2025-11-10T22:48:47.924Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f4/264b6bb95488eebd79565372c8fbe397df2aac1478224629ebdc229eab00/clickhouse_driver-0.2.10-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:64aa403194bf4d3bdbecc2f8412ca85f4b9a35ed8dc228f23061a9889ce18dc4", size = 1038468, upload-time = "2025-11-10T22:48:50.706Z" }, + { url = "https://files.pythonhosted.org/packages/ec/95/01b3cc32ba060132ce9183f7af9059995fed2ded8d41865e86193f17ce30/clickhouse_driver-0.2.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20b16b066760cfcc6a8a5342e91585a84ba22152f83421030edd3582b6e420e3", size = 998721, upload-time = "2025-11-10T22:48:52.924Z" }, + { url = "https://files.pythonhosted.org/packages/85/d4/21e3203f8d22ad32224b0623ad2772103bb3a05ce367d6b27e78e118ea2f/clickhouse_driver-0.2.10-cp314-cp314-win32.whl", hash = "sha256:5ac6eed651dd6320a50338ff331fea93d20ed13dec27c7c7bfab71f8db3c76bb", size = 192639, upload-time = "2025-11-10T22:48:54.729Z" }, + { url = "https://files.pythonhosted.org/packages/ac/6b/1e87eb25614c0477fdadd5cf0b49675bc91cfcae95a10cbb83e3a688b01b/clickhouse_driver-0.2.10-cp314-cp314-win_amd64.whl", hash = "sha256:9f17746188833162e06e6af595e6a41c2ed09c7714b96860217c696fdb7689cf", size = 206586, upload-time = "2025-11-10T22:48:56.002Z" }, + { url = "https://files.pythonhosted.org/packages/1a/49/ad110cb8e92f9bef684048d621aba291313e312640b5b51ebcae40f20f17/clickhouse_driver-0.2.10-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:190e284f97fb06c30d88b12402666c0ec2a81ea35ba2019cdbc7e284db8ae505", size = 191126, upload-time = "2025-11-10T22:49:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/48/24/c49669b05d514261ab3f6d6b48d3aaa8c38e23e2cf4499b3c6cefc741424/clickhouse_driver-0.2.10-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d5b32d0824ecd348ba1f11d9df7d7e09134e11a5ca001e283cbe953bc9baf48", size = 216482, upload-time = "2025-11-10T22:49:41.378Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/c10cf67f8a65fb8919a8276241678d5580b0922cd5f612c4f9ed0dc2099f/clickhouse_driver-0.2.10-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14d6fed705c798d10c2426634a4856fb36702b7dbb786e363edad710972e2b47", size = 218823, upload-time = "2025-11-10T22:49:42.863Z" }, + { url = "https://files.pythonhosted.org/packages/11/77/4ec0e8d1d7a64a196d559448c541af0f75e23cd72d5ac5b3064d4b6aa02f/clickhouse_driver-0.2.10-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b9c74b9895ad8a313154a274374d113efef08ddfb911af5f23bc6a881c4a0c4", size = 192798, upload-time = "2025-11-10T22:49:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/d7/64/6d8ea99c03e99aaf8dfe87961fd2e5baedc72d0a289cab1c5138acbe2d1b/clickhouse_driver-0.2.10-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cb0bbad7b79ecb41f5f348528b6b0c46850d741669dd453f5fa633a8f3eff855", size = 190729, upload-time = "2025-11-10T22:49:45.635Z" }, + { url = "https://files.pythonhosted.org/packages/40/7d/9abdd95b0da0dcf6dc644336459f132575bfbdee1a4ba377195c2032c03a/clickhouse_driver-0.2.10-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68e96e04282d126486b3820391a8ebf1d7c32b61e5fcbd701aeeda79017349e5", size = 216933, upload-time = "2025-11-10T22:49:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/33d4b6f1650847280265756e4d54f94730cdac082ab3f9e6518ba97502bf/clickhouse_driver-0.2.10-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fd9f72fcfe86e0fef0681cd124325714e92e96ad1fd675dfbeaccdfb7bd2f64", size = 219670, upload-time = "2025-11-10T22:49:49.401Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c0/320b7cb5e2b6ab73e10ca6f1c5d0a2f04b42dc3acb689533d102d9f3f5e4/clickhouse_driver-0.2.10-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e24582110f3d532b35e106583981bea0b7a3b10167c8a890a7b6ea5c7c6ce6d6", size = 192945, upload-time = "2025-11-10T22:49:51.168Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/fd/0ab2772530e946e1be1abd0bc09e647ec9b02e88f0867857601fefca8953/coverage-7.14.1.tar.gz", hash = "sha256:30c08f7d90415aa98b3c990385dea2939b0da55f38515e5b369b83655f8523be", size = 920132, upload-time = "2026-05-26T20:41:36.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/69/0d2ef01ff4b8fcecd4cba920d11e92fa4f96ae412441d3b56a90a258e69b/coverage-7.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3e3680291c4a1d0dadfa84a2c459576a4af5133abb617905714339a0c73138cf", size = 219722, upload-time = "2026-05-26T20:38:14.002Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ae/9afdeaa31b9d9ce98124b6abf8bb49119bf71aecae04f8567c189d91299f/coverage-7.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a5274669f37f2343635a347b91a60777621341ab3378e9c6ac9335eee704bddf", size = 220240, upload-time = "2026-05-26T20:38:17.424Z" }, + { url = "https://files.pythonhosted.org/packages/51/69/c998589871df7ea7dba865cc5ee32b5a3e1d47ba6c68ef91104c7c46fa5e/coverage-7.14.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe5a5fec635799ef33428f1e5e61bafa45a92a96190ba731561ba558ccc214d", size = 246981, upload-time = "2026-05-26T20:38:19.266Z" }, + { url = "https://files.pythonhosted.org/packages/fc/10/1c7d04c13040dac531d21b712bbe08f902e6dd9b58f5d77875c4d030f8f2/coverage-7.14.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:62a9f70b52e0b5a95cfef4a5c5641b06983cadc5e538a3feeb5c00211f523ac2", size = 248812, upload-time = "2026-05-26T20:38:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/c1/65/2a38a4607ef27cadcfbcee034dba5830ae2569f90144a0f4c7dbf47d30b0/coverage-7.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c18ebc343e15be53049b3a2dce38fe82d58f37e20ab9094b3a39c0aa4f6bb47", size = 250675, upload-time = "2026-05-26T20:38:22.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a2/a446ed9752a4a59b79e0fb6cbb319f6facb2183045c0725462625e66f87e/coverage-7.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b84ffdf877644e7096aa936991efeed873f7f3df57b9cd001312b7668ab08550", size = 252590, upload-time = "2026-05-26T20:38:23.63Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fd/e81fbd7ba752365546e9842b1cbdaad3d6919d2a522c590aef16a281ec5e/coverage-7.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e854312c4103f2ad4c0dc023b69b77ebfd2c89db5f86c4c94dc2353f9a92167e", size = 247691, upload-time = "2026-05-26T20:38:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/53/35/f3c26fdaae9ea937d154ca4d372e5ea0a4167ff70d36c6074ac2eacb2f83/coverage-7.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c643734307300234fafa36bf2a040a7235f8f177ea1fd6ec1423aea6fb7b929f", size = 248716, upload-time = "2026-05-26T20:38:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/2e/14/940b6c49551fd343e8507ee2b0ba7af5d0aa04ed5bf768285cb7c72a9884/coverage-7.14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84ac9499e48700399a5dd0ea7085b5091961fec52c68d66b4ec0d3cf7f4441b1", size = 246721, upload-time = "2026-05-26T20:38:28.282Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2c/40fc0634186c28292a662dff578866b3913983d6c375a3c2a74020938719/coverage-7.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7f02d09f70776579b926d889a4c9c235070a1f47c40458aeaca563fae5acfdb5", size = 250533, upload-time = "2026-05-26T20:38:29.753Z" }, + { url = "https://files.pythonhosted.org/packages/de/e3/2c26bf1e811f9df991ff2a9bdddebdd13ee0665d564df7d05979f9146297/coverage-7.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ce66d8e46da2bb5ee313a745cbd2e391d319176c1f7a9451bfcd3a2fb920859b", size = 246990, upload-time = "2026-05-26T20:38:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b0/060260ef56bd92363ebdce0c7095ce422b06e69aae71828efeca473ab1ca/coverage-7.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c912c259304cfb5ee584481cfb7ce1ff932b4d61e6c9140b8f19cb7b5ed82332", size = 247593, upload-time = "2026-05-26T20:38:33.065Z" }, + { url = "https://files.pythonhosted.org/packages/63/f3/501502046efeb0d6d94b5ca54941d95f1184183dd6bdb7f283985783bb4a/coverage-7.14.1-cp310-cp310-win32.whl", hash = "sha256:1238cb94638e610e972c60dac68e813f868dc7d6e982535270558443058d9d59", size = 222330, upload-time = "2026-05-26T20:38:35.36Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5d/1bf99f2c558f128faf7906817ccbdb576ba815d3b41ce2ac1719b70a3663/coverage-7.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:fc459e5d73be2d6332fcfe8dbf3d8994671fe33c700f4565988ecfa511547253", size = 223261, upload-time = "2026-05-26T20:38:37.196Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/477ad149490e6cb849f28abea1dabb9c823cea72e7500c81b4240ce619c0/coverage-7.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:478b5bcd63c2e1357c5c7e16c070690df7b07f676b1c114d7b93e533c664309f", size = 219848, upload-time = "2026-05-26T20:38:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/91/82/a5eb47257c50601bb7b9a9d2857c67b7a3a85ad74180eb2c98bb1fbe0ce5/coverage-7.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a24a81f9715ee42ef59a316cc11611c98fe23920f7c81861315c9f3ff4a230f4", size = 220354, upload-time = "2026-05-26T20:38:40.232Z" }, + { url = "https://files.pythonhosted.org/packages/43/8b/78419b5391a5cb706b6544390507e469d83ffc9a8248b02c4011aceb9365/coverage-7.14.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:196a13319ad88d6d8ef5ab489ec4f44ddde2143c0c7d5b27786f6c3ffd56a7e1", size = 250771, upload-time = "2026-05-26T20:38:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/77/63/e77aaacd491182210d639636b7a8bba23ffffa9b82aa3762da9431855fa9/coverage-7.14.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d452fd08b5c72c5167c93e6867b5c08500bd40f2a21e1e854a500550b6cc36f", size = 252683, upload-time = "2026-05-26T20:38:43.305Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/a022e3cfbec2ac241640003cb3a817e161d9c7f5aa9b49173756cdc03204/coverage-7.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23bf7fa51ac02e07fc7c96849b82946da47ae862dc8f86d183b2a4864fc38129", size = 254791, upload-time = "2026-05-26T20:38:45.361Z" }, + { url = "https://files.pythonhosted.org/packages/61/d6/967e408aca4c1ceb88cb0cc677169110ae7f5995fb5eaf5fb1f5a1bb8f5d/coverage-7.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcaa50684dcaadfa599ac48f81103c756d791cfd85c97203d2217c593d48b860", size = 256748, upload-time = "2026-05-26T20:38:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/b8/be/869188f7fe28638078ec479331ace6dc5f7b40b7153eb616f47ab79404d8/coverage-7.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4ea1c034f95c9b056e856b794630b17f9fa3d57e4800ff1e503d3be0f9c9078c", size = 250907, upload-time = "2026-05-26T20:38:48.493Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/adb7d3b4278d690e68703abcd76ab1b948242e3668d921711551b78f9ddb/coverage-7.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7e057326434e441306226fbeb5d1aaf14a2637efe97ba668306635835f32ad7", size = 252483, upload-time = "2026-05-26T20:38:50.074Z" }, + { url = "https://files.pythonhosted.org/packages/43/61/331c74103c62dcb0c4b9b3a0de9a61aca016208b0a90f109592a9f9ecc28/coverage-7.14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59baf88468dbc8d63b1887afd92bda52e40bb1561696e5819670601403810cec", size = 250545, upload-time = "2026-05-26T20:38:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b6/c5dae3c104d89be04828f61810e6b3473825482e4c288cc4ed04553e08ae/coverage-7.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d75f892b3ab73ba11cab5442cce7b3e168fd64162b16f0e1e0d09c508edef", size = 254310, upload-time = "2026-05-26T20:38:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a1/2b9d5863e3b83c01ad8199e3c597802fbb3a9dc90b058885804c20296d31/coverage-7.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3a56abc20a472baf0304c455721bc601477440d28ecfde8a03dde79ede07e0df", size = 250266, upload-time = "2026-05-26T20:38:55.414Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/0e511fbdb269359be26fe678a1c3fa1f2aa2a01573cc3f54268c8d6d4797/coverage-7.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6a3cb83d1552c0cd1b4906655b6a33fd4a8473229633a901c6b73bf86914dee9", size = 251174, upload-time = "2026-05-26T20:38:57.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/10/e55307b622b3dd9671cb321824502dc10f93e72f2802b9946159a8edadeb/coverage-7.14.1-cp311-cp311-win32.whl", hash = "sha256:10274a1fbeb8ec5d72966e17bb198a3104257aca4ac09d98667c5f8aca8c8548", size = 222354, upload-time = "2026-05-26T20:38:58.727Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/107421693cfb71e4f1ca5bf70443f64d4161878068d07a3e51c7ad21d17b/coverage-7.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:87ebdf787d4888e3f3f2d523eadc6e18c6d18c6d0eb173801a189641627fb37e", size = 223290, upload-time = "2026-05-26T20:39:00.413Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1d/3e3644585eb29e9dafefb19555078529a4d7cce12bd21929664eea989277/coverage-7.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:dd34767fa19848d35659ffc0a75314f58c7af3f1cd87ec521e8292a1238398a3", size = 221953, upload-time = "2026-05-26T20:39:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b7/bdbb725ba02c5b42825b200c940f38b7a54fcad24627b7192f78f8110d76/coverage-7.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a06c76364a9360e33d6d23769aefdf7f66f38e2ffb60ceb1baaa4989d83b695c", size = 220022, upload-time = "2026-05-26T20:39:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/72/81/fdc0898a55c6219223291ec1a1fe89966ef212ce82276aa0899df84b5de0/coverage-7.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fad54e871165f6ec2f536063ac74c3104508a12963e64072ba44bd822de52b0c", size = 220379, upload-time = "2026-05-26T20:39:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/de/72/de048c4a25e13bce59ac6a339351c10bdf2515e07459afcdaf04dc3143a2/coverage-7.14.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:84b535f00655ecafe1d929d1fb00ed5d6fa3051ea643ab2c161a3887b86f294b", size = 251888, upload-time = "2026-05-26T20:39:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/300c343f68beb9d4cbb64ec81e58c5b6b80b56927f72d2b38654ac26e013/coverage-7.14.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b6b0853b895fe0e98cbfc580d1ec3393d9302b4b1e96a77b3f5c91fdab899e6", size = 254624, upload-time = "2026-05-26T20:39:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ed/7b25642496e8170b6bac14adce00537c6e5fa2d586159401a4de3e8b49e6/coverage-7.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:442cc9c952b2df400cda54bb04ab87330cf2cd08a8692cbbea36773531eb6f37", size = 255739, upload-time = "2026-05-26T20:39:10.889Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/abd210b8c4e29c24e4624916db97bb519097a91034aaeb767f937e7da794/coverage-7.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8270544c361ed405a27a060dbc9ed2c124b084d96dfdc2d9a2510482aef981ad", size = 257998, upload-time = "2026-05-26T20:39:12.722Z" }, + { url = "https://files.pythonhosted.org/packages/7f/24/7c50beed3792fe62f6ce0545c6686ce83379719e2c0276179333d97eae92/coverage-7.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:48b283b1dd6372e8de2a7a9a4c4d5dc06f4d4fd209b876f3c88a7a205a0c8f84", size = 252296, upload-time = "2026-05-26T20:39:14.259Z" }, + { url = "https://files.pythonhosted.org/packages/15/05/0f874628ebcbfc77ead559ff210281ef06a97db08481832e7dd39274a135/coverage-7.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5b0c99ba93a07d56f6df340bb79be53202a082b2fdb81bfe6190b741a3470d54", size = 253658, upload-time = "2026-05-26T20:39:15.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/ca6ad067364b337ef997802115e7ecad2abd2248b05471464b0dea02b4d4/coverage-7.14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e471bc5769ff073b058cfadb0d736b56ce067c8560eabeb0da88462df98c23e7", size = 251803, upload-time = "2026-05-26T20:39:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b9b4d377cd9f40baf228068f5a81faf8450c6228503011bd499708483a50/coverage-7.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f497a1ea81d4cd7c10ddcaa685135b9aabd291af3d55775a9ddf3cb7a364cdd9", size = 255873, upload-time = "2026-05-26T20:39:19.414Z" }, + { url = "https://files.pythonhosted.org/packages/3c/21/7c721a9e5e6bb88547d30a787aefb97512d3f54c1324c7488d9b3743f7f9/coverage-7.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2222be86d0b54f5dd5a38f45f17f315f737245e857bf0bdedc70734f84a13c02", size = 251372, upload-time = "2026-05-26T20:39:21.169Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f8ae5a2200130e1503cd7661a6cd3b2b7bacef98277fbf3571fb13f8b766/coverage-7.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:85e85586565842f6932abebd4c18bcb1074223dc0b3576e7d173ca710622813a", size = 253245, upload-time = "2026-05-26T20:39:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/34/62/70a9024672a5f6910517d9628c52c9afbdd3cf8f46426af52bb148a56fff/coverage-7.14.1-cp312-cp312-win32.whl", hash = "sha256:4a28fd227808366b196a75476dced2eb35b351d6766ba9c858dc93319e87f4f1", size = 222567, upload-time = "2026-05-26T20:39:24.868Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/8b7cd386839b039ebe1855733b9f9449a8dec5d79564018234f185a7fa70/coverage-7.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:54acdb6674a4661768d7bf7db32dfb9f46ab1d764f8aba6df75ce1a6a088724e", size = 223372, upload-time = "2026-05-26T20:39:26.603Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ba/b44d472022f620d289d95fa830143235c0c36461c6f2437ea8d51e5481ed/coverage-7.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:99cd41ff91afd94896fea3bc002706b6ae4ce95727d06e4a0f39c0a8d8bd8b1a", size = 221989, upload-time = "2026-05-26T20:39:28.242Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/5f6d56327c62b185225d145191c607e07515294a0aa6338e58805cd4a5ac/coverage-7.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:be9f2c802dcfce3f71298303aa5dad0dce440a76c52f2f60dacd8656dab78793", size = 220044, upload-time = "2026-05-26T20:39:29.902Z" }, + { url = "https://files.pythonhosted.org/packages/75/92/e82aca356744cbbc0f77a0b623e38918c1872361963413a3bab5d0340393/coverage-7.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6223a72fd0e4c7156353ec0f08a5f93623e1d3034d0e2683b9bb8ea674131b1d", size = 220412, upload-time = "2026-05-26T20:39:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/27/c9/385bde0bf7ed0f4bf3a7ee5367060a86b5d218718cfd6fb943c0f836b34f/coverage-7.14.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7279d2110a28cebc738b6459ecda2771735a4c18465fbbd36b3288fe5ed92247", size = 251412, upload-time = "2026-05-26T20:39:33.337Z" }, + { url = "https://files.pythonhosted.org/packages/51/8c/23faf6a2343a0d17f960a4bd56c43bc7eb4cf312f774dd6ceebd82c7d8fc/coverage-7.14.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9eeb3fcbc13ba40dfbdb22d01d196a28e9cef9ed4c29b60061a1e0e823a9929d", size = 254008, upload-time = "2026-05-26T20:39:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/36f4aa9ca8a815e6036156e80706a67828bb97bd826948244f6996dda957/coverage-7.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f0cfc27c539f07cf5c0a4cfe211d0b6cae039f8f40526dbaa71944e64b50a7b", size = 255241, upload-time = "2026-05-26T20:39:36.71Z" }, + { url = "https://files.pythonhosted.org/packages/ca/79/95266316352f90f6b1c6736bb413302edfde2453fb32422d3911642691b3/coverage-7.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:221c70f316241a78e77e607c227cefc8808d4e08f28d99c04f35694690e940be", size = 257373, upload-time = "2026-05-26T20:39:38.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9c/58316d1f66c488b5fca8a0eb3e98348807813efa8a0d0833b9021be27488/coverage-7.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da028256b04ec30e5e0114b6f76172938c313991f0a2d3d894271315cf5d5e43", size = 251635, upload-time = "2026-05-26T20:39:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/ca2398a568e16fed7bb713e84ba3603a7164fb65779abe645c565ec890d5/coverage-7.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a085d7005236a767e3426148b2c407e53ad61695c562f8a81da2d373324901", size = 253373, upload-time = "2026-05-26T20:39:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/6e/2c/0396562c32deaebe7be51d865b3a41e9a87d7561acafe1a28f53b07e019a/coverage-7.14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b553d04b5e778a8e56d57eb134aff42a92718ecba45e79c4764ecfa40efd92ff", size = 251341, upload-time = "2026-05-26T20:39:43.907Z" }, + { url = "https://files.pythonhosted.org/packages/fd/8f/a94f9221184c9cae1ee115820e3798e48b6b17777a9f19e46fb9a0c8dc74/coverage-7.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:46f714d2fb8ae2f4f29f23ada7f1e79b759fff5a70f94a1dac23af204c3ec9e4", size = 255497, upload-time = "2026-05-26T20:39:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/71/69/505d70e47db1eaebcd002c39759707621ef184cd6b1ae084d9f41293f323/coverage-7.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1896f5e19ff3f0431c7ce2172adc54890fd97f86b59ced8ca1649145d9ffe35d", size = 251159, upload-time = "2026-05-26T20:39:48.03Z" }, + { url = "https://files.pythonhosted.org/packages/e0/aa/58681c383aa33a9d2ed40a02d7a22fbf780d1fa4d575396365777828198c/coverage-7.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:62fd185ef9df3c33d1c8178c5af105f762afbad96038de9a4ae100aa6297ca33", size = 252934, upload-time = "2026-05-26T20:39:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fd/11c928cd6bdffc7074bb5965c173d9ebf517fb00205e1da524b98d29ef92/coverage-7.14.1-cp313-cp313-win32.whl", hash = "sha256:ab4af6352741a604c431c6072fce5bee33bf0f20dc7a56618d6bf6bb89e9810c", size = 222584, upload-time = "2026-05-26T20:39:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/6f/92/fb416fc26d340dcba19518c418d6048e913186e17243982c5e435e41fa7a/coverage-7.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:7af486dabe8954d03b087f0021540897afe084f04e16ff5579e08cc46f871416", size = 223394, upload-time = "2026-05-26T20:39:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/02d56e3867972f77d5036de924643f26c056e848f00452cafb4dbc3c29b4/coverage-7.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:2224f89ffd0c5605ccce1ed7a584da162bc7c55f601ab1c946bc9de31a486b42", size = 222015, upload-time = "2026-05-26T20:39:55.374Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9e/fcc77914050df73f7662fa1f00902774c79c075a8388ab334074574bf77e/coverage-7.14.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de286598cc65d2b489411174b1faec2f5a7775fb3201fd925db2a76b4030f37d", size = 220733, upload-time = "2026-05-26T20:39:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/f7/67/2963cbdaf5cbadec44efa3a1e39eaa1f02df4079585f05387607a221e126/coverage-7.14.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:042c46ded7c288aeb07cf14a28b6c1e10b78fcba40171c3fa1e939377eeef0b5", size = 221086, upload-time = "2026-05-26T20:39:59.019Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/8701645574e11881f2f47d8930f98bc48b5d43b25eb5b4430dfc4a2f9f48/coverage-7.14.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f4ddbe407477f04c45115d1a4e5bc480f753553b534d338d4c3358b1cdd0ea52", size = 262381, upload-time = "2026-05-26T20:40:00.822Z" }, + { url = "https://files.pythonhosted.org/packages/7c/28/7a64d73598263e0c5abd5084211a8474488d31b3c552ff531c719dfcff62/coverage-7.14.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d13e6725992e2d2fd7d81d4f5241952d13740121dfd501da09201be39b2c003a", size = 264458, upload-time = "2026-05-26T20:40:02.506Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d8/4969179db9f7eb4df218e69540adf829d1c835f59452513d065d15446802/coverage-7.14.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f747dc8edcfe740130f28f32f3995e955494285717e86ee25af51db2219df08a", size = 266884, upload-time = "2026-05-26T20:40:04.421Z" }, + { url = "https://files.pythonhosted.org/packages/a6/78/a45d5794dbc9bafd97afc96a4377c86c7820d78b6cf51b89bc1d4e919275/coverage-7.14.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced2f09ef276fd58611a1ef502164ad266d2b75174e5a40cabbdb4033f9f6cf2", size = 268022, upload-time = "2026-05-26T20:40:06.298Z" }, + { url = "https://files.pythonhosted.org/packages/21/cb/4f5e354e9e3e67af96bd4e57113e6db6b22298c7168b13eec408a549903d/coverage-7.14.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b84800013769a78ccb9ef4659402e26d06867e337b61ec365f77ad008adea80e", size = 261631, upload-time = "2026-05-26T20:40:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ec/49/eced49af4cb996d5d8b7e94e736175c513e4facd3398507b89892b4326d8/coverage-7.14.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ea8cd6ca0ee9f616aaef3afc6882e32c2cbf18b00d96313ffd76af650574034d", size = 264443, upload-time = "2026-05-26T20:40:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/5603a88a7c5913a6b54f6cb1a8c46f7b39cbb30f27cd3f492908da09b2d7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:aa5e304a873fabddc11e484e9b6b738bd38bd7bed17b09aa84eecf5332e8b8bb", size = 262069, upload-time = "2026-05-26T20:40:11.999Z" }, + { url = "https://files.pythonhosted.org/packages/f0/59/2ae3cb79da554a06c8619d6c88ea19dd1e4aed4b834b6a83bb1fa243bdc5/coverage-7.14.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5a1c5215be81035e629d5bc756650634d0bf31991038db7a0eccb90f025ce16d", size = 265780, upload-time = "2026-05-26T20:40:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/b130c1dc999031f2648bd25317fbce505ad8d5562079b4ed81e736a84967/coverage-7.14.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:79058c47dae6788504b5effb319961bcd72d7240551464b91d474bc0ed186d69", size = 260970, upload-time = "2026-05-26T20:40:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/87/d1/ec13ccddeb48ec963bdfa72a11224bac2584bd045ba13beca82f8113e9c7/coverage-7.14.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:370c5afae3fa0658e11694a32b24c2778f6bc2d17718121f94ee185e69f26b54", size = 263157, upload-time = "2026-05-26T20:40:18.382Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c2/cd91ead503045161092d3845f7bb95ea2f25131ce96d3e314dd835d91b9c/coverage-7.14.1-cp313-cp313t-win32.whl", hash = "sha256:3758dd0a7f1fa57365ef2e781df0f0731d38b6e3772259d13dae4bd8a958d4b1", size = 223259, upload-time = "2026-05-26T20:40:20.381Z" }, + { url = "https://files.pythonhosted.org/packages/71/9f/1e28d97e6bd2c76b07f38b7c02870f1371255ff6717f54eca578fcbbdd0e/coverage-7.14.1-cp313-cp313t-win_amd64.whl", hash = "sha256:6ff665fb023a77386fe11685190cee1f60a7d635994a30d9b0a061533d470fce", size = 224320, upload-time = "2026-05-26T20:40:22.316Z" }, + { url = "https://files.pythonhosted.org/packages/a9/e0/d936e908f0e1efa55e52b91e01b52f1055cef5e1ab2718493390ed8e2fb8/coverage-7.14.1-cp313-cp313t-win_arm64.whl", hash = "sha256:17a5a241e5997621a956a7f402a7433ef4221e5152809b785bec79e2323799f1", size = 222577, upload-time = "2026-05-26T20:40:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d6/34/fc2f101b151af3799a101f0550b0454aa008afdc0add677394ec4aa8ea10/coverage-7.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d5ed429d0b8edaac649e889b4ffcedb6c80b06629a3f93050e3dddfb99235bee", size = 220091, upload-time = "2026-05-26T20:40:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a7/1ebae2ab5b961b5c79bb09fe7b3ac99edb190d8be4a8c510b2cf66f46468/coverage-7.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8011224a62280e50dab346960c03cf47aca1a1e09e608c0fb33fd6e0cc8e9500", size = 220421, upload-time = "2026-05-26T20:40:30.084Z" }, + { url = "https://files.pythonhosted.org/packages/5e/90/92aca9cf0acc95123c96cd1eb1f08917897a7f5dee01e15738922971ec31/coverage-7.14.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c42ec1e14f553c4f817e989365982e646e27211f10a0f717855b94a79c8906", size = 251466, upload-time = "2026-05-26T20:40:32.542Z" }, + { url = "https://files.pythonhosted.org/packages/26/2b/78048cbe3b999f6cbf9cc0d90abba6a88a3e0863a8c1c6cbc762f3f8802f/coverage-7.14.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06144cd511cf2624873a035c5069cf297144f6e77a73ee3d7a55b605ec5efb42", size = 253973, upload-time = "2026-05-26T20:40:34.473Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/c2e33b29d1cfde484a19d437afc343c6cd30b08d78cbbf9f5aff14e57b2b/coverage-7.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a311d8e1da24be5c1ccf85cbfb06315dbaa1703d5a1eab3f6432c72b837917c8", size = 255318, upload-time = "2026-05-26T20:40:38.154Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ee/aad2f108d63b769121005302f16bf66db8625c88ceaba466942e09a2607e/coverage-7.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c79cead5b5bc584d9c71451cb984d0e3a84e0c0937379c8efcbf27c8d661b851", size = 257633, upload-time = "2026-05-26T20:40:40.164Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/11a2c29b4fd76d9849f81d0bb812ec0017a9396df3217214e38934a8c837/coverage-7.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dcbf65f1f66a26cdd88c35cf68fb4729c5d1cd2e88added72420541dfb212034", size = 251488, upload-time = "2026-05-26T20:40:42.631Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/9a5820de4b8ac2b71d85e3b5fb49108d7469c665f0e2ad0dd7569023e305/coverage-7.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fd86572566fb40189a8260446158235159bc7a82dfbc87a3b39cf4fb57fcec1c", size = 253329, upload-time = "2026-05-26T20:40:45.208Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/f33e4823667e27548e8fd8df44217515303f9808d0ff29817db56f87d990/coverage-7.14.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7771b601718fdde84832c3a434ca9bbf4ae9adbc49d84198b4110700c3c77c36", size = 251291, upload-time = "2026-05-26T20:40:47.502Z" }, + { url = "https://files.pythonhosted.org/packages/68/9b/489db0ebb209054766b90a9014a45f6d26eb724c02ec21311c3733b5a644/coverage-7.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:39b21e212c55af06fa375e3dbf90a8a8e38792f3a910c580066d23563830ddd5", size = 255564, upload-time = "2026-05-26T20:40:49.372Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/16bc2d4c2409b23c7737edb68c83bc89e345f378050549fe1d75ac7d34d5/coverage-7.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f2302660e32562a532b442480121aef8aa61a5bdb20b30bf0adab29f10a5a4b4", size = 251107, upload-time = "2026-05-26T20:40:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/2629997469a00cd069d588a41c9dc887610f2775ae89d250c4791e65272a/coverage-7.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:03a6f93c1ec3b7f2e77b5dbcc5573a2c21f12529a5c6bbe0f16f72303cc2fa4d", size = 252764, upload-time = "2026-05-26T20:40:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ee/f78d63c8f079e0d7211c7e2401fa17e311514534ba61bae03e4b287ce4ab/coverage-7.14.1-cp314-cp314-win32.whl", hash = "sha256:8a3ce026d73290f42f08dafecbd82c193a74df280461fbf97300fec51fd133ee", size = 222837, upload-time = "2026-05-26T20:40:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/be539854f93a70dfbeec69117f33ec70dc42ff0b65b5b07ab8d40d04228e/coverage-7.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:114c95ef29302423b87d159075805f4ab973254a2638a5d7d046c94887cc87d7", size = 223650, upload-time = "2026-05-26T20:40:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/24e2842fef40f35ac82ba3a7719c8023d011bf3bf652d0675316a9d088a1/coverage-7.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:a07891c3f4805442b31b71e84ba3cf29ed1aa9a428284e06deeb4b23e5b46343", size = 222218, upload-time = "2026-05-26T20:41:00.321Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/ac0a9df5fe31c1e8bdd658074905fc12844a05c1a7e3fdb8417e97c31e23/coverage-7.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1101a5ebb083aecb625ebb6209d4105b58f647b093cb2dc8122d7b33f743cfe1", size = 220822, upload-time = "2026-05-26T20:41:02.281Z" }, + { url = "https://files.pythonhosted.org/packages/32/cf/f964fd9aff20323f9f1a726c97135f8a76bcd87b92dad141a456a43f3c64/coverage-7.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:851b9e1e4e8a4608e77c79714b2e77c0970d2ed7202a05e92ae407817481887b", size = 221084, upload-time = "2026-05-26T20:41:04.593Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5e/7e5ef2aba844de2b80d678619fcf0841b42e3f37f16411226f3fe4c1016f/coverage-7.14.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d5b89cdfb2ee051b71e8c3c70bd81a9eff81100f736a269136fe1a68efe00474", size = 262454, upload-time = "2026-05-26T20:41:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/75809bded87015cc4935524218a2a8ed8dd1a8498bfed30a2f4f7a4b4d34/coverage-7.14.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0177614a0370f227888b4e436a7c55686d6a9f90eb1ade2b624ba685a1686e86", size = 264578, upload-time = "2026-05-26T20:41:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/d33392dc14633525012d2d504fa1a33b05538bf535f5c1d64675e5754b78/coverage-7.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d69af5dea2de76fc485a83032a630523f985198b7e25be901ec60181587b01e", size = 266981, upload-time = "2026-05-26T20:41:10.824Z" }, + { url = "https://files.pythonhosted.org/packages/2a/49/0157c4428c2aca7f1e09d5565930586fd5ae36f1655f08b0daa7cf1fcae1/coverage-7.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:35ab22d91de736e8966b980dc355cbcdd2c6dbbcfe275f9a2991bc8a91b3df65", size = 268112, upload-time = "2026-05-26T20:41:12.966Z" }, + { url = "https://files.pythonhosted.org/packages/96/26/86b9ce71f4092b1ed325ce1421698081df1286b833400b6836912834d6e0/coverage-7.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:357d4e32935c36588aaba057d734fa32428c360c9fc2e4442afbf1b646beee6e", size = 261558, upload-time = "2026-05-26T20:41:15Z" }, + { url = "https://files.pythonhosted.org/packages/20/4c/c311210c5472cf5401d8422b0d7812cdd520f24417673afabda6c323faca/coverage-7.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:51bd64741cc6fa065abd300ede1afe5a5291ece9c31da8b24884deda48bcc3f8", size = 264447, upload-time = "2026-05-26T20:41:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/59513f8710ed3e6b0ac0a050a5b7e977bb9c9e880354863b5d00d8809256/coverage-7.14.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9132cd363a68a4c3daa7c8704a654b1e39d3360f6f5b8ddd470608a945236c07", size = 262048, upload-time = "2026-05-26T20:41:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/bceed32dc494f5bbf50f775cd2e78ca814953942b5ea28d3c1c3ac316f14/coverage-7.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07c6290b1697b862c0478eab545eec949a0d0e4d6d03497f446d706da3b4f2de", size = 265781, upload-time = "2026-05-26T20:41:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c5/9348fe40dbfd4991aaf78df2c6c3098bfb2cc834d1fd362a64b4efef855a/coverage-7.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5ea0c297e27133853b4d8a3eb799bff5a2dbd9f2f41537a240d337ac9b4df890", size = 260896, upload-time = "2026-05-26T20:41:23.428Z" }, + { url = "https://files.pythonhosted.org/packages/ca/92/1ea0f03929da7cf87206b1fa24f4c8e9c158be0455481af29ec0a1f3503f/coverage-7.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:01b7733daad0237daa01ef80fe2dfceffc911e6a17fa7b55d14aa8214eaaaecd", size = 263214, upload-time = "2026-05-26T20:41:25.419Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/b2493c054c0e01a643266742ab45e15744e60743f9260cd930c7142b1124/coverage-7.14.1-cp314-cp314t-win32.whl", hash = "sha256:6adc5a36984624a70bf11d7184e20fa0a49aa7c47ffab43804106a1a695ea22e", size = 223624, upload-time = "2026-05-26T20:41:27.795Z" }, + { url = "https://files.pythonhosted.org/packages/fc/bd/3e1e6a57fccd2d7c83fcdf338e93ba98eb85c6e877dd34731ac585375490/coverage-7.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ddf799247318f34dbcd2efa8c95a8d0642674e926bb1774cf9b63dfd2a389d1c", size = 224728, upload-time = "2026-05-26T20:41:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d7/31066cf1d2f0c6c797fce911bcfa01dd35642dc6da992a950256097c5860/coverage-7.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:145986fe66647eb489f18d9a997567a3fd358584c4b5a808769113abc07466af", size = 222752, upload-time = "2026-05-26T20:41:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/8a/3c/1a983b9a745d7f83d53f057bcc5bf79ba6a2bbc08266b3f0c7d6fe630c9b/coverage-7.14.1-py3-none-any.whl", hash = "sha256:a252f21c27e38347e60111a3266b03827422a7d5525951aceee313aa68bab1d2", size = 211815, upload-time = "2026-05-26T20:41:34.078Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "crate" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "orjson" }, + { name = "urllib3" }, + { name = "verlib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/c0/bafb5700785ac6f8e13cd47e1d34d657f5c7cbc035f439f09869a437d0d6/crate-2.2.1.tar.gz", hash = "sha256:dd329ed1d83f678d2f5ecba1adafb4673ef7006c76aa1d7ac8e9e2b9e091975d", size = 72619, upload-time = "2026-06-17T08:57:45.392Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/3a/1e7973cc233ba64c1a892f5fa4641f634bda4740b5a2bd0997732c84744a/crate-2.2.1-py3-none-any.whl", hash = "sha256:2f17bf6a8c7e8ab7351a28c8da98c3686f5adfcc5c7123cbe664f7d7bcc81831", size = 34189, upload-time = "2026-06-17T08:57:44.085Z" }, +] + +[[package]] +name = "cryptography" +version = "50.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" }, + { url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" }, + { url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" }, + { url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fb/951032a3bf22a5697c83183fb6294a4843772947a70e616c57b3ff5f522e/cryptography-50.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41", size = 3989258, upload-time = "2026-07-31T14:23:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/67/91eb047e69c5e845f2f14b8a2e4a1aab0f283cb885531e9e22c8adb176bc/cryptography-50.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc", size = 4700648, upload-time = "2026-07-31T14:24:00.702Z" }, + { url = "https://files.pythonhosted.org/packages/30/82/85f0f7425c856b9f96459411eb12e74ef72df9caf6f8f15bf23a33ff131f/cryptography-50.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f", size = 4682442, upload-time = "2026-07-31T14:24:02.538Z" }, + { url = "https://files.pythonhosted.org/packages/1a/28/b555a365adff1cca2fbe7b9e487d68a40de6bc67ff2cb587473eb43de0e7/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3", size = 4707596, upload-time = "2026-07-31T14:24:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/72/d8/f52538140cc719df62a01cf87d1c7142318d235817109d6f4054d7c352d6/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533", size = 5314552, upload-time = "2026-07-31T14:24:06.31Z" }, + { url = "https://files.pythonhosted.org/packages/38/14/6120e5bd7c5aa022ad15424ba4d5c5269d0d9448ed4d55e492ea91e3c1c4/cryptography-50.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037", size = 4717113, upload-time = "2026-07-31T14:24:08.349Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/190bf38c3ee2e0f8efc9860ae100c9df4169742eef274b91e7aa1cb133b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f", size = 4338580, upload-time = "2026-07-31T14:24:10.227Z" }, + { url = "https://files.pythonhosted.org/packages/3a/63/504ccfbbe61fd8aa983f7f146399cdf034c72c2fc55f5b2dfdcdcdb20c99/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11", size = 4707038, upload-time = "2026-07-31T14:24:12.169Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/2cf79bbfc4d12ca106437a6e170d6aaa01a373e93093118aaaef0e801bd4/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d", size = 5273110, upload-time = "2026-07-31T14:24:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e5/45/8aae2972c520145377ea3559a605a899bebe227bf070b33cdb445929a9b9/cryptography-50.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c", size = 4716439, upload-time = "2026-07-31T14:24:16.415Z" }, + { url = "https://files.pythonhosted.org/packages/7b/20/4fe50b619a48c2525cc46e2dbc1ac490708d704be5d467bdaac6dc955682/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c", size = 4837383, upload-time = "2026-07-31T14:24:18.553Z" }, + { url = "https://files.pythonhosted.org/packages/92/91/3a31366e183343d3703f8995c095f5734676bd6938118047e50fcf279eb4/cryptography-50.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95", size = 4985772, upload-time = "2026-07-31T14:24:20.385Z" }, + { url = "https://files.pythonhosted.org/packages/74/9a/02ffe35b2853d121689871eb5dce862092562b3a1ed5cc98f1aaed441506/cryptography-50.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269", size = 3816291, upload-time = "2026-07-31T14:24:22.125Z" }, + { url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" }, + { url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" }, + { url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" }, + { url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" }, + { url = "https://files.pythonhosted.org/packages/9d/3e/e54cde8c01631a5a8226ccd617eab9e57fd5cfdad90f1a9e6bb570794631/cryptography-50.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c", size = 3963170, upload-time = "2026-07-31T14:24:51.968Z" }, + { url = "https://files.pythonhosted.org/packages/01/b6/0b9e125e90f3d2dcf599a218a899cda7326a3158cfa258723f0b398b08f6/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a", size = 4692441, upload-time = "2026-07-31T14:24:53.743Z" }, + { url = "https://files.pythonhosted.org/packages/53/c9/a5151588710785a96d7bc4de27d4cd62f263bbbcb203cfe29df537eb6505/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e", size = 4699810, upload-time = "2026-07-31T14:24:55.746Z" }, + { url = "https://files.pythonhosted.org/packages/c7/1a/15b92b25eb6ce3089cd49377ae990a0f3ad485a510f968aed1f19dbdcdf2/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d", size = 4691924, upload-time = "2026-07-31T14:24:58.082Z" }, + { url = "https://files.pythonhosted.org/packages/62/15/219075012ab13e8905f3cd572204f4acb4b111df787104346b9bc0cea789/cryptography-50.0.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437", size = 4699593, upload-time = "2026-07-31T14:24:59.951Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b5/c2c5fce26f0ee40d21bafe7f191d29a34b35a65ac4fe8a1191d1983612e9/cryptography-50.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9", size = 3813796, upload-time = "2026-07-31T14:25:02.298Z" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/b2/d6fc3f2347f43dada79e5ff118493e8109c98400a0e29a1d5264a3aa479b/distlib-0.4.1.tar.gz", hash = "sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b", size = 610526, upload-time = "2026-06-02T11:17:40.691Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/18/3497c4fa83a76dcb154923fd2075522e8dd6995ecee4093c00ae18160046/distlib-0.4.1-py2.py3-none-any.whl", hash = "sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97", size = 469216, upload-time = "2026-06-02T11:17:38.779Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docker" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/7f/731ff914b0255d3d065f45fd4e626d4b8c95dbcbaada049f337a6ac16410/docker-7.2.0.tar.gz", hash = "sha256:cebb93773d334f778e023a7ee352a8d6e13ab1bd3b863a4d4a59dec897df43ac", size = 118731, upload-time = "2026-07-09T14:53:46.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/23/529140fe1aab80fc6992f93a706deec709140a6397439139a054e1515c45/docker-7.2.0-py3-none-any.whl", hash = "sha256:a3f45fdeb9165e2d25d9a1d02ddf3bc70fb572cf5ebbf9b58558c22caf29b71f", size = 148775, upload-time = "2026-07-09T14:53:45.224Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "durationpy" +version = "0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, +] + +[[package]] +name = "events" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/ed/e47dec0626edd468c84c04d97769e7ab4ea6457b7f54dcb3f72b17fcd876/Events-0.5-py3-none-any.whl", hash = "sha256:a7286af378ba3e46640ac9825156c93bdba7502174dd696090fdfcd4d80a1abd", size = 6758, upload-time = "2023-07-31T08:23:13.645Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "geojson" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/a9/bd61eee2c7904947094b74866b569f3fd5a8d6ac907ecdfecef74b19d459/geojson-3.3.0.tar.gz", hash = "sha256:92e83b9cb378a450b42f1207bb9b2a031f9fc89185f335153c44369b8b8b71fd", size = 25141, upload-time = "2026-05-28T21:48:08.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/5e/fdd72167b57158d743353f71d453200719744d1e75f18b1c8230508db370/geojson-3.3.0-py3-none-any.whl", hash = "sha256:a2d885187eeaa8b357600b3fcc9d963cb4300d1694196636dbd7eddc82fd0825", size = 15181, upload-time = "2026-05-28T21:48:06.648Z" }, +] + +[[package]] +name = "geomet" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/8c/dde022aa6747b114f6b14a7392871275dea8867e2bd26cddb80cc6d66620/geomet-1.1.0.tar.gz", hash = "sha256:51e92231a0ef6aaa63ac20c443377ba78a303fd2ecd179dc3567de79f3c11605", size = 28732, upload-time = "2023-11-14T15:43:36.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/90/3bc780df088d439714af8295196a4332a26559ae66fd99865e36f92efa9e/geomet-1.1.0-py3-none-any.whl", hash = "sha256:4372fe4e286a34acc6f2e9308284850bd8c4aa5bc12065e2abbd4995900db12f", size = 31522, upload-time = "2023-11-14T15:43:35.305Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.30.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.53.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/ad/ff781329bbbdc0974a098d996e89c9e1f7024262f9e3eec442fbb9ad1ac6/google_auth-2.53.0.tar.gz", hash = "sha256:e7e6aa16f6bee7b2b264830fd04f08087a1d5a836df516251a5d15327b246c9c", size = 335844, upload-time = "2026-05-15T20:53:07.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/c9/db44165ba7c581268c6d46017ef63339110378305062830104fc7fa144cb/google_auth-2.53.0-py3-none-any.whl", hash = "sha256:6e7449917c599b35126a99ec268ec6880301f2fea41dce198fe8fd83ff642b68", size = 246071, upload-time = "2026-05-15T20:53:05.609Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/dd/1eef226e470369b26824a505c34482c0b493bc35fe8e0c6b003b5feca21a/google_cloud_core-2.6.0.tar.gz", hash = "sha256:e76149739f90fac1fc6757c09f47eaccb3145b54adbd7759b0f7c4b235f46c83", size = 36001, upload-time = "2026-05-07T08:04:04.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/4a/98da8930ab109c73d9a5d13782a9ebb81ea8c111f6d534a567b71d23e52b/google_cloud_core-2.6.0-py3-none-any.whl", hash = "sha256:6d63ac8e5eca6d9e4319d0a1e2265fadcd7f1049904378caecfa01cf52dd869e", size = 29390, upload-time = "2026-05-07T08:02:34.672Z" }, +] + +[[package]] +name = "google-cloud-datastore" +version = "2.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "grpcio" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/b2/d4679b666980da16cfe8c80c7a3f1996c29fc2b9d5a2db1ab22144d88a09/google_cloud_datastore-2.25.0.tar.gz", hash = "sha256:dd646a3d8f99c2750bb5f6e0f18ece7bed95fd76e02dacaddbfa35a2b22328ff", size = 265596, upload-time = "2026-06-03T15:28:05.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/b2/fd5a3e55a5f698fd527f783ce27368e8e9c3d872bd1e1c39dd377b2b9f14/google_cloud_datastore-2.25.0-py3-none-any.whl", hash = "sha256:e47e25933bcfad7118335015b0de2f2b2b5444e81f6f029a62040f568f4f52eb", size = 207230, upload-time = "2026-06-03T15:27:15.488Z" }, +] + +[[package]] +name = "google-cloud-pubsub" +version = "2.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "grpcio" }, + { name = "grpcio-status" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/2b/4bf2c17e319ff65340389565b0e1b4d72696d87802b2f5f94390fbefa73c/google_cloud_pubsub-2.39.0.tar.gz", hash = "sha256:eed65e25f57f95bf3e02d96d7ee171688b23922471f9f21b5a91ed90e1282c0f", size = 402096, upload-time = "2026-06-03T15:28:26.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/20/dd0b27d4ad4577c062e77ff968ca3e2d404186cd78c8a2a53a0ef5fe5389/google_cloud_pubsub-2.39.0-py3-none-any.whl", hash = "sha256:7210d691a46d7a66559696899ebe6eb731e63de29b624964b3be4dd2d12d3e19", size = 324665, upload-time = "2026-06-03T15:27:41.119Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + +[[package]] +name = "greenlet" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/21/117c8710abb7f146d804a124c07eb5964a60b90d02b72452885aecc18efa/greenlet-3.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7eacb17a9d41538a2bc4912eba5ef13823c83cb69e4d141d0813debe7163187f", size = 283510, upload-time = "2026-05-20T13:12:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f7/6762a56fa5f6c2295c449c6524e10ce481e381c994cc44d9d03aef0700fb/greenlet-3.5.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5cc9606aa5f4e0bde0d3bd502b44f743864c3ffa5cfa1011b1e30f5aa02366f", size = 599696, upload-time = "2026-05-20T14:00:02.906Z" }, + { url = "https://files.pythonhosted.org/packages/0f/05/85a511e68ee109aff0aa00b4b497806091dd2d82ce209e49c6e801bd5d92/greenlet-3.5.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3d35f87c7253b715d13d679e0783d845910144f282cb939fe1ba4ac8616269c", size = 612618, upload-time = "2026-05-20T14:05:39.202Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/8b83d18ae07c46c019617f35afd7b47aab7f9b4fbb12fc637d681e10bdd8/greenlet-3.5.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:540dae7b956209af4d70a3be35927b4055f617763771e5e84a5255bea934d2f5", size = 612947, upload-time = "2026-05-20T13:14:23.469Z" }, + { url = "https://files.pythonhosted.org/packages/5d/14/ad1f9fc9b82384c010212464a3702bd911f95dab2f1180bc6fbcfb1f958c/greenlet-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cdb691169715a9a492844a83246f090182247d1a5031dc78a403f68ba1e97", size = 1571425, upload-time = "2026-05-20T14:02:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/46/1c/43b8203cf10f4292c9e3d270e9e5f5ade79115a0a0ca5ea6f1be5f8915a7/greenlet-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d59e840387076a51016777a9328b3f2c427c6f9208a6e958bad251be50a648d", size = 1638688, upload-time = "2026-05-20T13:14:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/ac/6e/0344b1e99f58f71715456e46492101fd2daa408957b8186ade0a4b515da7/greenlet-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:b9152fca4a6466e114aaec745ae61cba739903a109754a9d4e1262f01e9259b1", size = 237763, upload-time = "2026-05-20T13:11:35.659Z" }, + { url = "https://files.pythonhosted.org/packages/42/3c/ff890b466eaba2b0f5e6bdfff025f8c75f41b8ffdc3dbc3d24ad261e764a/greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f", size = 284764, upload-time = "2026-05-20T13:09:10.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/5e5457be3d256918f6a4756f073548a3f0190836e2cc94aa6d0d617a940b/greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2", size = 603479, upload-time = "2026-05-20T14:00:04.757Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e1/f89a21d58d308298e6f275f13a1b472ed96c680b601a371b08be6a725989/greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33", size = 615495, upload-time = "2026-05-20T14:05:40.87Z" }, + { url = "https://files.pythonhosted.org/packages/75/de/af6cef182862d2ccd6975440d21c9058a77c3f9b469abf94e322dfd2e0e3/greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563", size = 614754, upload-time = "2026-05-20T13:14:24.947Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c6/50e520283a9f19388a7326b05f9e8637e566003475eacaadad04f558c68d/greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071", size = 1574097, upload-time = "2026-05-20T14:02:24.003Z" }, + { url = "https://files.pythonhosted.org/packages/21/1c/13abd1f4860d987fa5e1170a01930d6e6cd40d328de487a3c9fdaff0ffd0/greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c", size = 1641058, upload-time = "2026-05-20T13:14:31.83Z" }, + { url = "https://files.pythonhosted.org/packages/f5/56/5f332b7705545eac2dc01b4e9254d24a793f2656d55d5cc6b94ee59d22ae/greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e", size = 238089, upload-time = "2026-05-20T13:14:03.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a9/a3c2fa886c5b94863fb0e61b3bc14610b7aa94cf4f17f8741b11708305fc/greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523", size = 234989, upload-time = "2026-05-20T13:08:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/37/4549f149c9797c21b32c2683c33522af22522099de128b2406672526d005/greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2", size = 286220, upload-time = "2026-05-20T13:07:28.463Z" }, + { url = "https://files.pythonhosted.org/packages/38/ff/a4f436709716965eaab9f36ea7b906c8a927fbe32fb1372a2071d964f6b1/greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed", size = 601585, upload-time = "2026-05-20T14:00:06.141Z" }, + { url = "https://files.pythonhosted.org/packages/65/ad/54bc3fcee3ad368a61b19b67d88117f7a8c29727bf71fffdeda81fbd946e/greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10", size = 614215, upload-time = "2026-05-20T14:05:42.675Z" }, + { url = "https://files.pythonhosted.org/packages/40/69/b91cda0647df839483201545913514c2827ebea5e5ccdf931842763bc127/greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b", size = 611358, upload-time = "2026-05-20T13:14:26.37Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/3cf77e080350cd02fa307bb2abf05df48f4482c240275bbd2c203ba8bb1c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207", size = 1570475, upload-time = "2026-05-20T14:02:25.29Z" }, + { url = "https://files.pythonhosted.org/packages/65/2c/18cece62045e74598c3c393f70dce4a63f56222015ba29a5d4eeb04f764c/greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823", size = 1635625, upload-time = "2026-05-20T13:14:34.027Z" }, + { url = "https://files.pythonhosted.org/packages/30/f5/310d104ddf41eb5a70f4c268d22508dfb0c3c8e86fec152be34d0d2ed819/greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b", size = 238791, upload-time = "2026-05-20T13:10:39.018Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/ceca11f504cd23a8047a3dea31919adc48df9b626dd0c13f0d858734fdfd/greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188", size = 235580, upload-time = "2026-05-20T13:08:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" }, + { url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" }, + { url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" }, + { url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" }, + { url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" }, + { url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" }, + { url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" }, + { url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" }, + { url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" }, + { url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" }, + { url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" }, + { url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" }, + { url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" }, + { url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" }, +] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/4f/d098419ad0bfc06c9ce440575f05aa22d8973b6c276e86ac7890093d3c37/grpc_google_iam_v1-0.14.4.tar.gz", hash = "sha256:392b3796947ed6334e61171d9ab06bf7eb357f554e5fc7556ad7aab6d0e17038", size = 23706, upload-time = "2026-04-01T01:57:49.813Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/22/c2dd50c09bf679bd38173656cd4402d2511e563b33bc88f90009cf50613c/grpc_google_iam_v1-0.14.4-py3-none-any.whl", hash = "sha256:412facc320fcbd94034b4df3d557662051d4d8adfa86e0ddb4dca70a3f739964", size = 32675, upload-time = "2026-04-01T01:57:47.69Z" }, +] + +[[package]] +name = "grpcio" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/a8/690a085b4d1fe066130de97a87de32c45062cf2ecd218df9675add895550/grpcio-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5", size = 5946986, upload-time = "2026-02-06T09:54:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/c7/1b/e5213c5c0ced9d2d92778d30529ad5bb2dcfb6c48c4e2d01b1f302d33d64/grpcio-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2", size = 11816533, upload-time = "2026-02-06T09:54:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/18/37/1ba32dccf0a324cc5ace744c44331e300b000a924bf14840f948c559ede7/grpcio-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d", size = 6519964, upload-time = "2026-02-06T09:54:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f5/c0e178721b818072f2e8b6fde13faaba942406c634009caf065121ce246b/grpcio-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb", size = 7198058, upload-time = "2026-02-06T09:54:42.389Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b2/40d43c91ae9cd667edc960135f9f08e58faa1576dc95af29f66ec912985f/grpcio-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7", size = 6727212, upload-time = "2026-02-06T09:54:44.91Z" }, + { url = "https://files.pythonhosted.org/packages/ed/88/9da42eed498f0efcfcd9156e48ae63c0cde3bea398a16c99fb5198c885b6/grpcio-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec", size = 7300845, upload-time = "2026-02-06T09:54:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/23/3f/1c66b7b1b19a8828890e37868411a6e6925df5a9030bfa87ab318f34095d/grpcio-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a", size = 8284605, upload-time = "2026-02-06T09:54:50.475Z" }, + { url = "https://files.pythonhosted.org/packages/94/c4/ca1bd87394f7b033e88525384b4d1e269e8424ab441ea2fba1a0c5b50986/grpcio-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813", size = 7726672, upload-time = "2026-02-06T09:54:53.11Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/f16e487d4cc65ccaf670f6ebdd1a17566b965c74fc3d93999d3b2821e052/grpcio-1.78.0-cp310-cp310-win32.whl", hash = "sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de", size = 4076715, upload-time = "2026-02-06T09:54:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/2a/32/4ce60d94e242725fd3bcc5673c04502c82a8e87b21ea411a63992dc39f8f/grpcio-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf", size = 4799157, upload-time = "2026-02-06T09:54:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" }, + { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" }, + { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" }, + { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" }, + { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/cd/89ce482a931b543b92cdd9b2888805518c4620e0094409acb8c81dd4610a/grpcio_status-1.78.0.tar.gz", hash = "sha256:a34cfd28101bfea84b5aa0f936b4b423019e9213882907166af6b3bddc59e189", size = 13808, upload-time = "2026-02-06T10:01:48.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/8a/1241ec22c41028bddd4a052ae9369267b4475265ad0ce7140974548dc3fa/grpcio_status-1.78.0-py3-none-any.whl", hash = "sha256:b492b693d4bf27b47a6c32590701724f1d3b9444b36491878fb71f6208857f34", size = 14523, upload-time = "2026-02-06T10:01:32.584Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "hvac" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/57/b46c397fb3842cfb02a44609aa834c887f38dd75f290c2fc5a34da4b2fee/hvac-2.4.0.tar.gz", hash = "sha256:e0056ad9064e7923e874e6769015b032580b639e29246f5ab1044f7959c1c7e0", size = 332543, upload-time = "2025-10-30T12:57:47.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/33/71e45a6bd6875f44a26f99da31c63b6840123e88bedf2c0b1ce429b8be12/hvac-2.4.0-py3-none-any.whl", hash = "sha256:008db5efd8c2f77bd37d2368ea5f713edceae1c65f11fd608393179478649e0f", size = 155921, upload-time = "2025-10-30T12:57:46.253Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "ibm-db" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/36/8cd1503b7f947293bb045ec86e6fa60a0bef3bacf1a601840ea65c4b78de/ibm_db-3.2.9.tar.gz", hash = "sha256:0ad6dc4575519a5563fd7f845fc6bc00d688020e1d3ae78d382e79183bc32b0b", size = 276595, upload-time = "2026-05-26T15:41:25.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/cc/4db3062ce3b404098e73ec67498b25c672b212303a870c2703ac8f9a00c6/ibm_db-3.2.9-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:e130a461a0ce3fecdb9896ec767d95d89d4f14e5eb4b8b5e63aca249790c5155", size = 24166574, upload-time = "2026-04-22T16:27:50.985Z" }, + { url = "https://files.pythonhosted.org/packages/ec/62/98bd8e55a9a2fdd20997bd81e8d8f68b8956fa55d8c2c0cae2fd94ccd2f5/ibm_db-3.2.9-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4edde94384f452720b6bc58b94ed03c9b3f5dadb7343d983a55c1122b4a38cf0", size = 20391416, upload-time = "2026-04-22T16:27:54.236Z" }, + { url = "https://files.pythonhosted.org/packages/14/be/6f95944b3052b288e5842141f334633c900ba3320f8534a8f80f657ffbde/ibm_db-3.2.9-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:80c2e242601ff2e99bce79da0fddc6e0e525b1f83d05423f00524bad5c41991e", size = 40635334, upload-time = "2026-04-22T16:27:56.746Z" }, + { url = "https://files.pythonhosted.org/packages/89/85/571df0152cb8632e99f7923599b94fd2139f01f9e9d6ca92345f75223e9e/ibm_db-3.2.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0838993f2e98827afa9f30a1a4648bdadccabb6f9f6805ee42f24787a6ae45b7", size = 43980561, upload-time = "2026-04-22T16:27:59.973Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4d/73602f4decf4c3f2f21079108cd5b43336db8c608c16aba8afd2578aa474/ibm_db-3.2.9-cp310-cp310-win32.whl", hash = "sha256:825f1ab7d911daef6ca370af62608d663e2193244832cad8dabbfa9708e81bdb", size = 24609207, upload-time = "2026-04-22T16:28:03.043Z" }, + { url = "https://files.pythonhosted.org/packages/0a/22/8d558528e019bdd595290ba109f837b17d476bf4fbeed46e4980b052a961/ibm_db-3.2.9-cp310-cp310-win_amd64.whl", hash = "sha256:b163dd252f695113851a3d480289f8b368885835fe3296e8b202063440fe34c8", size = 28754450, upload-time = "2026-04-22T16:28:05.536Z" }, + { url = "https://files.pythonhosted.org/packages/e0/47/992fe2d1157963df678d94cc2573e4e292d6e29c2c47fb069a36f16e3c90/ibm_db-3.2.9-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:b72049768de33e0767e7ca4f5cf9c44812a6422c8a4e07bb07fd30c242f555b2", size = 24166574, upload-time = "2026-04-22T16:28:08.538Z" }, + { url = "https://files.pythonhosted.org/packages/a6/27/628276aff846d1afa4680106ef7905916b12f32855c95540c638184203ec/ibm_db-3.2.9-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a002be22327f29e1f6cf48b7ee84843e6a257bb7a2f7d017caa6e731aaea4e5a", size = 20391417, upload-time = "2026-04-22T16:28:11.392Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a5/3adf8573468a18f3c82fc41c949326a500ee0335f0290e90b565722eacc0/ibm_db-3.2.9-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5831070fe9e6b2654668cfada10fb31a64e7555a7ef35e04e17ca831822df3bb", size = 40641529, upload-time = "2026-04-22T16:28:13.944Z" }, + { url = "https://files.pythonhosted.org/packages/12/9d/9c0c4cf2eacfdfd5faf2f878ca1e2fdb02640a21f3defa2390c0d41c85cc/ibm_db-3.2.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ec172bc1c61ac15cb98ebe1a2737689752b6565a2afa9c4442a09129f2ada963", size = 43987216, upload-time = "2026-04-22T16:28:17.083Z" }, + { url = "https://files.pythonhosted.org/packages/05/c4/13723b51f6b560f44f146518e52b81c47e4a748d118e45f1ae3a5b496855/ibm_db-3.2.9-cp311-cp311-win32.whl", hash = "sha256:8aaefd0162f7c500af87b39593eeec126312cbebf6c4ff7c3051eeb9e8b7f47d", size = 24609273, upload-time = "2026-04-22T16:28:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/34/ef/b3dba8a536b8be36086c05f2d6903593aea7e9aef10c32bb7fabc7292660/ibm_db-3.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:82517f76253ba3153aa8a43073cf9230a487974a660f3cf99bd4adda41910407", size = 28754305, upload-time = "2026-04-22T16:28:22.521Z" }, + { url = "https://files.pythonhosted.org/packages/0d/19/e4664a631cf69c2aa6684c03073b732ccbd41ed440a9d5d94f3700b979cb/ibm_db-3.2.9-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:5432ef9103c094631ebb7e534a72dfd8324f265c3695e79698eb5c09fa28cddb", size = 24166563, upload-time = "2026-04-22T16:28:25.312Z" }, + { url = "https://files.pythonhosted.org/packages/40/23/5f9388f5ee2148a0ca5c372c4124415bbf7aaba5b64cd13ef5fb801becde/ibm_db-3.2.9-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d4b650ac1e3a9161090234821a655adb767baa52764e839c1a5ff306cd967de5", size = 20391654, upload-time = "2026-04-22T16:28:27.865Z" }, + { url = "https://files.pythonhosted.org/packages/77/02/e1226db9f19ae1672098027cbb0fab7bea8c29e9b802e4337a9651595c09/ibm_db-3.2.9-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4b3d8d707a9cf3bf633c7742fffc557d9c71e6555f0c9733a0f9b8ed7f874ffe", size = 40606621, upload-time = "2026-04-22T16:28:30.411Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/3bf46353bb850d0ab71b8f9cc396da4e76d41e54abad26bc5f2fed95c8f7/ibm_db-3.2.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e60e1a12fa8e36510384944e1b438e6e9a353bc92d1d496a66acc8e1cb853a5e", size = 43945644, upload-time = "2026-04-22T16:28:33.351Z" }, + { url = "https://files.pythonhosted.org/packages/fe/75/c0506dc89c4da674adb40aa038729b1513d90a1ef3e2e6e34faaa00a5981/ibm_db-3.2.9-cp312-cp312-win32.whl", hash = "sha256:a0b395c0f770627bbb6765f45d9fe99ed990fc4801c1adeeaa872d599ccc3b9d", size = 24608567, upload-time = "2026-04-22T16:28:36.145Z" }, + { url = "https://files.pythonhosted.org/packages/e9/fa/730129a0169d4986c3af351617051a2fbcc73858de21f141ef8978f502a4/ibm_db-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:2353c0b968ec337ee4d4111dc4f8fe15a56818b3c399939de952f9a1f2589840", size = 28755039, upload-time = "2026-04-22T16:28:38.868Z" }, + { url = "https://files.pythonhosted.org/packages/b1/62/2027a98282f2bc02ece82b46ea086b43cbc73fa4aa50e371a8e828a4b3cd/ibm_db-3.2.9-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09cc163f950d0e8ffb8e390ddd65c3de45c0dc752ce7744e0934e74374134572", size = 24166466, upload-time = "2026-04-22T16:28:41.663Z" }, + { url = "https://files.pythonhosted.org/packages/98/86/d66b45f1d7e975215abdcb9d33e61ed050467c0661fe6d98c65d2b6476bb/ibm_db-3.2.9-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:b9264183d3353f68a77f06a8cc4ff8ba8fdfc567bcde619c74b7fc685f27152a", size = 20391631, upload-time = "2026-04-22T16:28:44.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/8b/984c1584d1ffaa61a9c88d01ebda111bac04ede74ec8fa10528c89f680b5/ibm_db-3.2.9-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:06e578cceff73ec5a04a89b2d1807867b37553bee8dcd32369a9d07d17fe86c2", size = 40606656, upload-time = "2026-04-22T16:28:47.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/b017c182e3e5e902faa25b81a0416afdc7341a6fb03494f78fcfe69224cb/ibm_db-3.2.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bbc992915eb03a70be716844bb05f252dc0413f0d475602445be66df7e188a11", size = 43945653, upload-time = "2026-04-22T16:28:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/3f/08/fd17875b1ddf66a8252f6cb80d30f7d2d7ffe5fd317a473ce25067ec6305/ibm_db-3.2.9-cp313-cp313-win32.whl", hash = "sha256:03e044a50abb91852b194bf6f6ca35b1c46b8695e0377c663ee3361c118024ce", size = 24608571, upload-time = "2026-04-22T16:28:53.195Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c7/fcca5a11346d218e6a55328211c575cc29c53e7423e8b3c6c99e93144fbd/ibm_db-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:973edafd5622a947d3a205274db5ace68a7d5bf53d92003f0901151b33c89d27", size = 28755050, upload-time = "2026-04-22T16:28:55.681Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1e/93cb11b58589bab4b58433a54d2eb2716e4a735d9f604f01afe1dc26485c/ibm_db-3.2.9-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5190366f1d4a777797a39624146cfa99d0884f8a3c7ced1c82c8fb9520d65faa", size = 24166743, upload-time = "2026-04-22T16:28:58.396Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b4/238c00b95b2697c5e3cf1526057e5389c5d41d7803246c21f5057b5e2318/ibm_db-3.2.9-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d204acdaa08436dd84ef999bb048ff352b7a1deae02752d144586bd278d9d31a", size = 20391505, upload-time = "2026-04-22T16:29:01.228Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a1/e60b39120807bfafacdbc2a85b5d88b36fccfb36ed45338b792c77b63612/ibm_db-3.2.9-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9eb5725526b046b0d0e1021c30fe2e60078f2f1080010dc93f95d057c51cce0a", size = 40606947, upload-time = "2026-04-22T16:29:04.25Z" }, + { url = "https://files.pythonhosted.org/packages/46/ec/e44e5c11431a16ec933e10df9bfb55e97001b6946c10eba584219dd78e7a/ibm_db-3.2.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ccbce68115de998ba99af314b489f36daab5453dbdb69c9a625b3a9722da0e3", size = 43945000, upload-time = "2026-04-22T16:29:07.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/8b544037a245e573b4ba21f4b258143e5e4c147c9a75c25eae263ee6a861/ibm_db-3.2.9-cp314-cp314-win32.whl", hash = "sha256:350d6e40c62340b583b6aa4887730a626191290fa91f63d9f7e58ff60edf17af", size = 25562087, upload-time = "2026-04-22T16:29:10.118Z" }, + { url = "https://files.pythonhosted.org/packages/07/92/6a3749de901a633c4eff94e58fb2263691981582771f9dc03ba54ba226f2/ibm_db-3.2.9-cp314-cp314-win_amd64.whl", hash = "sha256:62b2fc14da8b49895f6d9670a3bdce65d8ea2a5fdabd00303fbe404b8f84d947", size = 29355366, upload-time = "2026-04-22T16:29:13.318Z" }, + { url = "https://files.pythonhosted.org/packages/2a/04/0730e66aa3dc9c61bc2b1f59abdb274188bc1ae1cafc01705e3724ccead1/ibm_db-3.2.9-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5fa430ce90eda04f2b1326a84817f09ad6a9f16d475afacb43522debf4283300", size = 24170941, upload-time = "2026-04-22T16:29:16.366Z" }, +] + +[[package]] +name = "ibm-db-sa" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ibm-db" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/9d/2d6a1ba44af4a6ee411a61e18dbaa735f5d63880e159b877658b17ba4ea3/ibm_db_sa-0.4.4.tar.gz", hash = "sha256:f2567853fd095821964d6a6880e54fddee249d68cc835a3fd0c7d1cdd6a01055", size = 45207, upload-time = "2026-03-18T06:49:20.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/6a/1fdabb124aa5a045e38ad68fec876b27537b50e85cf7603dc2f4778c6bfb/ibm_db_sa-0.4.4-py3-none-any.whl", hash = "sha256:db0464d87d39a6a3ec8716515ec1dbb7f4620596295e433be53375cdeab9d631", size = 42731, upload-time = "2026-03-18T06:49:19.742Z" }, +] + +[[package]] +name = "id" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[package]] +name = "influxdb" +version = "5.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/d4/4c1bd3a8f85403fad3137a7e44f7882b0366586b7c27d12713493516f1c7/influxdb-5.3.2.tar.gz", hash = "sha256:58c647f6043712dd86e9aee12eb4ccfbbb5415467bc9910a48aa8c74c1108970", size = 70792, upload-time = "2024-04-18T21:45:09.163Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/00/d0ea35f823c46a5cd4fa80d933176d251573601d920737ce8a85df7664db/influxdb-5.3.2-py2.py3-none-any.whl", hash = "sha256:00d86b18a968d011b2eee39ec3b2ae941b1dcf7086bc7211e675914623caffcd", size = 79391, upload-time = "2024-04-18T21:45:06.471Z" }, +] + +[[package]] +name = "influxdb-client" +version = "1.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "python-dateutil" }, + { name = "reactivex" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/23/77a945465a556a8067917ba84bac04e312cee2a4fe03d41198fe79ed9c84/influxdb_client-1.50.0.tar.gz", hash = "sha256:c2a4906573097103fa6f9a2ab08efe2eb48c2fed60b8129a3e320affde445743", size = 386749, upload-time = "2026-01-23T09:39:39.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/ec/6b120b4a86f6fadc7ddb1d7c7cdcb15bcfb332deb022ab60df51bcd4494c/influxdb_client-1.50.0-py3-none-any.whl", hash = "sha256:f172975cf7f0c95bfe74f288b31273393b164d2c58a948de55497d9956ab49be", size = 746289, upload-time = "2026-01-23T09:39:37.377Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "invoke" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/227c48c5fe47fa178ccf1fda8f047d16c97ba926567b661e9ce2045c600c/invoke-3.0.3.tar.gz", hash = "sha256:437b6a622223824380bfb4e64f612711a6b648c795f565efc8625af66fb57f0c", size = 343419, upload-time = "2026-04-07T15:17:48.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/de/bbc12563bbf979618d17625a4e753ff7a078523e28d870d3626daa97261a/invoke-3.0.3-py3-none-any.whl", hash = "sha256:f11327165e5cbb89b2ad1d88d3292b5113332c43b8553b494da435d6ec6f5053", size = 160958, upload-time = "2026-04-07T15:17:46.875Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/cf/ea4ef2920830dea3f5ab2ea4da6fb67724e6dca80ee2553788c3607243d0/jaraco_functools-4.5.0.tar.gz", hash = "sha256:3bb5665ea4a020cf78a7040e89154c77edadb3ca74f366479669c5999aa70b03", size = 20272, upload-time = "2026-05-15T21:34:10.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/9a/982e48afcffcd727a9144506720ffd4224b6b7e355c98641866f38b7c043/jaraco_functools-4.5.0-py3-none-any.whl", hash = "sha256:79ce39246eddbde4b3a03b77ea5f0f7878dc669b166a66cf3fa8e266aa3fa2f4", size = 10594, upload-time = "2026-05-15T21:34:08.595Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "joserfc" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/c3/2f590052b55cbdd0ace470ee7ee1f685f6882051be93a9374891005623e2/joserfc-1.7.0.tar.gz", hash = "sha256:4aced6ab0c47846f0a531402aec2419a874b91e918df9c4c9da8a82fb559d6c4", size = 232967, upload-time = "2026-06-02T09:59:34.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/83/b6b62a66a06ce872d9429a5eb5ee20b2002fd9c331b953c94381c1f7c9f9/joserfc-1.7.0-py3-none-any.whl", hash = "sha256:17e5d7a5a35e65442b05efc435a3d5d46696ffa2c8a2ed0eea6f63fc268e3224", size = 70387, upload-time = "2026-06-02T09:59:33.264Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jwcrypto" +version = "1.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/90/f065668004d22715c1940d6e88e4c3afc8ee16d5664e4478d2c8fd23a250/jwcrypto-1.5.7.tar.gz", hash = "sha256:70204d7cca406eda8c82352e3c41ba2d946610dafd19e54403f0a1f4f18633c6", size = 89535, upload-time = "2026-04-07T00:35:36.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/24/fb7da4d6613de7001feaf540d4b5969c6b5a1c42839043b0196cb13aa057/jwcrypto-1.5.7-py3-none-any.whl", hash = "sha256:729463fefe28b6de5cf1ebfda3e94f1a1b41d2799148ef98a01cb9678ebe2bb0", size = 94799, upload-time = "2026-04-07T00:35:35.085Z" }, +] + +[[package]] +name = "kafka-python-ng" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/04/1d65bdf3f0103a08710e226b851de4b357ac702f1cadabf6128bab7518a7/kafka_python_ng-2.2.3.tar.gz", hash = "sha256:f79f28e10ade9b5a9860b2ec15b7cc8dc510d5702f5a399430478cff5f93a05a", size = 330644, upload-time = "2024-10-02T16:56:58.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/61/22e778f642465a157c449782300d8817ebbc106794a8a7ebe88cbb846b05/kafka_python_ng-2.2.3-py2.py3-none-any.whl", hash = "sha256:adc6e82147c441ca4ae1f22e291fc08efab0d10971cbd4aa1481d2ffa38e9480", size = 232824, upload-time = "2024-10-02T16:56:56.932Z" }, +] + +[[package]] +name = "keyring" +version = "25.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, +] + +[[package]] +name = "kubernetes" +version = "36.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "certifi" }, + { name = "durationpy" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "six" }, + { name = "urllib3" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/57/b07b96353f902aa1bdbe00e878e3a12a137977d03a962479785576aa8ec9/kubernetes-36.0.3.tar.gz", hash = "sha256:36993ed25ce59b789c9341473a228fcf268504a2fec7c2b2b1531d73072e5ce7", size = 2337528, upload-time = "2026-07-13T20:38:12.128Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/30/a96d47df739689ac0001ade0afefc16e3b477fc2fb426b568515fdc8afce/kubernetes-36.0.3-py2.py3-none-any.whl", hash = "sha256:8fde9241c4b298e6374a069dcf728359b4e72c2fb29489a975ba4e1c047cf10f", size = 4618066, upload-time = "2026-07-13T20:38:10.172Z" }, +] + +[[package]] +name = "librt" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/2f/ec5241c38e7fa0fe6c26bfc450e78b9489a6c3c08b394b85d2c10e506975/librt-0.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34e47058fcc69a313293d6dee94216a4f30c929ae6f2476e58c5ba635aa639d5", size = 148654, upload-time = "2026-07-08T12:24:30.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1a/d651e18d3ee7aa2879322368c4f278bb7ecaa6b90caadfdec4ebfa8389f3/librt-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbdd5b6509d0c2a8fe72cf494c299a61dbd58142a90a4190664ae159e4a7b547", size = 153537, upload-time = "2026-07-08T12:24:31.773Z" }, + { url = "https://files.pythonhosted.org/packages/45/18/10bff2122577246009d9619b6569596daf69b7648812f997ca9ca0426f60/librt-0.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e56ea4ee4df77585a6b5c138f6538680886024fa559f5b55bd14b12e98e67b2", size = 494336, upload-time = "2026-07-08T12:24:33.079Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/87dfee871b852970f137fdeae8e2ca356c5ab38e6f21d2a3299535fc3159/librt-0.13.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f1f9cc4d09a46d9cb3c2063ae100629d3f52a6517c3c08c2f4c9828261883929", size = 485393, upload-time = "2026-07-08T12:24:34.324Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d5/625447a8c0441ff5f15f4ac5e1d323fb9d4d256ebfde7a3c8e003f646057/librt-0.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f125f5d46b20f89dc5587a55cc416b4ba2a5b2ffda36d048ee120e17598a653a", size = 515382, upload-time = "2026-07-08T12:24:35.575Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d8/1c8c49ea04235960426444deece9092a6b3a9587a850a81bae2335317411/librt-0.13.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2608d3b39f9e0b4a66a130d9150c615cba40a5090d25eeeaa225e0e46de8c0ac", size = 509483, upload-time = "2026-07-08T12:24:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/6f/65/f1760fc48050e215201a03506c32b7270159088d01f64557b53e39e74a45/librt-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9fd35e95ab5e45c3901d37110263c7db85a961110f5460588fe37f8c131f88a7", size = 532503, upload-time = "2026-07-08T12:24:38.203Z" }, + { url = "https://files.pythonhosted.org/packages/18/1b/793e281dcf494879eff99f642b63ebc9c7c58694a1c2d1e93362a22c7041/librt-0.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f31b0aa13c9b04370d4da6be1ab7779776b3a075cceb6747a39a4be85fe1e40", size = 537027, upload-time = "2026-07-08T12:24:39.34Z" }, + { url = "https://files.pythonhosted.org/packages/69/45/0801bbb40c9eea795d3dd3ce91c4c5f3fe7d42d23ec4be3e8cb283bcc754/librt-0.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0b795f5fc70fbbb787ceaf79bb3a0d627bcc33c53de51741755263ec406b775a", size = 517100, upload-time = "2026-07-08T12:24:40.907Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6c/eb5f514f8e29d4924bc0ff4601dd7b4175557e182e7c0721e84cffa39b8a/librt-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36b306a623aaad96fe4b378692b54f9c0789fccd833b9851753d5fbf6138cfde", size = 558653, upload-time = "2026-07-08T12:24:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/f140100d1b59fe87ff40b5ecbb4e27924335b189a784e230ee465452f6c2/librt-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a3762e75fcac8c9e4dacaaf438bffd9003e2ca2c531b756f3c0035deefa674c8", size = 104402, upload-time = "2026-07-08T12:24:43.668Z" }, + { url = "https://files.pythonhosted.org/packages/22/7c/57e40fef7cfb61869341cb28bdcefe8a950bebcbecca74a397bae14dce4a/librt-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:d63bae12a8aeb51380be3438e4dc4bd27354d0f8e19166b2f44e3e94d6f552dc", size = 125002, upload-time = "2026-07-08T12:24:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/a6498964cfeec270c468cffdc118f69c29b412593610d55fa1327ca51ff4/librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", size = 148029, upload-time = "2026-07-08T12:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", size = 153036, upload-time = "2026-07-08T12:24:47.257Z" }, + { url = "https://files.pythonhosted.org/packages/29/3f/b923826660f02f286186cd9303d52bb05ced0a13708edc104dc8480920e3/librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", size = 493062, upload-time = "2026-07-08T12:24:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/6c0980a9c9b1302cb68d108906697b89eceb55889bb1dcf77c109aa56ca5/librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", size = 485510, upload-time = "2026-07-08T12:24:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/32/81/795ae3b9df5dd94079fb807e38191855e023e8c6249014ae6bc3f0d9a490/librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", size = 515909, upload-time = "2026-07-08T12:24:51.135Z" }, + { url = "https://files.pythonhosted.org/packages/20/e5/182de15abce8907108a6fdb41487de65beb5099b74dc5841b19b099168db/librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", size = 508620, upload-time = "2026-07-08T12:24:52.358Z" }, + { url = "https://files.pythonhosted.org/packages/32/03/33978d32db76e1f66377e8f78e42a2ca3c162143331677d1f50bbad36cfb/librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", size = 530363, upload-time = "2026-07-08T12:24:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f5/b291fbd2d00f7d8287bcbf67b5aa0c6afed4bc26cef23e079629c47a2c04/librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", size = 534209, upload-time = "2026-07-08T12:24:55.138Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/6f41f17939d191bc21609f220da8509316bc62797f078545fe83be522e78/librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", size = 514254, upload-time = "2026-07-08T12:24:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/af/c2/2e4befa5410a7443019c14abccc94ff619797171f6b72013635fb87f31d7/librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", size = 557611, upload-time = "2026-07-08T12:24:57.561Z" }, + { url = "https://files.pythonhosted.org/packages/ab/54/8b69f81448417adbc040a2185f4e2eece1e1994b7dcfaeed4662b30f98a5/librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21", size = 104906, upload-time = "2026-07-08T12:24:58.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", size = 125852, upload-time = "2026-07-08T12:25:00.065Z" }, + { url = "https://files.pythonhosted.org/packages/f2/99/bf1820e6feeabc2f218c24450ec0c995d6a91e8ba0fd3caf042c9e8adb2a/librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", size = 111832, upload-time = "2026-07-08T12:25:01.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" }, + { url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" }, + { url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" }, + { url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" }, + { url = "https://files.pythonhosted.org/packages/67/3b/18e7b63255297a2bdc9c25c8d6d4ca8eca9f63aceb1252c0f7427ac7099e/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", size = 151027, upload-time = "2026-07-08T12:25:19.638Z" }, + { url = "https://files.pythonhosted.org/packages/4d/68/e2248452c00d1a03b45fee1752cdc8f790a476efd2402b75181da88a9e61/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", size = 155152, upload-time = "2026-07-08T12:25:20.851Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/52b1c99bf19057a062aac39c900cbb81499f6f75d6c537c14463d247ba78/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", size = 502499, upload-time = "2026-07-08T12:25:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/9f/54/b811151805c795f55e0dedee6ec687b75f9982a8105d240ea3910737a77b/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", size = 496108, upload-time = "2026-07-08T12:25:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/094d6b2bd93f3fdaa54db54cc788c4a365333bddad65ab02e04da0b1d004/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", size = 531576, upload-time = "2026-07-08T12:25:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/2e/40/541733d5755824f968f7ec39d78ffbd75d145964157ae5e69a09ec6d7326/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", size = 524390, upload-time = "2026-07-08T12:25:25.898Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b5/255673cfdbf5ba663339d36cd863c897289ab4337577e19f9405ce059f36/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", size = 543053, upload-time = "2026-07-08T12:25:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/9e/11/ab5005e9c9850710f21e354201bf090646349d3fabf5f951eaf70235729e/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", size = 546387, upload-time = "2026-07-08T12:25:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/a2/04/a5d7ce1d1df1afd15ca283dcdf7530ac073e12d69ae8c40879dda96f7868/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", size = 535970, upload-time = "2026-07-08T12:25:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/927e267a6daa290174ac281b23c9804c8829b042ade9c6f24a065f540958/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", size = 573582, upload-time = "2026-07-08T12:25:31.507Z" }, + { url = "https://files.pythonhosted.org/packages/10/24/b6c5213efe39c19f9e13605644d0cf063b4ddaa33ac2e45b088e23a70e2e/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", size = 82189, upload-time = "2026-07-08T12:25:32.675Z" }, + { url = "https://files.pythonhosted.org/packages/4c/00/d29736be177a906ac0b84a5b04b4fbfa22c776dc2f366de4172b0f968c08/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", size = 106193, upload-time = "2026-07-08T12:25:33.692Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ac/aff6fb45393cb8912f39dfb156ef6b2d1cadb207ff465fc8f66141054be8/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", size = 126962, upload-time = "2026-07-08T12:25:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3a/d68cb2b334d53fd30fac81d3a489ce4ba0d9506f4df43fcf676b68352b19/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", size = 112127, upload-time = "2026-07-08T12:25:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/7b/66/f49ae0d592bd45b6941e9a8bafcb6a87cddcd501ee7874707e767f01b585/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", size = 149818, upload-time = "2026-07-08T12:25:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/51c76d74014d04fb95b6506d286808984b78a2f7a41039094e6b2194ac48/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", size = 154071, upload-time = "2026-07-08T12:25:39.399Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/f19b0f5f82d5a1f2da736586bc840abd00ce07d6388136ae80b7333883fc/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", size = 494168, upload-time = "2026-07-08T12:25:40.641Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/b8550c75775127fd31a5f20e8775997f7b527ad661fc8ddccd7497c064f7/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", size = 491054, upload-time = "2026-07-08T12:25:41.905Z" }, + { url = "https://files.pythonhosted.org/packages/30/14/4d0204867623df3f33f86efd3d3692ba5e01321443f4d6eab35a22697618/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", size = 523006, upload-time = "2026-07-08T12:25:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/19/0a/c45fc9a260934696bace1ac5df1e148ac92bd71767aee3bf7cd7a4534f4c/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", size = 515058, upload-time = "2026-07-08T12:25:44.541Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/50c5ce45b326854ef8fa6ae4c36cf5142e5c55315eaf9e51d0ae73ac4da3/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", size = 534025, upload-time = "2026-07-08T12:25:45.825Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/08c413c8f93fc13b8103624fce38e5caa86cd08cbbc8465870ab287af54b/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", size = 540557, upload-time = "2026-07-08T12:25:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/93af71fb4a364952210051811dd4e40174e79656b050c89cacac18af3330/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", size = 523201, upload-time = "2026-07-08T12:25:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/6e/9766f07b676a4889d9f8bc2864e9ba5fff165653143ef4dda7df6aa34d16/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", size = 565740, upload-time = "2026-07-08T12:25:49.678Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1e/664e3472ce2b6e10e9b83f29d4a36eb982ff6b5a169ae7567bba3a4c4ff5/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", size = 81611, upload-time = "2026-07-08T12:25:50.857Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/8582a4d65e2234673685e07309d02c230b28a85724eb0acbf13f019b7f6e/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", size = 100106, upload-time = "2026-07-08T12:25:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/63/ce/0cb99efe6086b46cd985dc26672166fae312a239690e75871f7fafbd3fc5/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", size = 121209, upload-time = "2026-07-08T12:25:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/26/85/4f3ccb083a3c9b0d42e223acdb3c3f507953324a59cdcab4826e8e2e3b89/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", size = 106404, upload-time = "2026-07-08T12:25:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/b2/77/333191499538c8e8189de7a4cba8e6f49ee949fd6d6e6324b21fd1522466/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", size = 159231, upload-time = "2026-07-08T12:25:55.432Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9e/2aa83758f22c278b837a1d8025898434ce2b8bff36678d5330ecaef56dff/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", size = 161300, upload-time = "2026-07-08T12:25:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c0/86791e936553ca763d6b3c2fb4d31d596cd00e14fa631c283a40ba01559a/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", size = 582056, upload-time = "2026-07-08T12:25:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d3/a9ec15984a185e000c4d2a16ba28bd623124ad4c38a10974c7ff78e3a893/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", size = 562758, upload-time = "2026-07-08T12:25:59.544Z" }, + { url = "https://files.pythonhosted.org/packages/3c/af/dbe36b78b19c06a55097f99305e4ea9458e2273e6ae16a3cbecaad7ee978/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", size = 602095, upload-time = "2026-07-08T12:26:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a8/2966891b4dd2830f5203fbee92ac2c4947653a2390ba73dfa44244fad025/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", size = 593452, upload-time = "2026-07-08T12:26:02.352Z" }, + { url = "https://files.pythonhosted.org/packages/61/f5/4df8bfc8405ecf8c0d525b4d69636f694bdd8620b313ec8b76e54a5926cc/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", size = 623729, upload-time = "2026-07-08T12:26:04.294Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/9ac202dffc8db06f75d06c08c2f9f6ff054be67d21272dcc078fa1cc0c57/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", size = 617077, upload-time = "2026-07-08T12:26:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/ebe38610716aee5cb28efd95089bb90192096179802779381e1c5dcf239c/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", size = 599561, upload-time = "2026-07-08T12:26:07.21Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5c/c2e72e236fff7abc716d5b1753b8b8cd3ea85ac46fe17d2e7c51d4e1c723/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", size = 645511, upload-time = "2026-07-08T12:26:08.562Z" }, + { url = "https://files.pythonhosted.org/packages/0c/99/6203ce619dee940d6bfbe099ec3fe4be00a68e9d60f70abf906cf124fe66/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", size = 104357, upload-time = "2026-07-08T12:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/52/dd/843b6314087c41657c7036d7914d8f294bdf9b580aa8513ea0588c8e9a3d/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", size = 126998, upload-time = "2026-07-08T12:26:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/3dcec2884ba1b0806d1408612555c38dd5d68e90156b59f75f6e36435c3a/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", size = 110771, upload-time = "2026-07-08T12:26:12.303Z" }, +] + +[[package]] +name = "lz4" +version = "4.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/51/f1b86d93029f418033dddf9b9f79c8d2641e7454080478ee2aab5123173e/lz4-4.4.5.tar.gz", hash = "sha256:5f0b9e53c1e82e88c10d7c180069363980136b9d7a8306c4dca4f760d60c39f0", size = 172886, upload-time = "2025-11-03T13:02:36.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/45/2466d73d79e3940cad4b26761f356f19fd33f4409c96f100e01a5c566909/lz4-4.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d221fa421b389ab2345640a508db57da36947a437dfe31aeddb8d5c7b646c22d", size = 207396, upload-time = "2025-11-03T13:01:24.965Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/7da96077a7e8918a5a57a25f1254edaf76aefb457666fcc1066deeecd609/lz4-4.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dc1e1e2dbd872f8fae529acd5e4839efd0b141eaa8ae7ce835a9fe80fbad89f", size = 207154, upload-time = "2025-11-03T13:01:26.922Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/0fb54f84fd1890d4af5bc0a3c1fa69678451c1a6bd40de26ec0561bb4ec5/lz4-4.4.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e928ec2d84dc8d13285b4a9288fd6246c5cde4f5f935b479f50d986911f085e3", size = 1291053, upload-time = "2025-11-03T13:01:28.396Z" }, + { url = "https://files.pythonhosted.org/packages/15/45/8ce01cc2715a19c9e72b0e423262072c17d581a8da56e0bd4550f3d76a79/lz4-4.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daffa4807ef54b927451208f5f85750c545a4abbff03d740835fc444cd97f758", size = 1278586, upload-time = "2025-11-03T13:01:29.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/34/7be9b09015e18510a09b8d76c304d505a7cbc66b775ec0b8f61442316818/lz4-4.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a2b7504d2dffed3fd19d4085fe1cc30cf221263fd01030819bdd8d2bb101cf1", size = 1367315, upload-time = "2025-11-03T13:01:31.054Z" }, + { url = "https://files.pythonhosted.org/packages/2a/94/52cc3ec0d41e8d68c985ec3b2d33631f281d8b748fb44955bc0384c2627b/lz4-4.4.5-cp310-cp310-win32.whl", hash = "sha256:0846e6e78f374156ccf21c631de80967e03cc3c01c373c665789dc0c5431e7fc", size = 88173, upload-time = "2025-11-03T13:01:32.643Z" }, + { url = "https://files.pythonhosted.org/packages/ca/35/c3c0bdc409f551404355aeeabc8da343577d0e53592368062e371a3620e1/lz4-4.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:7c4e7c44b6a31de77d4dc9772b7d2561937c9588a734681f70ec547cfbc51ecd", size = 99492, upload-time = "2025-11-03T13:01:33.813Z" }, + { url = "https://files.pythonhosted.org/packages/1d/02/4d88de2f1e97f9d05fd3d278fe412b08969bc94ff34942f5a3f09318144a/lz4-4.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:15551280f5656d2206b9b43262799c89b25a25460416ec554075a8dc568e4397", size = 91280, upload-time = "2025-11-03T13:01:35.081Z" }, + { url = "https://files.pythonhosted.org/packages/93/5b/6edcd23319d9e28b1bedf32768c3d1fd56eed8223960a2c47dacd2cec2af/lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6da84a26b3aa5da13a62e4b89ab36a396e9327de8cd48b436a3467077f8ccd4", size = 207391, upload-time = "2025-11-03T13:01:36.644Z" }, + { url = "https://files.pythonhosted.org/packages/34/36/5f9b772e85b3d5769367a79973b8030afad0d6b724444083bad09becd66f/lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61d0ee03e6c616f4a8b69987d03d514e8896c8b1b7cc7598ad029e5c6aedfd43", size = 207146, upload-time = "2025-11-03T13:01:37.928Z" }, + { url = "https://files.pythonhosted.org/packages/04/f4/f66da5647c0d72592081a37c8775feacc3d14d2625bbdaabd6307c274565/lz4-4.4.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:33dd86cea8375d8e5dd001e41f321d0a4b1eb7985f39be1b6a4f466cd480b8a7", size = 1292623, upload-time = "2025-11-03T13:01:39.341Z" }, + { url = "https://files.pythonhosted.org/packages/85/fc/5df0f17467cdda0cad464a9197a447027879197761b55faad7ca29c29a04/lz4-4.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:609a69c68e7cfcfa9d894dc06be13f2e00761485b62df4e2472f1b66f7b405fb", size = 1279982, upload-time = "2025-11-03T13:01:40.816Z" }, + { url = "https://files.pythonhosted.org/packages/25/3b/b55cb577aa148ed4e383e9700c36f70b651cd434e1c07568f0a86c9d5fbb/lz4-4.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75419bb1a559af00250b8f1360d508444e80ed4b26d9d40ec5b09fe7875cb989", size = 1368674, upload-time = "2025-11-03T13:01:42.118Z" }, + { url = "https://files.pythonhosted.org/packages/fb/31/e97e8c74c59ea479598e5c55cbe0b1334f03ee74ca97726e872944ed42df/lz4-4.4.5-cp311-cp311-win32.whl", hash = "sha256:12233624f1bc2cebc414f9efb3113a03e89acce3ab6f72035577bc61b270d24d", size = 88168, upload-time = "2025-11-03T13:01:43.282Z" }, + { url = "https://files.pythonhosted.org/packages/18/47/715865a6c7071f417bef9b57c8644f29cb7a55b77742bd5d93a609274e7e/lz4-4.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:8a842ead8ca7c0ee2f396ca5d878c4c40439a527ebad2b996b0444f0074ed004", size = 99491, upload-time = "2025-11-03T13:01:44.167Z" }, + { url = "https://files.pythonhosted.org/packages/14/e7/ac120c2ca8caec5c945e6356ada2aa5cfabd83a01e3170f264a5c42c8231/lz4-4.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:83bc23ef65b6ae44f3287c38cbf82c269e2e96a26e560aa551735883388dcc4b", size = 91271, upload-time = "2025-11-03T13:01:45.016Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ac/016e4f6de37d806f7cc8f13add0a46c9a7cfc41a5ddc2bc831d7954cf1ce/lz4-4.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5aa4cead2044bab83e0ebae56e0944cc7fcc1505c7787e9e1057d6d549897e", size = 207163, upload-time = "2025-11-03T13:01:45.895Z" }, + { url = "https://files.pythonhosted.org/packages/8d/df/0fadac6e5bd31b6f34a1a8dbd4db6a7606e70715387c27368586455b7fc9/lz4-4.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d0bf51e7745484d2092b3a51ae6eb58c3bd3ce0300cf2b2c14f76c536d5697a", size = 207150, upload-time = "2025-11-03T13:01:47.205Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/34e36cc49bb16ca73fb57fbd4c5eaa61760c6b64bce91fcb4e0f4a97f852/lz4-4.4.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7b62f94b523c251cf32aa4ab555f14d39bd1a9df385b72443fd76d7c7fb051f5", size = 1292045, upload-time = "2025-11-03T13:01:48.667Z" }, + { url = "https://files.pythonhosted.org/packages/90/1c/b1d8e3741e9fc89ed3b5f7ef5f22586c07ed6bb04e8343c2e98f0fa7ff04/lz4-4.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c3ea562c3af274264444819ae9b14dbbf1ab070aff214a05e97db6896c7597e", size = 1279546, upload-time = "2025-11-03T13:01:50.159Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/e3867222474f6c1b76e89f3bd914595af69f55bf2c1866e984c548afdc15/lz4-4.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24092635f47538b392c4eaeff14c7270d2c8e806bf4be2a6446a378591c5e69e", size = 1368249, upload-time = "2025-11-03T13:01:51.273Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e7/d667d337367686311c38b580d1ca3d5a23a6617e129f26becd4f5dc458df/lz4-4.4.5-cp312-cp312-win32.whl", hash = "sha256:214e37cfe270948ea7eb777229e211c601a3e0875541c1035ab408fbceaddf50", size = 88189, upload-time = "2025-11-03T13:01:52.605Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0b/a54cd7406995ab097fceb907c7eb13a6ddd49e0b231e448f1a81a50af65c/lz4-4.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:713a777de88a73425cf08eb11f742cd2c98628e79a8673d6a52e3c5f0c116f33", size = 99497, upload-time = "2025-11-03T13:01:53.477Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7e/dc28a952e4bfa32ca16fa2eb026e7a6ce5d1411fcd5986cd08c74ec187b9/lz4-4.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:a88cbb729cc333334ccfb52f070463c21560fca63afcf636a9f160a55fac3301", size = 91279, upload-time = "2025-11-03T13:01:54.419Z" }, + { url = "https://files.pythonhosted.org/packages/2f/46/08fd8ef19b782f301d56a9ccfd7dafec5fd4fc1a9f017cf22a1accb585d7/lz4-4.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6bb05416444fafea170b07181bc70640975ecc2a8c92b3b658c554119519716c", size = 207171, upload-time = "2025-11-03T13:01:56.595Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3f/ea3334e59de30871d773963997ecdba96c4584c5f8007fd83cfc8f1ee935/lz4-4.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b424df1076e40d4e884cfcc4c77d815368b7fb9ebcd7e634f937725cd9a8a72a", size = 207163, upload-time = "2025-11-03T13:01:57.721Z" }, + { url = "https://files.pythonhosted.org/packages/41/7b/7b3a2a0feb998969f4793c650bb16eff5b06e80d1f7bff867feb332f2af2/lz4-4.4.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:216ca0c6c90719731c64f41cfbd6f27a736d7e50a10b70fad2a9c9b262ec923d", size = 1292136, upload-time = "2025-11-03T13:02:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/89/d1/f1d259352227bb1c185288dd694121ea303e43404aa77560b879c90e7073/lz4-4.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:533298d208b58b651662dd972f52d807d48915176e5b032fb4f8c3b6f5fe535c", size = 1279639, upload-time = "2025-11-03T13:02:01.649Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fb/ba9256c48266a09012ed1d9b0253b9aa4fe9cdff094f8febf5b26a4aa2a2/lz4-4.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:451039b609b9a88a934800b5fc6ee401c89ad9c175abf2f4d9f8b2e4ef1afc64", size = 1368257, upload-time = "2025-11-03T13:02:03.35Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6d/dee32a9430c8b0e01bbb4537573cabd00555827f1a0a42d4e24ca803935c/lz4-4.4.5-cp313-cp313-win32.whl", hash = "sha256:a5f197ffa6fc0e93207b0af71b302e0a2f6f29982e5de0fbda61606dd3a55832", size = 88191, upload-time = "2025-11-03T13:02:04.406Z" }, + { url = "https://files.pythonhosted.org/packages/18/e0/f06028aea741bbecb2a7e9648f4643235279a770c7ffaf70bd4860c73661/lz4-4.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:da68497f78953017deb20edff0dba95641cc86e7423dfadf7c0264e1ac60dc22", size = 99502, upload-time = "2025-11-03T13:02:05.886Z" }, + { url = "https://files.pythonhosted.org/packages/61/72/5bef44afb303e56078676b9f2486f13173a3c1e7f17eaac1793538174817/lz4-4.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:c1cfa663468a189dab510ab231aad030970593f997746d7a324d40104db0d0a9", size = 91285, upload-time = "2025-11-03T13:02:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/6a5c2952971af73f15ed4ebfdd69774b454bd0dc905b289082ca8664fba1/lz4-4.4.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67531da3b62f49c939e09d56492baf397175ff39926d0bd5bd2d191ac2bff95f", size = 207348, upload-time = "2025-11-03T13:02:08.117Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d7/fd62cbdbdccc35341e83aabdb3f6d5c19be2687d0a4eaf6457ddf53bba64/lz4-4.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a1acbbba9edbcbb982bc2cac5e7108f0f553aebac1040fbec67a011a45afa1ba", size = 207340, upload-time = "2025-11-03T13:02:09.152Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/225ffadaacb4b0e0eb5fd263541edd938f16cd21fe1eae3cd6d5b6a259dc/lz4-4.4.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a482eecc0b7829c89b498fda883dbd50e98153a116de612ee7c111c8bcf82d1d", size = 1293398, upload-time = "2025-11-03T13:02:10.272Z" }, + { url = "https://files.pythonhosted.org/packages/c6/9e/2ce59ba4a21ea5dc43460cba6f34584e187328019abc0e66698f2b66c881/lz4-4.4.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e099ddfaa88f59dd8d36c8a3c66bd982b4984edf127eb18e30bb49bdba68ce67", size = 1281209, upload-time = "2025-11-03T13:02:12.091Z" }, + { url = "https://files.pythonhosted.org/packages/80/4f/4d946bd1624ec229b386a3bc8e7a85fa9a963d67d0a62043f0af0978d3da/lz4-4.4.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2af2897333b421360fdcce895c6f6281dc3fab018d19d341cf64d043fc8d90d", size = 1369406, upload-time = "2025-11-03T13:02:13.683Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/d429ba4720a9064722698b4b754fb93e42e625f1318b8fe834086c7c783b/lz4-4.4.5-cp313-cp313t-win32.whl", hash = "sha256:66c5de72bf4988e1b284ebdd6524c4bead2c507a2d7f172201572bac6f593901", size = 88325, upload-time = "2025-11-03T13:02:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/4b/85/7ba10c9b97c06af6c8f7032ec942ff127558863df52d866019ce9d2425cf/lz4-4.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:cdd4bdcbaf35056086d910d219106f6a04e1ab0daa40ec0eeef1626c27d0fddb", size = 99643, upload-time = "2025-11-03T13:02:15.978Z" }, + { url = "https://files.pythonhosted.org/packages/77/4d/a175459fb29f909e13e57c8f475181ad8085d8d7869bd8ad99033e3ee5fa/lz4-4.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:28ccaeb7c5222454cd5f60fcd152564205bcb801bd80e125949d2dfbadc76bbd", size = 91504, upload-time = "2025-11-03T13:02:17.313Z" }, + { url = "https://files.pythonhosted.org/packages/63/9c/70bdbdb9f54053a308b200b4678afd13efd0eafb6ddcbb7f00077213c2e5/lz4-4.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c216b6d5275fc060c6280936bb3bb0e0be6126afb08abccde27eed23dead135f", size = 207586, upload-time = "2025-11-03T13:02:18.263Z" }, + { url = "https://files.pythonhosted.org/packages/b6/cb/bfead8f437741ce51e14b3c7d404e3a1f6b409c440bad9b8f3945d4c40a7/lz4-4.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c8e71b14938082ebaf78144f3b3917ac715f72d14c076f384a4c062df96f9df6", size = 207161, upload-time = "2025-11-03T13:02:19.286Z" }, + { url = "https://files.pythonhosted.org/packages/e7/18/b192b2ce465dfbeabc4fc957ece7a1d34aded0d95a588862f1c8a86ac448/lz4-4.4.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b5e6abca8df9f9bdc5c3085f33ff32cdc86ed04c65e0355506d46a5ac19b6e9", size = 1292415, upload-time = "2025-11-03T13:02:20.829Z" }, + { url = "https://files.pythonhosted.org/packages/67/79/a4e91872ab60f5e89bfad3e996ea7dc74a30f27253faf95865771225ccba/lz4-4.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b84a42da86e8ad8537aabef062e7f661f4a877d1c74d65606c49d835d36d668", size = 1279920, upload-time = "2025-11-03T13:02:22.013Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/d52c7b11eaa286d49dae619c0eec4aabc0bf3cda7a7467eb77c62c4471f3/lz4-4.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bba042ec5a61fa77c7e380351a61cb768277801240249841defd2ff0a10742f", size = 1368661, upload-time = "2025-11-03T13:02:23.208Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/137ddeea14c2cb86864838277b2607d09f8253f152156a07f84e11768a28/lz4-4.4.5-cp314-cp314-win32.whl", hash = "sha256:bd85d118316b53ed73956435bee1997bd06cc66dd2fa74073e3b1322bd520a67", size = 90139, upload-time = "2025-11-03T13:02:24.301Z" }, + { url = "https://files.pythonhosted.org/packages/18/2c/8332080fd293f8337779a440b3a143f85e374311705d243439a3349b81ad/lz4-4.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:92159782a4502858a21e0079d77cdcaade23e8a5d252ddf46b0652604300d7be", size = 101497, upload-time = "2025-11-03T13:02:25.187Z" }, + { url = "https://files.pythonhosted.org/packages/ca/28/2635a8141c9a4f4bc23f5135a92bbcf48d928d8ca094088c962df1879d64/lz4-4.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:d994b87abaa7a88ceb7a37c90f547b8284ff9da694e6afcfaa8568d739faf3f7", size = 93812, upload-time = "2025-11-03T13:02:26.133Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "minio" +version = "7.2.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "certifi" }, + { name = "pycryptodome" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/df/6dfc6540f96a74125a11653cce717603fd5b7d0001a8e847b3e54e72d238/minio-7.2.20.tar.gz", hash = "sha256:95898b7a023fbbfde375985aa77e2cd6a0762268db79cf886f002a9ea8e68598", size = 136113, upload-time = "2025-11-27T00:37:15.569Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-codeinclude-plugin" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/b5/f72df157abc7f85e33ffa417464e9dd535ef5fda7654eda41190047a53b6/mkdocs-codeinclude-plugin-0.2.1.tar.gz", hash = "sha256:305387f67a885f0e36ec1cf977324fe1fe50d31301147194b63631d0864601b1", size = 8140, upload-time = "2023-03-01T19:57:06.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/7b/60573ebf2a22b144eeaf3b29db9a6d4d342d68273f716ea2723d1ad723ba/mkdocs_codeinclude_plugin-0.2.1-py3-none-any.whl", hash = "sha256:172a917c9b257fa62850b669336151f85d3cd40312b2b52520cbcceab557ea6c", size = 8093, upload-time = "2023-03-01T19:57:05.207Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocs-include-markdown-plugin" +version = "7.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/2b/c788fc5c39ccba342eb9067be637327faebbc0e47da41ea79dc2526e693a/mkdocs_include_markdown_plugin-7.3.0.tar.gz", hash = "sha256:2800126746452e31c2e321bbd43c8190b356e0de353e20cbc16a34a3c3d6796c", size = 25527, upload-time = "2026-05-15T18:16:13.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/84/c0109c5991b89cbf028b8597f73fa7bd6a6b092407be2369a354b52737ab/mkdocs_include_markdown_plugin-7.3.0-py3-none-any.whl", hash = "sha256:5b5c99b5d3c9b9ce0114a9e60353bbafb6be53a26c2d3b74ec6b767a7a8e55ca", size = 29675, upload-time = "2026-05-15T18:16:12.654Z" }, +] + +[[package]] +name = "mkdocs-markdownextradata-plugin" +version = "0.2.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mkdocs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/c4/142510a1ea41465f63dc0419748ce693d96d714f4fe06d086fcd11b4435b/mkdocs_markdownextradata_plugin-0.2.6.tar.gz", hash = "sha256:4aed9b43b8bec65b02598387426ca4809099ea5f5aa78bf114f3296fd46686b5", size = 6882, upload-time = "2024-08-22T08:57:10.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/4e/c09876f08fa9faaa5e1178f3d77b7af3f343258689bd6f3b72593b2f74e3/mkdocs_markdownextradata_plugin-0.2.6-py3-none-any.whl", hash = "sha256:34dd40870781784c75809596b2d8d879da783815b075336d541de1f150c94242", size = 6665, upload-time = "2024-08-22T08:57:07.75Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/cd/c05d3a530ba7934f144fb45f7203cd236adc25c7bdcc34673d202f4b0278/mkdocs_material-9.7.7.tar.gz", hash = "sha256:c0649c065b1b0512d60aad8c10f947f8e455284475239b364b610f2deb4d0855", size = 4097923, upload-time = "2026-07-17T16:21:33.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/21/17c1bc9e6f47c972ad66fb2ac2568f99f90f1207eeb6fc3b34d094dba7b5/mkdocs_material-9.7.7-py3-none-any.whl", hash = "sha256:8ea9bb1737a5b524a5f9dcf2e1b4ebda8274ae3008aa7845720a97083bef708f", size = 9305438, upload-time = "2026-07-17T16:21:30.017Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + +[[package]] +name = "msgpack" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/16/f70100614b69feb3ade7285f08c9c52d6cda0a5c03f3f5e2facd63acb211/msgpack-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c7b398c56ff125feae96c2737abfec5595f1fa0aa186df60c56040b8accb95c", size = 82926, upload-time = "2026-06-18T16:12:31.531Z" }, + { url = "https://files.pythonhosted.org/packages/e4/3c/08ecd5cdfe4e2de43aec79062028ad0f7b2d9b1fea5430068c198ba570da/msgpack-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1548006a91aa93c5da81f3bdcebc1a0d10cea2d25969754fbe848da622b2b895", size = 82730, upload-time = "2026-06-18T16:12:32.894Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/a70c9cb1a04ecc134005149367dcfe35d167284e8f65035a1e4156ad17b5/msgpack-1.2.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dabedcd0f23559f3596428c6589c1cd8c6eaed3a0d720795b07b0225d769203", size = 400729, upload-time = "2026-06-18T16:12:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7f/5ce020168cf0439041526e95aa068c722c016aee21624e331aeabeee2e8e/msgpack-1.2.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efa1c898e0fc5380fc0cabbf75164c52e3b5cbb45973710d75821928380c73", size = 407625, upload-time = "2026-06-18T16:12:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/fb7668ce0386819303047057aef6fc1da73b584291d9cff82b821744e2ef/msgpack-1.2.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01e2dd6c9b19d333a00282330cc8a73d38d8dabc306dc5b42cd668c3ac82e833", size = 377891, upload-time = "2026-06-18T16:12:36.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dc/9ebe654a73c3aed2e40aa6b52e3c2a02b5f53ef0085fa235a45d5b367f87/msgpack-1.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:350cb813d0af6e65d2f7ef0d729f7ff5be5a8bce03665892f43e5883d4ecc1b8", size = 391987, upload-time = "2026-06-18T16:12:37.839Z" }, + { url = "https://files.pythonhosted.org/packages/42/eb/b67cf64218a2fa25e1c671fe1d3dbb06cbeb973e71bc4b822da079862d0b/msgpack-1.2.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ee1d9ed27d0497b848923746cf762ed2e7db24f4be7eec8e5cbe8c766aa707b7", size = 374603, upload-time = "2026-06-18T16:12:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2e/9ee200cde32fd1a0101b4006202fde554c1860adfb9bf7bff31ea4c08df8/msgpack-1.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:633727297ed063441fd1cda2288865487f33ad14eeb8831afb5f0c396a62cfce", size = 405121, upload-time = "2026-06-18T16:12:40.524Z" }, + { url = "https://files.pythonhosted.org/packages/43/b6/f10117be7ca7a51e8feed699a907b8e663a8cd66e115ae6b4fb30cc7945c/msgpack-1.2.1-cp310-cp310-win32.whl", hash = "sha256:298872ecf9e61950f1c6af4ca969b859ee91783bb920ef6e6172697d0c8aad74", size = 64088, upload-time = "2026-06-18T16:12:41.762Z" }, + { url = "https://files.pythonhosted.org/packages/ba/93/89976c696fb0224662239d952c47b4d1661b34d79a332ef5584facaa8579/msgpack-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2ff164c1b0bcb740b073b99e945234d0212852fa378e44a208c425379140dbeb", size = 70113, upload-time = "2026-06-18T16:12:42.78Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" }, + { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" }, + { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/e673683c4c6c90c1022b24c65af4b03eda72b182a1176ef6449069d66acc/msgpack-1.2.1-cp311-cp311-win32.whl", hash = "sha256:91054a783328e0ea7954b8771095705c8d2243b814743fbaadf14552c9c52c5d", size = 64091, upload-time = "2026-06-18T16:12:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/ca212739d179f9083bff2c7c08c24101c3555a334fadc2b876b18768a3ae/msgpack-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2eda0b7ebb1283a98d3e4492ac933c8af6aff59fd3df1c3ed024f536af4b1dc8", size = 70462, upload-time = "2026-06-18T16:12:55.898Z" }, + { url = "https://files.pythonhosted.org/packages/6d/be/6798347b425e26f35db82e69dd83c09716c856a3714e7bffc4c0860fd830/msgpack-1.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ee967f7c7e1df2890c671ff2ee51a28ded0efc95da3e507176dee881ce36c66", size = 65059, upload-time = "2026-06-18T16:12:57.053Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" }, + { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" }, + { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a", size = 419989, upload-time = "2026-06-18T16:13:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/85039b7b0eb168aaad7383a23c97e291a11f08351cb45a606ce865e4e3f1/msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6", size = 377544, upload-time = "2026-06-18T16:13:03.637Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/35963899493b32030c85fc513b723ae66144ac70c11ebc52e889e16e3d99/msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a", size = 400842, upload-time = "2026-06-18T16:13:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/8e2ac970c8f99264cd9997d1c73df5466bc19da3301d7dc5500862a9b089/msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1", size = 374108, upload-time = "2026-06-18T16:13:06.232Z" }, + { url = "https://files.pythonhosted.org/packages/17/dd/fa8bd265110dfa51c20cb529f9e6d240a16fafe7e645004c6af2d01353ba/msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64", size = 414939, upload-time = "2026-06-18T16:13:07.478Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b9/8377a5ad8953fc0437c70cc98d9ae29f27fe5ac5109fbec0812085865735/msgpack-1.2.1-cp312-cp312-win32.whl", hash = "sha256:0c0d9802354507bcba62af19c17918e3eb437cc25e6f50657d511b5856a77aac", size = 64504, upload-time = "2026-06-18T16:13:08.822Z" }, + { url = "https://files.pythonhosted.org/packages/57/7f/ce1e377df7e62461fefd9eb23bfb93a4a523f40a517b377b8f844d836828/msgpack-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c24aa15d5963051e1a5c62b12c50cd705992502b5ec1f3bece6046f33c9fc24", size = 71421, upload-time = "2026-06-18T16:13:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/8f/32/ebfe84c9929f08f188d56c7a2fd913406a9ddad76a634697c1c43b8112e6/msgpack-1.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4227224aaec8f7fbcbfbd4272319347b2bb4030366502600f8c45588c5187b07", size = 64775, upload-time = "2026-06-18T16:13:11.056Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/dcddcab6f6c20ecb387ca5e980371cdb3f87ff69aeca388be97eebc4c074/msgpack-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a70e3cf2804a300d921bb0940426e35f4e489a23adfb77a808892241db0a064", size = 83151, upload-time = "2026-06-18T16:13:12.173Z" }, + { url = "https://files.pythonhosted.org/packages/64/71/fbcfa83a1d6a9c6091942d1cfd070962244664b87427a9a49a6897b1b219/msgpack-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:491cc39455ca765fad51fb451bf2915eb2cf41192ab5801ce8d67c1d614fe056", size = 82351, upload-time = "2026-06-18T16:13:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/e3/10/ddf7b06db879e8792d13934ddda09ff20bd2a583fd84c9b59aae9b0e650b/msgpack-1.2.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f310233ef7fb9c14e201c93639fe5f5260b005f56f0b29048e999c30935596cc", size = 407518, upload-time = "2026-06-18T16:13:14.233Z" }, + { url = "https://files.pythonhosted.org/packages/79/d3/36a46a8ed992b781acbc05928bd5bee3c810cb0c3563bf81a7b0c04a1a76/msgpack-1.2.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:787c9bebb5833e8f6fc8abca3c0597683d8d87f56a8842b6b89c75a5f3176e2d", size = 416405, upload-time = "2026-06-18T16:13:15.435Z" }, + { url = "https://files.pythonhosted.org/packages/f9/84/e8e9598b557c0ba6ddae901a73780a4c75ac667dddf59414b1e56a42fb34/msgpack-1.2.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc871b997a9370d855b7394465f2f350e847a5b806dd38dcc9c989e7d87da155", size = 376257, upload-time = "2026-06-18T16:13:17.022Z" }, + { url = "https://files.pythonhosted.org/packages/40/16/738fe6d875ad7e2a9429c165322a4ec088f4f273cdfae63d96a89c467961/msgpack-1.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:85f57e960d877f2977f6430896191b04a21f8901b3b4baf2e4604329f4db5402", size = 397469, upload-time = "2026-06-18T16:13:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/ca/be/6d5952df75a7f24f35833af764c3a6860780364cb3a0030beb8099e1b2b4/msgpack-1.2.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:1233ee2dd0cefba127583de50ea654677277047d238303521db35def3d7b2e7c", size = 372802, upload-time = "2026-06-18T16:13:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/e1/39/e2ef7dbf0473bcb8dc7c50bf782a892d67414877b63e47fc88eb189ef5e6/msgpack-1.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e3dc2feb0876209d9c38aa56cb1de169bd6c4348f1aa48271f241226590993e6", size = 411273, upload-time = "2026-06-18T16:13:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c5/133f4512a56e983a93445c836c9d94d88f3bc2e0980ff4b9e577bd8416ce/msgpack-1.2.1-cp313-cp313-win32.whl", hash = "sha256:6d09badf350af2be9d189184e04e64cf54ad93569ab3d96fca58bd3e84aad707", size = 64471, upload-time = "2026-06-18T16:13:22.293Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/577e10b055096a7dd40732358cabaf7180a20c79ed1dcdbb618e4b9deac7/msgpack-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:33f14fba63278b714efe6ad07e50ea5f03d91537aa6a1c5f1ceca4cf44013ca9", size = 71274, upload-time = "2026-06-18T16:13:23.455Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ee/0c0048e7cfbef23c6a94791b8959ab28155232e7956de8a305b5ff588f05/msgpack-1.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc5febcd4c99effbc02b528e49d6fd0760b2b7d48c05239e345a5fa6e743d9a", size = 64795, upload-time = "2026-06-18T16:13:24.687Z" }, + { url = "https://files.pythonhosted.org/packages/77/58/cce442852c6b9e1639c7c8ac8fd9143121cb32dab0f308df4d1426a8eb9c/msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d", size = 83610, upload-time = "2026-06-18T16:13:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/60/5c/15b4c7a0182f75ffa90751958ba36a9c01cafee367d49a3edc10ed140b01/msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7", size = 83138, upload-time = "2026-06-18T16:13:26.781Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/99e58722feaffc5f2fbcc0c8c0d1451ab9f84097f7af87291b46af2390f4/msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889", size = 406090, upload-time = "2026-06-18T16:13:28.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/03/8c63e8cf52958534ef688625965ab04c269a6cadd8caef16758b380a821a/msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720", size = 412106, upload-time = "2026-06-18T16:13:29.427Z" }, + { url = "https://files.pythonhosted.org/packages/63/d2/155d9e71b40e41fd934bc0c48b9b2770f22263e1ac20aad8e29fdca7be3f/msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190", size = 374851, upload-time = "2026-06-18T16:13:30.631Z" }, + { url = "https://files.pythonhosted.org/packages/98/48/deaf2326262a8d5ea3295ce9649912ecd3f551ba7ec8e33c665d2ba583f3/msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d", size = 396168, upload-time = "2026-06-18T16:13:31.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/2a/b4410f906c2ec0008f1608d3ab5143afc3ad3f4e6da0fed3ea2231d0bef4/msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24", size = 371959, upload-time = "2026-06-18T16:13:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/59/86/1edc67270099a528fa2093ea60fe191233cd238e4bd30cfacf7db79fc959/msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7", size = 408457, upload-time = "2026-06-18T16:13:34.567Z" }, + { url = "https://files.pythonhosted.org/packages/82/90/8b630fef07d8c5ab457b71ff2c217910c83d333c7a68472c186e87cc504a/msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb", size = 65942, upload-time = "2026-06-18T16:13:36.056Z" }, + { url = "https://files.pythonhosted.org/packages/16/f1/467b81e98b24dd3885d7b1857728797b4ffc76a7a7483af4fb321a07de3c/msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b", size = 72627, upload-time = "2026-06-18T16:13:37.079Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1d/5d8c4c89985feb6acefb82a09e501c60392261856d2408d20bfe4f0360b1/msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7", size = 66908, upload-time = "2026-06-18T16:13:38.23Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ad2afb678b4de94496cd432b581759b756a92c1192d8c767edd6b132efdc/msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273", size = 86000, upload-time = "2026-06-18T16:13:39.44Z" }, + { url = "https://files.pythonhosted.org/packages/54/74/0b797484013128837f3b1cbb6cea019277c4de4e377dc512b4d9a0f92940/msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1", size = 86544, upload-time = "2026-06-18T16:13:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b4/b774d7eb95561739907fec675582f83203cf41c597a418c2589b4bfb8e9d/msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc", size = 427661, upload-time = "2026-06-18T16:13:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f9/3243191dc9937e00756c8bc1b0272fed8f23758e43df2a3b46f533e5090f/msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde", size = 426375, upload-time = "2026-06-18T16:13:42.936Z" }, + { url = "https://files.pythonhosted.org/packages/23/c7/1693111db9944ba4ad4b67a1e788400d78a0b6af7a6523dc7e4e58f8274b/msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4", size = 380495, upload-time = "2026-06-18T16:13:44.306Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2b/92f86956a0c13e8662f7e2ad630c4eb4db07497b967589bd5245e018b2c1/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d", size = 410897, upload-time = "2026-06-18T16:13:45.629Z" }, + { url = "https://files.pythonhosted.org/packages/da/ea/1479f72d200313a76fc2f823a79d1e07ed052ab7b8a0280640aa7b95de42/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355", size = 378519, upload-time = "2026-06-18T16:13:46.998Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4d/fa006060ffa1011d32bfae826fe766fe73e02982183601633b7121058ab3/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c", size = 419815, upload-time = "2026-06-18T16:13:48.205Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/aab6c946570496b78e67804721f3d5e2d62a93081b9b37df77764ef56347/msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1", size = 70914, upload-time = "2026-06-18T16:13:49.385Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/e608956488a2af014cfe6e3d665e090b8ee42aa14b07f8f95b8880d66b09/msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2", size = 77999, upload-time = "2026-06-18T16:13:50.467Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8a/27e2e57055176e366a46b85d02d68e7a5bcfbdd8474c9706375d965f24d3/msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107", size = 71160, upload-time = "2026-06-18T16:13:51.498Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "mypy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/09/f2f5f45dae0c9a0891e4751a73312730e009395102e5d72a22a976cca41f/mypy-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fa8d916ac3b705af733c4c1e6c9ebe38fd0d52beb15b105c3e8355b55e6ecdc", size = 14927774, upload-time = "2026-07-13T11:28:38.224Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/345367effd3a6877275a94d481614bfca983f45e028c6290e2cc54603811/mypy-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28e1e2af8cd8fff551fd30f2fe4b03fb76764ac8b1ba6c6a1bd00ad32b412db3", size = 14000127, upload-time = "2026-07-13T11:30:19.57Z" }, + { url = "https://files.pythonhosted.org/packages/99/6c/a10b7a7b9f0a755fb94e27ae834d4cea9ad6c5221f9325eef8f182641feb/mypy-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e77244df3843048c3f927182916730e40c124cbaa43905c1fb86cb382aa0805", size = 14229437, upload-time = "2026-07-13T11:28:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bd/a26a602acb1bbf849fa4bdac4bc657ee2f11c0c2a764a2cc87a5304e865c/mypy-2.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9559ab18a9c9957dfa3004ab57cd4bac5f26a724329a9584e583367f0c2e1117", size = 15171457, upload-time = "2026-07-13T11:29:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/124f462bef69bcbc90b9358088460b6091954a3e004852fcd9948db617a5/mypy-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09abd66d8685e73f8f7d17b847c3e104d9a7b164a8706ea87d6c96a3d45816d5", size = 15478281, upload-time = "2026-07-13T11:32:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/8bdca6a8ac8d856d82ed049144af2721245a135c2e8001d3890c93975852/mypy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e91adad1ca81742ac7ef9893959911df867752206b37135185e88dfb3c89494", size = 11148008, upload-time = "2026-07-13T11:34:17.332Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/490eea348e60ba50decec20bc750605444149a5d7a8cc560042f90ba2c75/mypy-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6f99ec626e3c3a2f7c0b22c5b90ddb5dabb1c18729c971e9bdaca1f1766d2cee", size = 10142329, upload-time = "2026-07-13T11:32:52.116Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b9/d75b3082b05f1b3028828aeb18e74ae5ab0a0936051bbf1f32f59f654747/mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", size = 14838725, upload-time = "2026-07-13T11:32:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/a9/50/79a65c6ea6e115bc73296038a4543b2d5c91f07912b918a2c616a2514bba/mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", size = 13911128, upload-time = "2026-07-13T11:32:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/90/48/e11ed7716c26953ca321f726e452e374dbf81a6f2b8b212ec02af29b6b8f/mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", size = 14146742, upload-time = "2026-07-13T11:33:03.313Z" }, + { url = "https://files.pythonhosted.org/packages/06/72/6807565b1c4861ef66f7fdd98b51c61556356eab80235717b46c53bb8627/mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", size = 15081418, upload-time = "2026-07-13T11:31:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/00/80/1ea14c5d80e589e415973db3e47c78c2219a305b808b2b506395342c1d79/mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", size = 15328164, upload-time = "2026-07-13T11:31:35.723Z" }, + { url = "https://files.pythonhosted.org/packages/37/28/8223157404a3d51920078459c37f80fbdc590e1d8ea049dc5ce48643022a/mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", size = 11136472, upload-time = "2026-07-13T11:27:37.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cc/ea27e5959c5f258585a756b252031f3b313583d81b5064b2bebc41d3706b/mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", size = 10135800, upload-time = "2026-07-13T11:30:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", size = 15026523, upload-time = "2026-07-13T11:34:49.206Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/1e1731df090a857df2807177a4626863e5ac0f0256513c35780efe53986f/mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c", size = 14032189, upload-time = "2026-07-13T11:33:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/cab921f4a806e171f34113e6181dd23c55358ccf6a80741269ef594a410e/mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", size = 14198696, upload-time = "2026-07-13T11:32:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/e6d008bb19fe446e3662d85e0e2717bf9f2d611a2164fb29d6e067dbf46c/mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", size = 15286904, upload-time = "2026-07-13T11:34:27.594Z" }, + { url = "https://files.pythonhosted.org/packages/db/83/94397c9293608a364aa03e8084fb34ede4ae976a260384b9b52929308135/mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", size = 15528342, upload-time = "2026-07-13T11:34:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/d8b37d819adec6cfccfb1fd3afc1735d94717ddeafb45536db9c6943e09b/mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", size = 11218346, upload-time = "2026-07-13T11:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/cd9f725b19b19e5b530a154cf9bcf9e94279c5d55b3c34fb42b3aa48ea1b/mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", size = 10204525, upload-time = "2026-07-13T11:31:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", size = 14947298, upload-time = "2026-07-13T11:27:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/db3e7af01e7844d21662c6ddc1f7825ec7cb4053f0391ac02faf3638396f/mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", size = 13950768, upload-time = "2026-07-13T11:27:57.726Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", size = 14151586, upload-time = "2026-07-13T11:29:18.615Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c3/f8b2ffc60883084da91be51af58e88a7ffd4ff9795acb7d902ff88d31eb1/mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", size = 15227411, upload-time = "2026-07-13T11:30:29.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/16b917fc7adcf03f1aadddfc93aab804ffb234b1ab09c0ffd6d92a5d34a2/mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", size = 15478790, upload-time = "2026-07-13T11:33:14.686Z" }, + { url = "https://files.pythonhosted.org/packages/c0/88/aaa65a93c73d0cdae7e42f8adb302bf6885bb281302084f99d0290a35347/mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", size = 11234919, upload-time = "2026-07-13T11:33:39.28Z" }, + { url = "https://files.pythonhosted.org/packages/35/19/b40de63f1a80e63bc2d40f0679a6a8dbd34e95176c8122119bdf406aa552/mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", size = 10201510, upload-time = "2026-07-13T11:31:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" }, + { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" }, + { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nats-py" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/f0/fc5e93f2b0dd14a202590ad9d30eda1955ea872039b5204357348d0f4b1e/nats_py-2.15.0.tar.gz", hash = "sha256:6622c547d9a7d2313d9c147d46c386188f4ec2c7b5c9f9a0438a4d1b55f54a93", size = 75995, upload-time = "2026-06-05T07:34:03.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/a8/b55606c7c621fb813c8ec78baf201d2c78bf6051091ec0c7ada572999e95/nats_py-2.15.0-py3-none-any.whl", hash = "sha256:9f8d36aa52a9926a88b8f1d70cf1fdce0ad387941479b500ee9ab3e51073cefd", size = 90334, upload-time = "2026-06-05T07:34:02.81Z" }, +] + +[[package]] +name = "neo4j" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/aaa4ac19adae4b01bc742b63afd2672a77e7351566f02721e713e4b863ee/neo4j-6.2.0.tar.gz", hash = "sha256:e1e246b65b572bd8ea97f9e0e721b7d40a5ce53e53d0007c29aef63e4f9124d9", size = 241459, upload-time = "2026-05-04T07:35:41.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/cf/1c3795866cefaac6e648d4e98c373cafd97810f6e317c307371007ab4abb/neo4j-6.2.0-py3-none-any.whl", hash = "sha256:b87abdd13a5cc2e3bd51026926c2f20ac38fa3febe98c340520dce19e97388d0", size = 327824, upload-time = "2026-05-04T07:35:39.604Z" }, +] + +[[package]] +name = "nh3" +version = "0.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/5f/1d19bdc7d27238e37f3672cdc02cb77c56a4a86d140cd4f4f23c90df6e16/nh3-0.3.5.tar.gz", hash = "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8", size = 20743, upload-time = "2026-04-25T10:44:16.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/b0/8587ac42a9627ab88e7e221601f1dfccbf4db80b2a29222ea63266dc9abc/nh3-0.3.5-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a", size = 1420126, upload-time = "2026-04-25T10:43:39.834Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/1dbc4d0c43f12e8c1784ede17eaee6f061d4fbe5505757c65c49b2ceab95/nh3-0.3.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263", size = 793943, upload-time = "2026-04-25T10:43:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/47/9f/d6758d7a14ee964bf439cc35ae4fa24a763a93399c8ef6f22bd11d532d29/nh3-0.3.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9", size = 841150, upload-time = "2026-04-25T10:43:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/d5d1ae8374612c98f390e1ea7c610fa6c9716259a03bbf4d15b269f40073/nh3-0.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588", size = 1008415, upload-time = "2026-04-25T10:43:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/d13a9c3fd2d9c131a2a281737380e9379eb0f8c33fea24c2b923aaafbb15/nh3-0.3.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd", size = 1092706, upload-time = "2026-04-25T10:43:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/bb/57/2f3add7f8680fcc896afa6a675cb2bab09982853ee8af40bad621f6b61c4/nh3-0.3.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17", size = 1048346, upload-time = "2026-04-25T10:43:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c3/2f9e4ffa82863074d1361bfe949bc46393d91b3411579dfbbd090b24cac5/nh3-0.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29", size = 1029038, upload-time = "2026-04-25T10:43:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/e8/10/2804deb3f3315184c9cae41702e293c87524b5a21f766b07d7fe3ffbcfbb/nh3-0.3.5-cp314-cp314t-win32.whl", hash = "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88", size = 603263, upload-time = "2026-04-25T10:43:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/f6685248b49f7548fc9a8c335ab3a52f68610b72e8a61576447151e4e2e6/nh3-0.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f", size = 616866, upload-time = "2026-04-25T10:43:51.005Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/d8c9018635d4acfefde6b68470daa510eed715a350cbaa2f928ba0609f81/nh3-0.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79", size = 602566, upload-time = "2026-04-25T10:43:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/d162e99746a2fb1d98bb0ef23af3e201b156cf09f7de867c7390c8fe1c06/nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101", size = 1442393, upload-time = "2026-04-25T10:43:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/25/8c/072120d506978ab053e1732d0efa7c86cb478fee0ee098fda0ac0d31cb34/nh3-0.3.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878", size = 837722, upload-time = "2026-04-25T10:43:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/d4e06e28c5ad1c4b065f89737d02631bd49f1660b6ebcf17a87ffcd201da/nh3-0.3.5-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93", size = 822872, upload-time = "2026-04-25T10:43:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/0a/62/50659255213f241ec5797ae7427464c969397373e83b3659372b341ae869/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3", size = 1100031, upload-time = "2026-04-25T10:43:58.098Z" }, + { url = "https://files.pythonhosted.org/packages/00/7a/a12ae77593b2fcf3be25df7bc1c01967d0de448bdb4b6c7ec80fe4f5a74f/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:207c01801d3e9bb8ec08f08689346bdd30ce15b8bf60013a925d08b5388962a4", size = 1057669, upload-time = "2026-04-25T10:43:59.328Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/5647dc04c0233192a3956fc91708822b21403a06508cacf78083c68e7bf0/nh3-0.3.5-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea232933394d1d58bf7c4bb348dc4660eae6604e1ae81cd2ba6d9ed80d390f3b", size = 914795, upload-time = "2026-04-25T10:44:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0e/bf298920729f216adcb002acf7ea01b90842603d2e4e2ce9b900d9ee8fab/nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3a787dc76b50de6bee54ef242f26c41dfe47654428e3e94f0fae5bb6dd2cc1", size = 806976, upload-time = "2026-04-25T10:44:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/85/01/26761e1dc2b848e65a62c19e5d39ad446283287cd4afddc89f364ab86bc9/nh3-0.3.5-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:488928988caad25ba14b1eb5bc74e25e21f3b5e40341d956f3ce4a8bc19460dc", size = 834904, upload-time = "2026-04-25T10:44:03.454Z" }, + { url = "https://files.pythonhosted.org/packages/33/53/0766113e679540ac1edc1b82b1295aecd321eeb75d6fead70109a838b6ee/nh3-0.3.5-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c069570b06aa848457713ad7af4a9905691291548c4466a9ad78ee95808382b", size = 857159, upload-time = "2026-04-25T10:44:05.003Z" }, + { url = "https://files.pythonhosted.org/packages/58/36/734d353dfaf292fed574b8b3092f0ef79dc6404f3879f7faaa61a4701fad/nh3-0.3.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eeedc90ed8c42c327e8e10e621ccfa314fc6cce35d5929f4297ff1cdb89667c4", size = 1018600, upload-time = "2026-04-25T10:44:06.18Z" }, + { url = "https://files.pythonhosted.org/packages/6b/aa/d9c59c1b49669fcb7bababa55df82385f029ad5c2651f583c3a1141cfdd1/nh3-0.3.5-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d", size = 1103530, upload-time = "2026-04-25T10:44:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/cdd210bfb8d9d43fb02fc3c868336b9955934d8e15e66eb1d15a147b8af0/nh3-0.3.5-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad", size = 1061754, upload-time = "2026-04-25T10:44:09.362Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/7a39e72e668c8445bdd95e494b3e21cfdddc68329be8ea3522c8befb46c4/nh3-0.3.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9", size = 1040938, upload-time = "2026-04-25T10:44:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/4c/fc2f9ed208a3801a319f59b5fea03cdc20cf3bd8af14be930d3a8de01224/nh3-0.3.5-cp38-abi3-win32.whl", hash = "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f", size = 611445, upload-time = "2026-04-25T10:44:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/db/1a/e4c9b5e2ae13e6092c9ec16d8ca30646cb01fcdea245f36c5b08fd21fbd5/nh3-0.3.5-cp38-abi3-win_amd64.whl", hash = "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30", size = 626502, upload-time = "2026-04-25T10:44:13.682Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/19cd0671d1ba2762fb388fc149697d20d0568ccfeef833b11280a619e526/nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", size = 611069, upload-time = "2026-04-25T10:44:14.934Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "openfga-sdk" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "opentelemetry-api" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/56/e83a75eae64d75aec96a6cced80bb5bbe4d74837bb6409692debb14b10cb/openfga_sdk-0.10.4.tar.gz", hash = "sha256:f5a681af3adb11249ebe33d5977286bb385a5c448edb47d71583e2a04b546e4c", size = 348682, upload-time = "2026-06-29T10:28:29.154Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/a6/a55854a9ee7aadf2b32eaed16211075bf7667309775e53e01756eacf02ca/openfga_sdk-0.10.4-py3-none-any.whl", hash = "sha256:7daa358ff669f47a094045f01235bb2733cbcfef84b9783e2deb6b64f5611176", size = 267237, upload-time = "2026-06-29T10:28:27.467Z" }, +] + +[[package]] +name = "opensearch-protobufs" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/2f/e0cc165af7bb7b44cb00023b9fcaa01a28d1755a059ede28d0cd970c3cec/opensearch_protobufs-1.2.0-py3-none-any.whl", hash = "sha256:e806730894d0a0c8cdaa3cdbe07e4b7c46e1823f453777b36caf39e9cba28e2c", size = 54751, upload-time = "2026-01-22T18:51:56.805Z" }, +] + +[[package]] +name = "opensearch-py" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "events" }, + { name = "opensearch-protobufs" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/9e/e77844cb2d625ca32331bfdd28930113b3778399c01dd5f1c350ceb55e65/opensearch_py-3.2.0.tar.gz", hash = "sha256:f40fb3a295275422df2ad6d9459f667af94472d5a9e567072e9ecf163eb22613", size = 259927, upload-time = "2026-04-27T18:17:50.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/63/7abb96bf2e3619acbd27de99e60619bfacfb7c55b68c4792a258e6d92871/opensearch_py-3.2.0-py3-none-any.whl", hash = "sha256:721a0d3b13fbed9e82278aed748285cf63a1855354ab7e73e3d4992d1b93418b", size = 387286, upload-time = "2026-04-27T18:17:48.658Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/1c/125e1c936c0873796771b7f04f6c93b9f1bf5d424cea90fda94a99f61da8/opentelemetry_api-1.42.1.tar.gz", hash = "sha256:56c63bea9f77b62856be8c47600474acad853b2924b99b1687c4cb6297166716", size = 72296, upload-time = "2026-05-21T16:32:49.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/ca/9520cc1f3dfbbd03ac5903bbf55833e257bc64b1cf30fa8b0d6df374d821/opentelemetry_api-1.42.1-py3-none-any.whl", hash = "sha256:51a69edacadbc03a8950ace1c4c21099cacc538820ac2c9e36277e78cebba714", size = 61311, upload-time = "2026-05-21T16:32:28.822Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/9c/216acfeaedadf2e1937f4373929b20f73197c5c4a2546d4f584b7fa63813/opentelemetry_exporter_otlp_proto_common-1.42.1.tar.gz", hash = "sha256:04f1f01fb597c4249dfcd7f8b861c902c2102369d376d9d346ff38de4469a2ee", size = 21433, upload-time = "2026-05-21T16:32:55.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/43/2375e7612e1121a4518c17603b6e0b03ad94f565aafad53f464dc5be2bf6/opentelemetry_exporter_otlp_proto_common-1.42.1-py3-none-any.whl", hash = "sha256:f48d395ab815b444da118868977e9798ea354c25737d5cf39578ae894011c140", size = 17327, upload-time = "2026-05-21T16:32:33.387Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/87/ca7fc790dfdbcf4f9e9aab14a39ef1b7508ead13707e283de0b3131478d2/opentelemetry_exporter_otlp_proto_grpc-1.42.1.tar.gz", hash = "sha256:975c4461f167dd8ed8857d68d3b6b25f3d272eab896f6a9470d0f5b90e2faf15", size = 27140, upload-time = "2026-05-21T16:32:56.162Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/2b/28ba5b128f47fe8c3bab541000d6feb4b5a9bd26623ca013406f01c0fb60/opentelemetry_exporter_otlp_proto_grpc-1.42.1-py3-none-any.whl", hash = "sha256:0ae1177e2038b18a929b3098215243631ef91136cba26b7e2b12790ceb7e87cc", size = 19617, upload-time = "2026-05-21T16:32:34.278Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/55/63eac3e1089b768ba014091fdd2ae8a9a440c821ef5e2b786909c94c8836/opentelemetry_proto-1.42.1.tar.gz", hash = "sha256:c6a51e6b4f05ae63565f3a113217f3d2bfaec68f78c02d7a6c85f9010d1cfca6", size = 45839, upload-time = "2026-05-21T16:33:03.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/9d/171c02c84a76940b7e601805b3bb536985aded9168fbcc9ba52f0a730fa2/opentelemetry_proto-1.42.1-py3-none-any.whl", hash = "sha256:dedb74cba2886c59c7789b227a7a670613025a07489040050aedff6e5c0fb43c", size = 71782, upload-time = "2026-05-21T16:32:44.867Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.42.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f7/b390bd9bfd703bf98a68fea1f27786c6872331fd617164a54b8a59bdc008/opentelemetry_sdk-1.42.1.tar.gz", hash = "sha256:8c834e8f8c9ba4171d4ec843d0cb8a67e4c7394d3f9e9297e582cbd9456ddbf7", size = 239262, upload-time = "2026-05-21T16:33:04.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/6b/4287766cfbde577ae2272e8884abac325aeaac0d64f41c61d5b8cc595105/opentelemetry_sdk-1.42.1-py3-none-any.whl", hash = "sha256:083cd4bbfaa5aa7b5a9e552430d9951219967cfb27aa61feb13a77aba1fc839d", size = 170907, upload-time = "2026-05-21T16:32:45.894Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.63b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/99/4d7dd6df64795951413ce6e815f8cf1eb191daf7196ae86574589643d5f3/opentelemetry_semantic_conventions-0.63b1.tar.gz", hash = "sha256:3daf963611334b365e98a57438183eb012d3bfb40b2d931a9af613476b8701a9", size = 148340, upload-time = "2026-05-21T16:33:05.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7a/7fe66f5f3682b1dd47d88cc4e11f1c6c0966b737de2d16671146e23c39a5/opentelemetry_semantic_conventions-0.63b1-py3-none-any.whl", hash = "sha256:dfe5ef4dee82586b746f522b818ceb298d00b3d59f660042bd79404bff8d0682", size = 203713, upload-time = "2026-05-21T16:32:47.016Z" }, +] + +[[package]] +name = "oracledb" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/01/203599755aaff4f79683c2d61d2e6dc9d30f6228485d9e0280cc358cede3/oracledb-4.0.1.tar.gz", hash = "sha256:34bbea44423ed8b24093aa859ca7ee9b6e76ea490f9acdc5f6ff01aa1083e343", size = 879077, upload-time = "2026-05-19T18:14:34.881Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/4e/d1a16f398963eb41a6a1024030bf6c5ead34df2f9e558fbf24759effa703/oracledb-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dbe8b44fea57385617838f2acfce8cc19f6c95cd9e65e7235e86b5932af1acd9", size = 4374567, upload-time = "2026-05-19T18:14:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/db/da/849e06ddd29f5d8807ba9fb93ebdf210958273eeb12ca08f72d9f07f87cb/oracledb-4.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3d54b624748cfe42248c4bc62c3f788632a2077058485a9acb3150312b1c396", size = 2499287, upload-time = "2026-05-19T18:14:43.321Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b6/d6df13f297ccf64396867da84bed8529e2c1755839207746e5ac2cc6abc2/oracledb-4.0.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29ae0ff517a3241060eeee15a321b710c3f83a688cf2da7d5729adbe212e2b00", size = 2689995, upload-time = "2026-05-19T18:14:44.832Z" }, + { url = "https://files.pythonhosted.org/packages/01/98/5dec7011c2c26c8c3c2f9ea76783e2bc6cd6ad5f0489782f9cf4637e733f/oracledb-4.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73ba32597fe1da72e0824aaa4b1900ec08a3b77268cb4eb45c733ae7e7043e70", size = 2540194, upload-time = "2026-05-19T18:14:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/09/ef/21049e705a5dbac0011e647a4dcaf1746a501e8e19da98bd1674b21206d7/oracledb-4.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ce3f25552fe58df5c266874f8b13f0a8ab7fcd09ab4b476bc15520a67527ca4b", size = 2714751, upload-time = "2026-05-19T18:14:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c1/0e02cc9f84b92a2fc8cbcb48be22b43076c511392b6ec36795e8a9ebc424/oracledb-4.0.1-cp310-cp310-win32.whl", hash = "sha256:7db5a43c29a23ed23923a29816c65c7a81fe00f2abfe6bf36d83ad952abd9b89", size = 1546877, upload-time = "2026-05-19T18:14:50.081Z" }, + { url = "https://files.pythonhosted.org/packages/b9/4a/d14ac7ed4e2f87f77807eaed88d8a2e787219407f170e80546cbebc466dd/oracledb-4.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:03afeda85bec3eca983ebf3ad9910d0f217d99300258366d287e015a041d6c13", size = 1894743, upload-time = "2026-05-19T18:14:52.038Z" }, + { url = "https://files.pythonhosted.org/packages/88/46/f38813e076a43cfa632db2b80761c0b1aa1c85dca7b89720d4984b3b2e17/oracledb-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86a06d0afb3bb3a24bace0e72fb9abca2093efe0fa3457c65c13ba4eb5000b0b", size = 4351657, upload-time = "2026-05-19T18:14:53.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e6/8ad2592ae4d5657882b900bad1b1286f1b29befa0f418df8550d8f7defd6/oracledb-4.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:416b324cd7715073cf5f3d577330387ffd59741463995c25bdc2d82b3e80b88e", size = 2492515, upload-time = "2026-05-19T18:14:55.834Z" }, + { url = "https://files.pythonhosted.org/packages/08/4e/eaab1183a2ba63e7f8673e3305f5382488c773d8f51d9a705e90d8b8b3e1/oracledb-4.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce6319ee01dcbb4d74f0e2a5794c6a566f339958ecac9830c67c7070521620e2", size = 2680701, upload-time = "2026-05-19T18:14:57.418Z" }, + { url = "https://files.pythonhosted.org/packages/44/f5/824b419dc3758c86ddcb1d8f69d1e7957882e58e99a3907a45a063ed0878/oracledb-4.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:873fcca53306e2b3b445a7d657cddc19e415a7aa7e392c473dfd1a3ae3970989", size = 2542151, upload-time = "2026-05-19T18:14:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/be/5c/35b60bb1c85be5607913a24467052d6496eed9493793989de2a6b9b71147/oracledb-4.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b09eec35681d72c9476e6d715b89bb775724a31e7363df6beba7470494ea8040", size = 2709740, upload-time = "2026-05-19T18:15:01.03Z" }, + { url = "https://files.pythonhosted.org/packages/79/22/d4639df519c7cca02f45b3404c9e7934970f50a0296f704be7b1805b1ce7/oracledb-4.0.1-cp311-cp311-win32.whl", hash = "sha256:08e84a6af1b6e5921dba088dd9fc0738927206eafe5ce9763c34195f87556849", size = 1543018, upload-time = "2026-05-19T18:15:02.516Z" }, + { url = "https://files.pythonhosted.org/packages/20/88/7304ff7d512f45d6804501e404ceb7e567eb15661cd7d06c9ffaa6025cb5/oracledb-4.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:3b5ef1676a27b7e0a7ec55be27fd8f6d28d1601f5e8dfdae78705909f25b7c0a", size = 1901166, upload-time = "2026-05-19T18:15:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b5/079fe8aa90cb769c68a54dde7591b374c64260d3d448c086acf4585cb8fc/oracledb-4.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:90586b3c7729b9cf3d40df902e81257f01e15e3408d8b6b9dbf91e939b64f72c", size = 1586727, upload-time = "2026-05-19T18:15:05.523Z" }, + { url = "https://files.pythonhosted.org/packages/53/1a/237ddaa33fe9576e1bb1ee9008eba2269fe49dee974a581465589eac658e/oracledb-4.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c05a01d6ad610a88c2aa1a43b1dc0a8485f5fbd4374d2b36908859d4205de192", size = 4345951, upload-time = "2026-05-19T18:15:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bd/2ee10e0bd4d7a44db68a3f10d8941a5ea762bdb9dcff9b37739b2554e011/oracledb-4.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf61e42b9ef723dbdd0b23032b695e872009ed7341003df59d9a97cd960df977", size = 2290431, upload-time = "2026-05-19T18:15:09.041Z" }, + { url = "https://files.pythonhosted.org/packages/80/54/98fac98c9c1e2461512dcad81d2bd491fb7fe779d5044614867b2e827a78/oracledb-4.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2d394453f669858bec942ff0da18b6ebade296ece823d582ad2b464ed5c6c90", size = 2493620, upload-time = "2026-05-19T18:15:10.635Z" }, + { url = "https://files.pythonhosted.org/packages/58/71/ebeade4753fd20b6d9cc7568c3853bec44425f24982def220835544008ca/oracledb-4.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d7cd278d59780e22e0a7451d208460756d779dc62b55bdbd95652f9640fbf8c3", size = 2329161, upload-time = "2026-05-19T18:15:12.158Z" }, + { url = "https://files.pythonhosted.org/packages/21/1d/4ec3ef256633126b669b99bf14e1c2af264ecb25c28f80f5365ea279c39a/oracledb-4.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b73820521eccd290506af94e1ffb9a8a5941b4018e3861df9b040652a7cef123", size = 2513518, upload-time = "2026-05-19T18:15:14.863Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7a/4506477b52bb2db61a114202beab19bf24859f9fdc0979b8031042582477/oracledb-4.0.1-cp312-cp312-win32.whl", hash = "sha256:8fcad6d9628923281bf21e48a391ac2f87ec6950dc63381d8fea470e3128aef0", size = 1498632, upload-time = "2026-05-19T18:15:16.361Z" }, + { url = "https://files.pythonhosted.org/packages/dd/66/3d2246e5473b85c61d4b206ea9fcf50619db6b60a1425e34a3a1c1f64c44/oracledb-4.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:523b3356cde9d588ba250cefafdfc34869233d65c179f805ea6e4d3d6b209a7f", size = 1846730, upload-time = "2026-05-19T18:15:18.532Z" }, + { url = "https://files.pythonhosted.org/packages/dd/95/70420ebc1f051cff48cffb48f720540446d3fc497f7d9ba6f2e63337cb3f/oracledb-4.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:10204432f0eea8707a79c75bdccb84071e43fd19c658cb3b34d1746b12c6e7fe", size = 1518529, upload-time = "2026-05-19T18:15:19.916Z" }, + { url = "https://files.pythonhosted.org/packages/e3/52/c95031186ddec3788ee72708df406a6b7bafa308bd04722b7d5857942366/oracledb-4.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:443b2f03461e873ccd73dff3d8541fcf974c05e13e296a6687ffbb0c4a72c0a1", size = 4331008, upload-time = "2026-05-19T18:15:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c3/7aaf12fe0f8b8323bce4647762722268da6b3b04ab1ff2349e4b43d30515/oracledb-4.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae894ca2705929eb0ac228329336fd03388ad6e3b54002be6f5d4400a8feaf52", size = 2284457, upload-time = "2026-05-19T18:15:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/db/1e/48af5b2c559ee2fbee2ad13372858f34f2260cd9727b8ccad6cbe26e09b8/oracledb-4.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b42725337f80d433a3bd2928c08667e5b89da9ce05cf9ae3a4189c4fc4805ea", size = 2506136, upload-time = "2026-05-19T18:15:26.057Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/50cb8f7a3bc7ef9edc61db5fbf0ee2af9a4f487f4967eb54cc9f330ea908/oracledb-4.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8e13ff1e6f28fdb863180d23fa94cb42c619c29d2981e24992431e51b97caa54", size = 2332705, upload-time = "2026-05-19T18:15:27.632Z" }, + { url = "https://files.pythonhosted.org/packages/48/7d/6eea9e9ffece7d270a7d3caf364b5033fa8c73a0f377c10750eca8760a6e/oracledb-4.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e36581bb10e719d928dad12018c2d42606db2c34f49d6665b06f701f049255f0", size = 2528849, upload-time = "2026-05-19T18:15:29.437Z" }, + { url = "https://files.pythonhosted.org/packages/97/5c/553afdf90a144e25c5ee9825fbbc0c88d52bcf2b1dd7c5b509cad1022a60/oracledb-4.0.1-cp313-cp313-win32.whl", hash = "sha256:86ac65cbc8d29626b1d9d203f9151566c26a78e55bdfc030c06169ae8017f458", size = 1503094, upload-time = "2026-05-19T18:15:30.838Z" }, + { url = "https://files.pythonhosted.org/packages/b9/73/9305fa2755b234352e29dd5ed6063d017a661ccb08c06da5e14db90b143d/oracledb-4.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:a029dcee759bca56a8c95e952040c3d3f57e5ec05965355293b21930a66967fb", size = 1846068, upload-time = "2026-05-19T18:15:32.475Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d8/6995a755dbdeee1eab1a6cb77283e3ab6634631512c47e2c3a64efcb515f/oracledb-4.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:20a10f903c8da59e9689a98bd68012f78fa19bed950ad9f19cd8f5b8b97e73a0", size = 1518345, upload-time = "2026-05-19T18:15:34.156Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c1/e31ea17105b037b8b637a0e2936fc3cab65f87098c79df50fdf443a0ca6e/oracledb-4.0.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:cb7727f93ff962ab826bc3d0bca4b0e5bf45ecb7c525551c70c9e094f0f27027", size = 4374586, upload-time = "2026-05-19T18:15:36.73Z" }, + { url = "https://files.pythonhosted.org/packages/14/87/541fec6a6e37b74cd113007927e9caced188cf262f6015e88732651b7cc9/oracledb-4.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:032ca4f558b05f03fa1bef1b04e59ec350ae0b22e6d85c47f4ac62ae98315823", size = 2324417, upload-time = "2026-05-19T18:15:38.795Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ba/14b9112c3f81c2b26cdd16748a5317263db1227e363bd29491d3291ad6e8/oracledb-4.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7156ef112a901967b3ee89b6c582bafc5a3082c47ca566de1a79e9ac3b48da32", size = 2528659, upload-time = "2026-05-19T18:15:40.38Z" }, + { url = "https://files.pythonhosted.org/packages/44/5a/cddfbdf30d23edd6484aa022f5e329eaecced2ead27cc2c01a05065d3e2d/oracledb-4.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8159c5bd8f25b0ca0ce30f21e7a732a2bdfb4adb81b9c8ea1ca75339d8ec8398", size = 2374071, upload-time = "2026-05-19T18:15:42.069Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c9/1561ab033f35bbccaca4f07360b6c446eb2726ab03754fbb5506e2d3fe89/oracledb-4.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4926e699a42c526137724960fa4303ecb0b542186b11d3705ac84414a896508", size = 2552725, upload-time = "2026-05-19T18:15:43.541Z" }, + { url = "https://files.pythonhosted.org/packages/bd/22/a5dacf6d5d976850bf7c62b8c1ef7f1a56a56421962f081bc421ab8eb952/oracledb-4.0.1-cp314-cp314-win32.whl", hash = "sha256:b05bfadbfe462c39cc97258a973972f5bbbc9f8e2e9a4c2e0efcb1ec86b91088", size = 1525688, upload-time = "2026-05-19T18:15:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/36/eb/1f959914455bda7e02531c5c07a5c6a2354ce164b29281f441777747a1b2/oracledb-4.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:0ece951553c106a0896c8e1690bcdf69d472761fa65fec9b8152cbce13ab8b81", size = 1890335, upload-time = "2026-05-19T18:15:46.7Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b1/1289009c7407953fd19960dca73be921a4f6c88b097a6db59e0a2138bfc4/oracledb-4.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d3c6ed987df64b914ece0722692419fe494d07f15bb4d7715adeada4f914c3a", size = 1572519, upload-time = "2026-05-19T18:15:48.234Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5d/b95ca542a001135cc250a49370f282f578c8f4e46cc8617d73775297eea8/orjson-3.11.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:135869ef917b8704ea0a94e01620e0c05021c15c52036e4663baffe75e72f8ce", size = 228986, upload-time = "2026-05-06T15:09:14.765Z" }, + { url = "https://files.pythonhosted.org/packages/80/01/be33fbff646e22f93398429ea645f20d2097aea1a6cdc1e6628e70125f83/orjson-3.11.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115ab5f5f4a0f203cc2a5f0fb09aee503a3f771aa08392949ab5ca230c4fbdbd", size = 132558, upload-time = "2026-05-06T15:09:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/4e/61/73d49333bba660a075daccca10970dc6409ce1cf42ae4046646a19468aad/orjson-3.11.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4da3c38a2083ca4aaf9c2a36776cce3e9328e6647b10d118948f3cfb4913ffe4", size = 128213, upload-time = "2026-05-06T15:09:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/7d/30e844b3dac3f74aed66b1f984daf9db3c98c0328c03d965a9e8dc06449e/orjson-3.11.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53b50b0e14084b8f7e29c5ce84c5af0f1160169b30d8a6914231d97d2fe297d4", size = 135430, upload-time = "2026-05-06T15:09:20.257Z" }, + { url = "https://files.pythonhosted.org/packages/16/64/bd815f5c610b3facc204f26ba94e87a9eb49b0d83de3d5fc1eee2402d91b/orjson-3.11.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:231742b4a11dad8d5380a435962c57e91b7c37b79be858f4ef1c0df1a259897e", size = 146178, upload-time = "2026-05-06T15:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/c7/35/e744fd36c79b339d27beb06068b5a08a8882ef5418804d0ce545a31f718d/orjson-3.11.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34fd2317602587321faab75ab76c623a0117e80841a6413654f04e47f339a8fb", size = 133068, upload-time = "2026-05-06T15:09:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/d54152b67b63a0b3e556cfc549d6ce84f74d7f425ddeadc6c8a74d913da7/orjson-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71f3db16e69b667b132e0f305a833d5497da302d801508cbb051ed9a9819da47", size = 134217, upload-time = "2026-05-06T15:09:24.847Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ee/66154baf69f71c7164a268a5e888908aec5a0819d13c81d5e2755a257758/orjson-3.11.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0b34789fa0da61cf7bef0546b09c738fb195331e017e477096d129e9105ab03d", size = 141917, upload-time = "2026-05-06T15:09:26.647Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/c5824260ca8b9d7ba82648d042a3f8f4815d18c15bb98a1f30edd1bb2d83/orjson-3.11.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:87e4d4ab280b0c87424d47695bec2182caf8cfc17879ea78dab76680194abc13", size = 415356, upload-time = "2026-05-06T15:09:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/64/cb/509c2e816fe4df641d93dc92f6a89adc8df3ada8ebdee2bd44aba3264c3c/orjson-3.11.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ace6c58523302d3b97b6ac5c38a5298a54b473762b6be82726b4265c41029f92", size = 148112, upload-time = "2026-05-06T15:09:29.783Z" }, + { url = "https://files.pythonhosted.org/packages/db/b5/3ceae56d2e4962979eedb023ba6a46a4bb65f333960379be0ca470686220/orjson-3.11.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:97d0d932803c1b164fde11cb542a9efcb1e0f63b184537cca65887147906ff48", size = 137112, upload-time = "2026-05-06T15:09:31.432Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/81fa3f2c7bef79b04cf2ab7838e5ac74b1f12511ceab979759b0275d6bb4/orjson-3.11.9-cp310-cp310-win32.whl", hash = "sha256:b3afcf569c15577a9fe64627292daa3e6b3a70f4fb77a5df246a87ec21681b94", size = 131706, upload-time = "2026-05-06T15:09:32.707Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/b64600f9083c7f151ad39717a5877fccbeb0ef6d7efcb55f971ce00b6bee/orjson-3.11.9-cp310-cp310-win_amd64.whl", hash = "sha256:8697ab6a080a5c46edaad50e2bc5bd8c7ca5c66442d24104fa44ec74910a8244", size = 127282, upload-time = "2026-05-06T15:09:33.955Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, + { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, + { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, + { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, + { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, + { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +] + +[[package]] +name = "paramiko" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bcrypt" }, + { name = "cryptography" }, + { name = "invoke" }, + { name = "pynacl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/93/dcc25d52f49022ae6175d15e6bd751f1acc99b98bc61fc55e5155a7be2e7/paramiko-5.0.0.tar.gz", hash = "sha256:36763b5b95c2a0dcfdf1abc48e48156ee425b21efe2f0e787c2dd5a95c0e5e79", size = 1548586, upload-time = "2026-05-09T18:28:52.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/5b/eadf6d45de38d30ab603f49393b6cd2cbe7e233af8cf90197e32782b68a9/paramiko-5.0.0-py3-none-any.whl", hash = "sha256:b7044611c30140d9a75261653210e2002977b71a0497ff3ba0d98d7edbf62f7c", size = 208919, upload-time = "2026-05-09T18:28:50.295Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pg8000" +version = "1.31.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "scramp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c8/9a/077ab21e700051e03d8c5232b6bcb9a1a4d4b6242c9a0226df2cfa306414/pg8000-1.31.5.tar.gz", hash = "sha256:46ebb03be52b7a77c03c725c79da2ca281d6e8f59577ca66b17c9009618cae78", size = 118933, upload-time = "2025-09-14T09:16:49.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/07/5fd183858dff4d24840f07fc845f213cd371a19958558607ba22035dadd7/pg8000-1.31.5-py3-none-any.whl", hash = "sha256:0af2c1926b153307639868d2ee5cef6cd3a7d07448e12736989b10e1d491e201", size = 57816, upload-time = "2025-09-14T09:16:47.798Z" }, +] + +[[package]] +name = "pika" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/06/a5e4589eccb22be5790bf1327e20c5387b64327daa40c4d857cf9265ec3c/pika-1.4.2.tar.gz", hash = "sha256:48d1f50297e76be4fc798fd5232d4d532d7a4758e51f7c0ae6c4004b9808a26b", size = 154365, upload-time = "2026-07-23T02:11:26.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/82/3b047c707700e08539cde489475eb5eaca13550599baaa17a8da038003e7/pika-1.4.2-py3-none-any.whl", hash = "sha256:b1df7389cdffaa45856bd01ade4e81bd488e0e199c443faf0c7bea89015f373f", size = 165087, upload-time = "2026-07-23T02:11:24.712Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/56/030b7b4719d53085722893e0009dffb9236aa10bca1b12121bdc5626ef16/propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b", size = 93417, upload-time = "2026-05-08T20:59:15.597Z" }, + { url = "https://files.pythonhosted.org/packages/1a/55/1140a8e067b8ec093a18a4ae7bb0045d9db65da38a08618ddc5e2f1994aa/propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c", size = 53847, upload-time = "2026-05-08T20:59:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/0e7443c90310498561addf346e7d57fe3c6ba1914e1ba938b5464c7bbfd2/propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb", size = 53512, upload-time = "2026-05-08T20:59:18.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/db/cf51a71bab2009517d1a7f0ee07657e3bd446c4d69f67e6966cf17bcf956/propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e", size = 58068, upload-time = "2026-05-08T20:59:20.683Z" }, + { url = "https://files.pythonhosted.org/packages/b7/43/39b6bdee9699fa1e1641c519feeb64a67e2a9f93bb465c70776b37a7333f/propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e", size = 61020, upload-time = "2026-05-08T20:59:22.112Z" }, + { url = "https://files.pythonhosted.org/packages/26/0b/843726fbb0a29a8c5684fdb25971823638399f31e52e9d1f06a02dc9aa6b/propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b", size = 62732, upload-time = "2026-05-08T20:59:23.805Z" }, + { url = "https://files.pythonhosted.org/packages/39/6e/899fed76dc1942b8a64193a4f059d7f1a2c7ef65085e8a9366ed8ec0d199/propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d", size = 60140, upload-time = "2026-05-08T20:59:25.389Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/3da4be9b5b879219ad234aa535b3dd4a080ed1ad48d3a73ca07a9e798f22/propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d", size = 60400, upload-time = "2026-05-08T20:59:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/60/2f/09b72b874a9aa0044faf52a69807a6ed618e267ceaa9ec4a63195fa5b504/propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0", size = 58155, upload-time = "2026-05-08T20:59:28.48Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/97489848c54c95578045473954f10956d619ce6a09e7ac137b71cdcb698b/propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b", size = 57037, upload-time = "2026-05-08T20:59:30.146Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/6c695285ccfc49012743ee9c98212b8c5dd0aed7b63cfd816d4a0f7a1601/propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf", size = 61103, upload-time = "2026-05-08T20:59:31.626Z" }, + { url = "https://files.pythonhosted.org/packages/98/a9/1e500401ca593b0bdb6bf75a70bc2d723835fd53360edff6af70692c7546/propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf", size = 60394, upload-time = "2026-05-08T20:59:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/f638b6e375eae0f30a1a2325d8b34fd85fdc785bb9960cf805f3bf1ec69a/propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e", size = 63084, upload-time = "2026-05-08T20:59:35.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/18/884573f5d97b6d9eba68de759a82c901b7e39d7904d30f7b8d58d42d2a12/propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274", size = 60999, upload-time = "2026-05-08T20:59:38.481Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/c3915eb059ceec9e758a56e4cfd955292bc0f201be2176a46b76d94b303a/propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe", size = 39036, upload-time = "2026-05-08T20:59:40.323Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/1dfd5607501a602d19c1c449d2d193b7d1c611f9246b4059026a1189a80e/propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d", size = 42190, upload-time = "2026-05-08T20:59:42.232Z" }, + { url = "https://files.pythonhosted.org/packages/57/93/f71588ad08b3e6f4b555b5ef215808a3c02b042d0151ad82fa6f15be677a/propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5", size = 38545, upload-time = "2026-05-08T20:59:44.087Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/56/e647b0c675392d2da368da7b6f158f7368b18542fd6f7d7400a2f39de000/proto_plus-1.28.0.tar.gz", hash = "sha256:38e5696342835b08fc116f30a25665b29531cda9d5d5643e9b81fc312385abd9", size = 57221, upload-time = "2026-05-07T08:04:50.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/20/b122d4626976acb81132036d2ad1bb35a1a8775fceb837ec30964622516a/proto_plus-1.28.0-py3-none-any.whl", hash = "sha256:a630604310899e73c59ec302e5765c058d412b2f090b9c79c8822589f14955b8", size = 50410, upload-time = "2026-05-07T08:03:31.962Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "psycopg" +version = "3.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/2f/cb91e5502ec9de1de6f1b76cfbf69531932725361168bb06963620c77e2e/psycopg-3.3.4.tar.gz", hash = "sha256:e21207764952cff81b6b8bdacad9a3939f2793367fdac2987b3aac36a651b5bc", size = 165799, upload-time = "2026-05-01T23:31:55.179Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/e0/7b3dee031daae7743609ce3c746565d4a3ed7c2c186479eb48e34e838c64/psycopg-3.3.4-py3-none-any.whl", hash = "sha256:b6bbc25ccf05c8fad3b061d9db2ef0909a555171b84b07f29458a447253d679a", size = 213001, upload-time = "2026-05-01T23:20:50.816Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/f2/8e377d29c2ecf99f6062d35ea606b036e8800720eccfec5fe3dd672c2b24/psycopg2_binary-2.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6fe6b47d0b42ce1c9f1fa3e35bb365011ca22e39db37074458f27921dca40f2", size = 3756506, upload-time = "2025-10-10T11:10:30.144Z" }, + { url = "https://files.pythonhosted.org/packages/24/cc/dc143ea88e4ec9d386106cac05023b69668bd0be20794c613446eaefafe5/psycopg2_binary-2.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c0e4262e089516603a09474ee13eabf09cb65c332277e39af68f6233911087", size = 3863943, upload-time = "2025-10-10T11:10:34.586Z" }, + { url = "https://files.pythonhosted.org/packages/8c/df/16848771155e7c419c60afeb24950b8aaa3ab09c0a091ec3ccca26a574d0/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c47676e5b485393f069b4d7a811267d3168ce46f988fa602658b8bb901e9e64d", size = 4410873, upload-time = "2025-10-10T11:10:38.951Z" }, + { url = "https://files.pythonhosted.org/packages/43/79/5ef5f32621abd5a541b89b04231fe959a9b327c874a1d41156041c75494b/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a28d8c01a7b27a1e3265b11250ba7557e5f72b5ee9e5f3a2fa8d2949c29bf5d2", size = 4468016, upload-time = "2025-10-10T11:10:43.319Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9b/d7542d0f7ad78f57385971f426704776d7b310f5219ed58da5d605b1892e/psycopg2_binary-2.9.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f3f2732cf504a1aa9e9609d02f79bea1067d99edf844ab92c247bbca143303b", size = 4164996, upload-time = "2025-10-10T11:10:46.705Z" }, + { url = "https://files.pythonhosted.org/packages/14/ed/e409388b537fa7414330687936917c522f6a77a13474e4238219fcfd9a84/psycopg2_binary-2.9.11-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:865f9945ed1b3950d968ec4690ce68c55019d79e4497366d36e090327ce7db14", size = 3981881, upload-time = "2025-10-30T02:54:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/bf/30/50e330e63bb05efc6fa7c1447df3e08954894025ca3dcb396ecc6739bc26/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91537a8df2bde69b1c1db01d6d944c831ca793952e4f57892600e96cee95f2cd", size = 3650857, upload-time = "2025-10-10T11:10:50.112Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e0/4026e4c12bb49dd028756c5b0bc4c572319f2d8f1c9008e0dad8cc9addd7/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4dca1f356a67ecb68c81a7bc7809f1569ad9e152ce7fd02c2f2036862ca9f66b", size = 3296063, upload-time = "2025-10-10T11:10:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/2c/34/eb172be293c886fef5299fe5c3fcf180a05478be89856067881007934a7c/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0da4de5c1ac69d94ed4364b6cbe7190c1a70d325f112ba783d83f8440285f152", size = 3043464, upload-time = "2025-10-30T02:55:02.483Z" }, + { url = "https://files.pythonhosted.org/packages/18/1c/532c5d2cb11986372f14b798a95f2eaafe5779334f6a80589a68b5fcf769/psycopg2_binary-2.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37d8412565a7267f7d79e29ab66876e55cb5e8e7b3bbf94f8206f6795f8f7e7e", size = 3345378, upload-time = "2025-10-10T11:11:01.039Z" }, + { url = "https://files.pythonhosted.org/packages/70/e7/de420e1cf16f838e1fa17b1120e83afff374c7c0130d088dba6286fcf8ea/psycopg2_binary-2.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:c665f01ec8ab273a61c62beeb8cce3014c214429ced8a308ca1fc410ecac3a39", size = 2713904, upload-time = "2025-10-10T11:11:04.81Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ae/8d8266f6dd183ab4d48b95b9674034e1b482a3f8619b33a0d86438694577/psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10", size = 3756452, upload-time = "2025-10-10T11:11:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/aa03d327739c1be70e09d01182619aca8ebab5970cd0cfa50dd8b9cec2ac/psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a", size = 3863957, upload-time = "2025-10-10T11:11:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, + { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/0d/61/4aa89eeb6d751f05178a13da95516c036e27468c5d4d2509bb1e15341c81/psycopg2_binary-2.9.11-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a311f1edc9967723d3511ea7d2708e2c3592e3405677bf53d5c7246753591fbb", size = 3981881, upload-time = "2025-10-30T02:55:07.332Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, + { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/6d/c2/782a3c64403d8ce35b5c50e1b684412cf94f171dc18111be8c976abd2de1/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00ce1830d971f43b667abe4a56e42c1e2d594b32da4802e44a73bacacb25535f", size = 3043477, upload-time = "2025-10-30T02:55:11.182Z" }, + { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b4/a5375cda5b54cb95ee9b836930fea30ae5a8f14aa97da7821722323d979b/psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03", size = 2713894, upload-time = "2025-10-10T11:11:48.775Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/3c/94/c1777c355bc560992af848d98216148be5f1be001af06e06fc49cbded578/psycopg2_binary-2.9.11-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a1cf393f1cdaf6a9b57c0a719a1068ba1069f022a59b8b1fe44b006745b59757", size = 3983083, upload-time = "2025-10-30T02:55:15.73Z" }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/ea/d3390e6696276078bd01b2ece417deac954dfdd552d2edc3d03204416c0c/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:edcb3aeb11cb4bf13a2af3c53a15b3d612edeb6409047ea0b5d6a21a9d744b34", size = 3044641, upload-time = "2025-10-30T02:55:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/c07374c501b45f3579a9eb761cbf2604ddef3d96ad48679112c2c5aa9c25/psycopg2_binary-2.9.11-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84011ba3109e06ac412f95399b704d3d6950e386b7994475b231cf61eec2fc1f", size = 3983133, upload-time = "2025-10-30T02:55:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/50c3facc66bded9ada5cbc0de867499a703dc6bca6be03070b4e3b65da6c/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d526864e0f67f74937a8fce859bd56c979f5e2ec57ca7c627f5f1071ef7fee60", size = 3044712, upload-time = "2025-10-30T02:55:27.975Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e0/f8cc36eadd1b716ab36bb290618a3292e009867e5c97ce4aba908cb99644/psycopg2_binary-2.9.11-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e35b7abae2b0adab776add56111df1735ccc71406e56203515e228a8dc07089f", size = 3983184, upload-time = "2025-10-30T02:55:32.483Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, + { url = "https://files.pythonhosted.org/packages/97/77/21b0ea2e1a73aa5fa9222b2a6b8ba325c43c3a8d54272839c991f2345656/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:32770a4d666fbdafab017086655bcddab791d7cb260a16679cc5a7338b64343b", size = 3044737, upload-time = "2025-10-30T02:55:35.69Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/9a/23310166d960def5897e91fe20e5b724601b02a22e84ba1f94232c0b7f67/pyasn1-0.6.4.tar.gz", hash = "sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81", size = 151262, upload-time = "2026-07-09T01:12:33.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3b/6163796d69c3977d1e4287bea4a6979161cbbdd170ebb430511e8e1999ce/pyasn1-0.6.4-py3-none-any.whl", hash = "sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b", size = 84410, upload-time = "2026-07-09T01:12:32.92Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pybase64" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/b8/4ed5c7ad5ec15b08d35cc79ace6145d5c1ae426e46435f4987379439dfea/pybase64-1.4.3.tar.gz", hash = "sha256:c2ed274c9e0ba9c8f9c4083cfe265e66dd679126cd9c2027965d807352f3f053", size = 137272, upload-time = "2025-12-06T13:27:04.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/47/16d7af6fae7803f4c691856bc0d8d433ccf30e106432e2ef7707ee19a38a/pybase64-1.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f63aa7f29139b8a05ce5f97cdb7fad63d29071e5bdc8a638a343311fe996112a", size = 38241, upload-time = "2025-12-06T13:22:27.396Z" }, + { url = "https://files.pythonhosted.org/packages/4d/3e/268beb8d2240ab55396af4d1b45d2494935982212549b92a5f5b57079bd3/pybase64-1.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5943ec1ae87a8b4fe310905bb57205ea4330c75e2c628433a7d9dd52295b588", size = 31672, upload-time = "2025-12-06T13:22:28.854Z" }, + { url = "https://files.pythonhosted.org/packages/80/14/4365fa33222edcc46b6db4973f9e22bda82adfb6ab2a01afff591f1e41c8/pybase64-1.4.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5f2b8aef86f35cd5894c13681faf433a1fffc5b2e76544dcb5416a514a1a8347", size = 65978, upload-time = "2025-12-06T13:22:30.191Z" }, + { url = "https://files.pythonhosted.org/packages/1c/22/e89739d8bc9b96c68ead44b4eec42fe555683d9997e4ba65216d384920fc/pybase64-1.4.3-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6ec7e53dd09b0a8116ccf5c3265c7c7fce13c980747525be76902aef36a514a", size = 68903, upload-time = "2025-12-06T13:22:31.29Z" }, + { url = "https://files.pythonhosted.org/packages/77/e1/7e59a19f8999cdefe9eb0d56bfd701dd38263b0f6fb4a4d29fce165a1b36/pybase64-1.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7528604cd69c538e1dbaafded46e9e4915a2adcd6f2a60fcef6390d87ca922ea", size = 57516, upload-time = "2025-12-06T13:22:32.395Z" }, + { url = "https://files.pythonhosted.org/packages/42/ad/f47dc7e6fe32022b176868b88b671a32dab389718c8ca905cab79280aaaf/pybase64-1.4.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:4ec645f32b50593879031e09158f8681a1db9f5df0f72af86b3969a1c5d1fa2b", size = 54533, upload-time = "2025-12-06T13:22:33.457Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/7ab312b5a324833953b00e47b23eb4f83d45bd5c5c854b4b4e51b2a0cf5b/pybase64-1.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:634a000c5b3485ccc18bb9b244e0124f74b6fbc7f43eade815170237a7b34c64", size = 57187, upload-time = "2025-12-06T13:22:34.566Z" }, + { url = "https://files.pythonhosted.org/packages/2c/84/80acab1fcbaaae103e6b862ef5019192c8f2cd8758433595a202179a0d1d/pybase64-1.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:309ea32ad07639a485580af1be0ad447a434deb1924e76adced63ac2319cfe15", size = 57730, upload-time = "2025-12-06T13:22:35.581Z" }, + { url = "https://files.pythonhosted.org/packages/1f/24/84256d472400ea3163d7d69c44bb7e2e1027f0f1d4d20c47629a7dc4578e/pybase64-1.4.3-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:d10d517566b748d3f25f6ac7162af779360c1c6426ad5f962927ee205990d27c", size = 53036, upload-time = "2025-12-06T13:22:36.621Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0f/33aecbed312ee0431798a73fa25e00dedbffdd91389ee23121fed397c550/pybase64-1.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a74cc0f4d835400857cc5c6d27ec854f7949491e07a04e6d66e2137812831f4c", size = 56321, upload-time = "2025-12-06T13:22:37.7Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1c/a341b050746658cbec8cab3c733aeb3ef52ce8f11e60d0d47adbdf729ebf/pybase64-1.4.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1b591d774ac09d5eb73c156a03277cb271438fbd8042bae4109ff3a827cd218c", size = 50114, upload-time = "2025-12-06T13:22:38.752Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d3/f7e6680ae6dc4ddff39112ad66e0fa6b2ec346e73881bafc08498c560bc0/pybase64-1.4.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5eb588d35a04302ef6157d17db62354a787ac6f8b1585dd0b90c33d63a97a550", size = 66570, upload-time = "2025-12-06T13:22:40.221Z" }, + { url = "https://files.pythonhosted.org/packages/4c/71/774748eecc7fe23869b7e5df028e3c4c2efa16b506b83ea3fa035ea95dc2/pybase64-1.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:df8b122d5be2c96962231cc4831d9c2e1eae6736fb12850cec4356d8b06fe6f8", size = 55700, upload-time = "2025-12-06T13:22:41.289Z" }, + { url = "https://files.pythonhosted.org/packages/b3/91/dd15075bb2fe0086193e1cd4bad80a43652c38d8a572f9218d46ba721802/pybase64-1.4.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:31b7a85c661fc591bbcce82fb8adaebe2941e6a83b08444b0957b77380452a4b", size = 52491, upload-time = "2025-12-06T13:22:42.628Z" }, + { url = "https://files.pythonhosted.org/packages/7b/27/f357d63ea3774c937fc47160e040419ed528827aa3d4306d5ec9826259c0/pybase64-1.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e6d7beaae65979fef250e25e66cf81c68a8f81910bcda1a2f43297ab486a7e4e", size = 53957, upload-time = "2025-12-06T13:22:44.615Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c3/243693771701a54e67ff5ccbf4c038344f429613f5643169a7befc51f007/pybase64-1.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4a6276bc3a3962d172a2b5aba544d89881c4037ea954517b86b00892c703d007", size = 68422, upload-time = "2025-12-06T13:22:45.641Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/f987081bf6bc1d1eda3012dae1b06ad427732ef9933a632cb8b58f9917f8/pybase64-1.4.3-cp310-cp310-win32.whl", hash = "sha256:4bdd07ef017515204ee6eaab17e1ad05f83c0ccb5af8ae24a0fe6d9cb5bb0b7a", size = 33622, upload-time = "2025-12-06T13:22:47.348Z" }, + { url = "https://files.pythonhosted.org/packages/79/28/c169a769fe90128f16d394aad87b2096dd4bf2f035ae0927108a46b617df/pybase64-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:5db0b6bbda15110db2740c61970a8fda3bf9c93c3166a3f57f87c7865ed1125c", size = 35799, upload-time = "2025-12-06T13:22:48.731Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f2/bdbe6af0bd4f3fe5bc70e77ead7f7d523bb9d3ca3ad50ac42b9adbb9ca14/pybase64-1.4.3-cp310-cp310-win_arm64.whl", hash = "sha256:f96367dfc82598569aa02b1103ebd419298293e59e1151abda2b41728703284b", size = 31158, upload-time = "2025-12-06T13:22:50.021Z" }, + { url = "https://files.pythonhosted.org/packages/2b/63/21e981e9d3f1f123e0b0ee2130112b1956cad9752309f574862c7ae77c08/pybase64-1.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:70b0d4a4d54e216ce42c2655315378b8903933ecfa32fced453989a92b4317b2", size = 38237, upload-time = "2025-12-06T13:22:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/92/fb/3f448e139516404d2a3963915cc10dc9dde7d3a67de4edba2f827adfef17/pybase64-1.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8127f110cdee7a70e576c5c9c1d4e17e92e76c191869085efbc50419f4ae3c72", size = 31673, upload-time = "2025-12-06T13:22:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/bb06a5b9885e7d853ac1e801c4d8abfdb4c8506deee33e53d55aa6690e67/pybase64-1.4.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f9ef0388878bc15a084bd9bf73ec1b2b4ee513d11009b1506375e10a7aae5032", size = 68331, upload-time = "2025-12-06T13:22:54.197Z" }, + { url = "https://files.pythonhosted.org/packages/64/15/8d60b9ec5e658185fc2ee3333e01a6e30d717cf677b24f47cbb3a859d13c/pybase64-1.4.3-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95a57cccf106352a72ed8bc8198f6820b16cc7d55aa3867a16dea7011ae7c218", size = 71370, upload-time = "2025-12-06T13:22:55.517Z" }, + { url = "https://files.pythonhosted.org/packages/ac/29/a3e5c1667cc8c38d025a4636855de0fc117fc62e2afeb033a3c6f12c6a22/pybase64-1.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cd1c47dfceb9c7bd3de210fb4e65904053ed2d7c9dce6d107f041ff6fbd7e21", size = 59834, upload-time = "2025-12-06T13:22:56.682Z" }, + { url = "https://files.pythonhosted.org/packages/a9/00/8ffcf9810bd23f3984698be161cf7edba656fd639b818039a7be1d6405d4/pybase64-1.4.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9fe9922698f3e2f72874b26890d53a051c431d942701bb3a37aae94da0b12107", size = 56652, upload-time = "2025-12-06T13:22:57.724Z" }, + { url = "https://files.pythonhosted.org/packages/81/62/379e347797cdea4ab686375945bc77ad8d039c688c0d4d0cfb09d247beb9/pybase64-1.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:af5f4bd29c86b59bb4375e0491d16ec8a67548fa99c54763aaedaf0b4b5a6632", size = 59382, upload-time = "2025-12-06T13:22:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f2/9338ffe2f487086f26a2c8ca175acb3baa86fce0a756ff5670a0822bb877/pybase64-1.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c302f6ca7465262908131411226e02100f488f531bb5e64cb901aa3f439bccd9", size = 59990, upload-time = "2025-12-06T13:23:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a4/85a6142b65b4df8625b337727aa81dc199642de3d09677804141df6ee312/pybase64-1.4.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:2f3f439fa4d7fde164ebbbb41968db7d66b064450ab6017c6c95cef0afa2b349", size = 54923, upload-time = "2025-12-06T13:23:02.369Z" }, + { url = "https://files.pythonhosted.org/packages/ac/00/e40215d25624012bf5b7416ca37f168cb75f6dd15acdb91ea1f2ea4dc4e7/pybase64-1.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a23c6866551043f8b681a5e1e0d59469148b2920a3b4fc42b1275f25ea4217a", size = 58664, upload-time = "2025-12-06T13:23:03.378Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/d7e19a63e795c13837f2356268d95dc79d1180e756f57ced742a1e52fdeb/pybase64-1.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:56e6526f8565642abc5f84338cc131ce298a8ccab696b19bdf76fa6d7dc592ef", size = 52338, upload-time = "2025-12-06T13:23:04.458Z" }, + { url = "https://files.pythonhosted.org/packages/f2/32/3c746d7a310b69bdd9df77ffc85c41b80bce00a774717596f869b0d4a20e/pybase64-1.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6a792a8b9d866ffa413c9687d9b611553203753987a3a582d68cbc51cf23da45", size = 68993, upload-time = "2025-12-06T13:23:05.526Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b3/63cec68f9d6f6e4c0b438d14e5f1ef536a5fe63ce14b70733ac5e31d7ab8/pybase64-1.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:62ad29a5026bb22cfcd1ca484ec34b0a5ced56ddba38ceecd9359b2818c9c4f9", size = 58055, upload-time = "2025-12-06T13:23:06.931Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cb/7acf7c3c06f9692093c07f109668725dc37fb9a3df0fa912b50add645195/pybase64-1.4.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11b9d1d2d32ec358c02214363b8fc3651f6be7dd84d880ecd597a6206a80e121", size = 54430, upload-time = "2025-12-06T13:23:07.936Z" }, + { url = "https://files.pythonhosted.org/packages/33/39/4eb33ff35d173bfff4002e184ce8907f5d0a42d958d61cd9058ef3570179/pybase64-1.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0aebaa7f238caa0a0d373616016e2040c6c879ebce3ba7ab3c59029920f13640", size = 56272, upload-time = "2025-12-06T13:23:09.253Z" }, + { url = "https://files.pythonhosted.org/packages/19/97/a76d65c375a254e65b730c6f56bf528feca91305da32eceab8bcc08591e6/pybase64-1.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e504682b20c63c2b0c000e5f98a80ea867f8d97642e042a5a39818e44ba4d599", size = 70904, upload-time = "2025-12-06T13:23:10.336Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/8338b6d3da3c265002839e92af0a80d6db88385c313c73f103dfb800c857/pybase64-1.4.3-cp311-cp311-win32.whl", hash = "sha256:e9a8b81984e3c6fb1db9e1614341b0a2d98c0033d693d90c726677db1ffa3a4c", size = 33639, upload-time = "2025-12-06T13:23:11.9Z" }, + { url = "https://files.pythonhosted.org/packages/39/dc/32efdf2f5927e5449cc341c266a1bbc5fecd5319a8807d9c5405f76e6d02/pybase64-1.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:a90a8fa16a901fabf20de824d7acce07586e6127dc2333f1de05f73b1f848319", size = 35797, upload-time = "2025-12-06T13:23:13.174Z" }, + { url = "https://files.pythonhosted.org/packages/da/59/eda4f9cb0cbce5a45f0cd06131e710674f8123a4d570772c5b9694f88559/pybase64-1.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:61d87de5bc94d143622e94390ec3e11b9c1d4644fe9be3a81068ab0f91056f59", size = 31160, upload-time = "2025-12-06T13:23:15.696Z" }, + { url = "https://files.pythonhosted.org/packages/86/a7/efcaa564f091a2af7f18a83c1c4875b1437db56ba39540451dc85d56f653/pybase64-1.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:18d85e5ab8b986bb32d8446aca6258ed80d1bafe3603c437690b352c648f5967", size = 38167, upload-time = "2025-12-06T13:23:16.821Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/c7ad35adff2d272bf2930132db2b3eea8c44bb1b1f64eb9b2b8e57cde7b4/pybase64-1.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f5791a3491d116d0deaf4d83268f48792998519698f8751efb191eac84320e9", size = 31673, upload-time = "2025-12-06T13:23:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/43/1b/9a8cab0042b464e9a876d5c65fe5127445a2436da36fda64899b119b1a1b/pybase64-1.4.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f0b3f200c3e06316f6bebabd458b4e4bcd4c2ca26af7c0c766614d91968dee27", size = 68210, upload-time = "2025-12-06T13:23:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/62/f7/965b79ff391ad208b50e412b5d3205ccce372a2d27b7218ae86d5295b105/pybase64-1.4.3-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb632edfd132b3eaf90c39c89aa314beec4e946e210099b57d40311f704e11d4", size = 71599, upload-time = "2025-12-06T13:23:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/03/4b/a3b5175130b3810bbb8ccfa1edaadbd3afddb9992d877c8a1e2f274b476e/pybase64-1.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:356ef1d74648ce997f5a777cf8f1aefecc1c0b4fe6201e0ef3ec8a08170e1b54", size = 59922, upload-time = "2025-12-06T13:23:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/da/5d/c38d1572027fc601b62d7a407721688b04b4d065d60ca489912d6893e6cf/pybase64-1.4.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:c48361f90db32bacaa5518419d4eb9066ba558013aaf0c7781620279ecddaeb9", size = 56712, upload-time = "2025-12-06T13:23:22.77Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d4/4e04472fef485caa8f561d904d4d69210a8f8fc1608ea15ebd9012b92655/pybase64-1.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:702bcaa16ae02139d881aeaef5b1c8ffb4a3fae062fe601d1e3835e10310a517", size = 59300, upload-time = "2025-12-06T13:23:24.543Z" }, + { url = "https://files.pythonhosted.org/packages/86/e7/16e29721b86734b881d09b7e23dfd7c8408ad01a4f4c7525f3b1088e25ec/pybase64-1.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:53d0ffe1847b16b647c6413d34d1de08942b7724273dd57e67dcbdb10c574045", size = 60278, upload-time = "2025-12-06T13:23:25.608Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/18515f211d7c046be32070709a8efeeef8a0203de4fd7521e6b56404731b/pybase64-1.4.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:9a1792e8b830a92736dae58f0c386062eb038dfe8004fb03ba33b6083d89cd43", size = 54817, upload-time = "2025-12-06T13:23:26.633Z" }, + { url = "https://files.pythonhosted.org/packages/e7/be/14e29d8e1a481dbff151324c96dd7b5d2688194bb65dc8a00ca0e1ad1e86/pybase64-1.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d468b1b1ac5ad84875a46eaa458663c3721e8be5f155ade356406848d3701f6", size = 58611, upload-time = "2025-12-06T13:23:27.684Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8a/a2588dfe24e1bbd742a554553778ab0d65fdf3d1c9a06d10b77047d142aa/pybase64-1.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e97b7bdbd62e71898cd542a6a9e320d9da754ff3ebd02cb802d69087ee94d468", size = 52404, upload-time = "2025-12-06T13:23:28.714Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/afcda7445bebe0cbc38cafdd7813234cdd4fc5573ff067f1abf317bb0cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b33aeaa780caaa08ffda87fc584d5eab61e3d3bbb5d86ead02161dc0c20d04bc", size = 68817, upload-time = "2025-12-06T13:23:30.079Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3a/87c3201e555ed71f73e961a787241a2438c2bbb2ca8809c29ddf938a3157/pybase64-1.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c0efcf78f11cf866bed49caa7b97552bc4855a892f9cc2372abcd3ed0056f0d", size = 57854, upload-time = "2025-12-06T13:23:31.17Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7d/931c2539b31a7b375e7d595b88401eeb5bd6c5ce1059c9123f9b608aaa14/pybase64-1.4.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:66e3791f2ed725a46593f8bd2761ff37d01e2cdad065b1dceb89066f476e50c6", size = 54333, upload-time = "2025-12-06T13:23:32.422Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/537601e02cc01f27e9d75f440f1a6095b8df44fc28b1eef2cd739aea8cec/pybase64-1.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:72bb0b6bddadab26e1b069bb78e83092711a111a80a0d6b9edcb08199ad7299b", size = 56492, upload-time = "2025-12-06T13:23:33.515Z" }, + { url = "https://files.pythonhosted.org/packages/96/97/2a2e57acf8f5c9258d22aba52e71f8050e167b29ed2ee1113677c1b600c1/pybase64-1.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5b3365dbcbcdb0a294f0f50af0c0a16b27a232eddeeb0bceeefd844ef30d2a23", size = 70974, upload-time = "2025-12-06T13:23:36.27Z" }, + { url = "https://files.pythonhosted.org/packages/75/2e/a9e28941c6dab6f06e6d3f6783d3373044be9b0f9a9d3492c3d8d2260ac0/pybase64-1.4.3-cp312-cp312-win32.whl", hash = "sha256:7bca1ed3a5df53305c629ca94276966272eda33c0d71f862d2d3d043f1e1b91a", size = 33686, upload-time = "2025-12-06T13:23:37.848Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/507ab649d8c3512c258819c51d25c45d6e29d9ca33992593059e7b646a33/pybase64-1.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:9f2da8f56d9b891b18b4daf463a0640eae45a80af548ce435be86aa6eff3603b", size = 35833, upload-time = "2025-12-06T13:23:38.877Z" }, + { url = "https://files.pythonhosted.org/packages/bc/8a/6eba66cd549a2fc74bb4425fd61b839ba0ab3022d3c401b8a8dc2cc00c7a/pybase64-1.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:0631d8a2d035de03aa9bded029b9513e1fee8ed80b7ddef6b8e9389ffc445da0", size = 31185, upload-time = "2025-12-06T13:23:39.908Z" }, + { url = "https://files.pythonhosted.org/packages/3a/50/b7170cb2c631944388fe2519507fe3835a4054a6a12a43f43781dae82be1/pybase64-1.4.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:ea4b785b0607d11950b66ce7c328f452614aefc9c6d3c9c28bae795dc7f072e1", size = 33901, upload-time = "2025-12-06T13:23:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/48/8b/69f50578e49c25e0a26e3ee72c39884ff56363344b79fc3967f5af420ed6/pybase64-1.4.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:6a10b6330188c3026a8b9c10e6b9b3f2e445779cf16a4c453d51a072241c65a2", size = 40807, upload-time = "2025-12-06T13:23:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/5c/8d/20b68f11adfc4c22230e034b65c71392e3e338b413bf713c8945bd2ccfb3/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:27fdff227a0c0e182e0ba37a99109645188978b920dfb20d8b9c17eeee370d0d", size = 30932, upload-time = "2025-12-06T13:23:43.348Z" }, + { url = "https://files.pythonhosted.org/packages/f7/79/b1b550ac6bff51a4880bf6e089008b2e1ca16f2c98db5e039a08ac3ad157/pybase64-1.4.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2a8204f1fdfec5aa4184249b51296c0de95445869920c88123978304aad42df1", size = 31394, upload-time = "2025-12-06T13:23:44.317Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/b5d7c5932bf64ee1ec5da859fbac981930b6a55d432a603986c7f509c838/pybase64-1.4.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:874fc2a3777de6baf6aa921a7aa73b3be98295794bea31bd80568a963be30767", size = 38078, upload-time = "2025-12-06T13:23:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/e66fe373bce717c6858427670736d54297938dad61c5907517ab4106bd90/pybase64-1.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2dc64a94a9d936b8e3449c66afabbaa521d3cc1a563d6bbaaa6ffa4535222e4b", size = 38158, upload-time = "2025-12-06T13:23:46.872Z" }, + { url = "https://files.pythonhosted.org/packages/80/a9/b806ed1dcc7aed2ea3dd4952286319e6f3a8b48615c8118f453948e01999/pybase64-1.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e48f86de1c145116ccf369a6e11720ce696c2ec02d285f440dfb57ceaa0a6cb4", size = 31672, upload-time = "2025-12-06T13:23:47.88Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c9/24b3b905cf75e23a9a4deaf203b35ffcb9f473ac0e6d8257f91a05dfce62/pybase64-1.4.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1d45c8fe8fe82b65c36b227bb4a2cf623d9ada16bed602ce2d3e18c35285b72a", size = 68244, upload-time = "2025-12-06T13:23:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/f8/cd/d15b0c3e25e5859fab0416dc5b96d34d6bd2603c1c96a07bb2202b68ab92/pybase64-1.4.3-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad70c26ba091d8f5167e9d4e1e86a0483a5414805cdb598a813db635bd3be8b8", size = 71620, upload-time = "2025-12-06T13:23:50.081Z" }, + { url = "https://files.pythonhosted.org/packages/0d/31/4ca953cc3dcde2b3711d6bfd70a6f4ad2ca95a483c9698076ba605f1520f/pybase64-1.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e98310b7c43145221e7194ac9fa7fffc84763c87bfc5e2f59f9f92363475bdc1", size = 59930, upload-time = "2025-12-06T13:23:51.68Z" }, + { url = "https://files.pythonhosted.org/packages/60/55/e7f7bdcd0fd66e61dda08db158ffda5c89a306bbdaaf5a062fbe4e48f4a1/pybase64-1.4.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:398685a76034e91485a28aeebcb49e64cd663212fd697b2497ac6dfc1df5e671", size = 56425, upload-time = "2025-12-06T13:23:52.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/65/b592c7f921e51ca1aca3af5b0d201a98666d0a36b930ebb67e7c2ed27395/pybase64-1.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7e46400a6461187ccb52ed75b0045d937529e801a53a9cd770b350509f9e4d50", size = 59327, upload-time = "2025-12-06T13:23:53.856Z" }, + { url = "https://files.pythonhosted.org/packages/23/95/1613d2fb82dbb1548595ad4179f04e9a8451bfa18635efce18b631eabe3f/pybase64-1.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1b62b9f2f291d94f5e0b76ab499790b7dcc78a009d4ceea0b0428770267484b6", size = 60294, upload-time = "2025-12-06T13:23:54.937Z" }, + { url = "https://files.pythonhosted.org/packages/9d/73/40431f37f7d1b3eab4673e7946ff1e8f5d6bd425ec257e834dae8a6fc7b0/pybase64-1.4.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:f30ceb5fa4327809dede614be586efcbc55404406d71e1f902a6fdcf322b93b2", size = 54858, upload-time = "2025-12-06T13:23:56.031Z" }, + { url = "https://files.pythonhosted.org/packages/a7/84/f6368bcaf9f743732e002a9858646fd7a54f428490d427dd6847c5cfe89e/pybase64-1.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0d5f18ed53dfa1d4cf8b39ee542fdda8e66d365940e11f1710989b3cf4a2ed66", size = 58629, upload-time = "2025-12-06T13:23:57.12Z" }, + { url = "https://files.pythonhosted.org/packages/43/75/359532f9adb49c6b546cafc65c46ed75e2ccc220d514ba81c686fbd83965/pybase64-1.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:119d31aa4b58b85a8ebd12b63c07681a138c08dfc2fe5383459d42238665d3eb", size = 52448, upload-time = "2025-12-06T13:23:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/92/6c/ade2ba244c3f33ed920a7ed572ad772eb0b5f14480b72d629d0c9e739a40/pybase64-1.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3cf0218b0e2f7988cf7d738a73b6a1d14f3be6ce249d7c0f606e768366df2cce", size = 68841, upload-time = "2025-12-06T13:23:59.886Z" }, + { url = "https://files.pythonhosted.org/packages/a0/51/b345139cd236be382f2d4d4453c21ee6299e14d2f759b668e23080f8663f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:12f4ee5e988bc5c0c1106b0d8fc37fb0508f12dab76bac1b098cb500d148da9d", size = 57910, upload-time = "2025-12-06T13:24:00.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b8/9f84bdc4f1c4f0052489396403c04be2f9266a66b70c776001eaf0d78c1f/pybase64-1.4.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:937826bc7b6b95b594a45180e81dd4d99bd4dd4814a443170e399163f7ff3fb6", size = 54335, upload-time = "2025-12-06T13:24:02.046Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c7/be63b617d284de46578a366da77ede39c8f8e815ed0d82c7c2acca560fab/pybase64-1.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:88995d1460971ef80b13e3e007afbe4b27c62db0508bc7250a2ab0a0b4b91362", size = 56486, upload-time = "2025-12-06T13:24:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/5e/96/f252c8f9abd6ded3ef1ccd3cdbb8393a33798007f761b23df8de1a2480e6/pybase64-1.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:72326fe163385ed3e1e806dd579d47fde5d8a59e51297a60fc4e6cbc1b4fc4ed", size = 70978, upload-time = "2025-12-06T13:24:04.221Z" }, + { url = "https://files.pythonhosted.org/packages/af/51/0f5714af7aeef96e30f968e4371d75ad60558aaed3579d7c6c8f1c43c18a/pybase64-1.4.3-cp313-cp313-win32.whl", hash = "sha256:b1623730c7892cf5ed0d6355e375416be6ef8d53ab9b284f50890443175c0ac3", size = 33684, upload-time = "2025-12-06T13:24:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ad/0cea830a654eb08563fb8214150ef57546ece1cc421c09035f0e6b0b5ea9/pybase64-1.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:8369887590f1646a5182ca2fb29252509da7ae31d4923dbb55d3e09da8cc4749", size = 35832, upload-time = "2025-12-06T13:24:06.35Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/eec2a8214989c751bc7b4cad1860eb2c6abf466e76b77508c0f488c96a37/pybase64-1.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:860b86bca71e5f0237e2ab8b2d9c4c56681f3513b1bf3e2117290c1963488390", size = 31175, upload-time = "2025-12-06T13:24:07.419Z" }, + { url = "https://files.pythonhosted.org/packages/db/c9/e23463c1a2913686803ef76b1a5ae7e6fac868249a66e48253d17ad7232c/pybase64-1.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eb51db4a9c93215135dccd1895dca078e8785c357fabd983c9f9a769f08989a9", size = 38497, upload-time = "2025-12-06T13:24:08.873Z" }, + { url = "https://files.pythonhosted.org/packages/71/83/343f446b4b7a7579bf6937d2d013d82f1a63057cf05558e391ab6039d7db/pybase64-1.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a03ef3f529d85fd46b89971dfb00c634d53598d20ad8908fb7482955c710329d", size = 32076, upload-time = "2025-12-06T13:24:09.975Z" }, + { url = "https://files.pythonhosted.org/packages/46/fc/cb64964c3b29b432f54d1bce5e7691d693e33bbf780555151969ffd95178/pybase64-1.4.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2e745f2ce760c6cf04d8a72198ef892015ddb89f6ceba489e383518ecbdb13ab", size = 72317, upload-time = "2025-12-06T13:24:11.129Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b7/fab2240da6f4e1ad46f71fa56ec577613cf5df9dce2d5b4cfaa4edd0e365/pybase64-1.4.3-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fac217cd9de8581a854b0ac734c50fd1fa4b8d912396c1fc2fce7c230efe3a7", size = 75534, upload-time = "2025-12-06T13:24:12.433Z" }, + { url = "https://files.pythonhosted.org/packages/91/3b/3e2f2b6e68e3d83ddb9fa799f3548fb7449765daec9bbd005a9fbe296d7f/pybase64-1.4.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:da1ee8fa04b283873de2d6e8fa5653e827f55b86bdf1a929c5367aaeb8d26f8a", size = 65399, upload-time = "2025-12-06T13:24:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/6b/08/476ac5914c3b32e0274a2524fc74f01cbf4f4af4513d054e41574eb018f6/pybase64-1.4.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:b0bf8e884ee822ca7b1448eeb97fa131628fe0ff42f60cae9962789bd562727f", size = 60487, upload-time = "2025-12-06T13:24:15.177Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b8/618a92915330cc9cba7880299b546a1d9dab1a21fd6c0292ee44a4fe608c/pybase64-1.4.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1bf749300382a6fd1f4f255b183146ef58f8e9cb2f44a077b3a9200dfb473a77", size = 63959, upload-time = "2025-12-06T13:24:16.854Z" }, + { url = "https://files.pythonhosted.org/packages/a5/52/af9d8d051652c3051862c442ec3861259c5cdb3fc69774bc701470bd2a59/pybase64-1.4.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:153a0e42329b92337664cfc356f2065248e6c9a1bd651bbcd6dcaf15145d3f06", size = 64874, upload-time = "2025-12-06T13:24:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/e4/51/5381a7adf1f381bd184d33203692d3c57cf8ae9f250f380c3fecbdbe554b/pybase64-1.4.3-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:86ee56ac7f2184ca10217ed1c655c1a060273e233e692e9086da29d1ae1768db", size = 58572, upload-time = "2025-12-06T13:24:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f0/578ee4ffce5818017de4fdf544e066c225bc435e73eb4793cde28a689d0b/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0e71a4db76726bf830b47477e7d830a75c01b2e9b01842e787a0836b0ba741e3", size = 63636, upload-time = "2025-12-06T13:24:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ad/8ae94814bf20159ea06310b742433e53d5820aa564c9fdf65bf2d79f8799/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2ba7799ec88540acd9861b10551d24656ca3c2888ecf4dba2ee0a71544a8923f", size = 56193, upload-time = "2025-12-06T13:24:21.559Z" }, + { url = "https://files.pythonhosted.org/packages/d1/31/6438cfcc3d3f0fa84d229fa125c243d5094e72628e525dfefadf3bcc6761/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2860299e4c74315f5951f0cf3e72ba0f201c3356c8a68f95a3ab4e620baf44e9", size = 72655, upload-time = "2025-12-06T13:24:22.673Z" }, + { url = "https://files.pythonhosted.org/packages/a3/0d/2bbc9e9c3fc12ba8a6e261482f03a544aca524f92eae0b4908c0a10ba481/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:bb06015db9151f0c66c10aae8e3603adab6b6cd7d1f7335a858161d92fc29618", size = 62471, upload-time = "2025-12-06T13:24:23.8Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0b/34d491e7f49c1dbdb322ea8da6adecda7c7cd70b6644557c6e4ca5c6f7c7/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:242512a070817272865d37c8909059f43003b81da31f616bb0c391ceadffe067", size = 58119, upload-time = "2025-12-06T13:24:24.994Z" }, + { url = "https://files.pythonhosted.org/packages/ce/17/c21d0cde2a6c766923ae388fc1f78291e1564b0d38c814b5ea8a0e5e081c/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5d8277554a12d3e3eed6180ebda62786bf9fc8d7bb1ee00244258f4a87ca8d20", size = 60791, upload-time = "2025-12-06T13:24:26.046Z" }, + { url = "https://files.pythonhosted.org/packages/92/b2/eaa67038916a48de12b16f4c384bcc1b84b7ec731b23613cb05f27673294/pybase64-1.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f40b7ddd698fc1e13a4b64fbe405e4e0e1279e8197e37050e24154655f5f7c4e", size = 74701, upload-time = "2025-12-06T13:24:27.466Z" }, + { url = "https://files.pythonhosted.org/packages/42/10/abb7757c330bb869ebb95dab0c57edf5961ffbd6c095c8209cbbf75d117d/pybase64-1.4.3-cp313-cp313t-win32.whl", hash = "sha256:46d75c9387f354c5172582a9eaae153b53a53afeb9c19fcf764ea7038be3bd8b", size = 33965, upload-time = "2025-12-06T13:24:28.548Z" }, + { url = "https://files.pythonhosted.org/packages/63/a0/2d4e5a59188e9e6aed0903d580541aaea72dcbbab7bf50fb8b83b490b6c3/pybase64-1.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:d7344625591d281bec54e85cbfdab9e970f6219cac1570f2aa140b8c942ccb81", size = 36207, upload-time = "2025-12-06T13:24:29.646Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/95b902e8f567b4d4b41df768ccc438af618f8d111e54deaf57d2df46bd76/pybase64-1.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:28a3c60c55138e0028313f2eccd321fec3c4a0be75e57a8d3eb883730b1b0880", size = 31505, upload-time = "2025-12-06T13:24:30.687Z" }, + { url = "https://files.pythonhosted.org/packages/e4/80/4bd3dff423e5a91f667ca41982dc0b79495b90ec0c0f5d59aca513e50f8c/pybase64-1.4.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:015bb586a1ea1467f69d57427abe587469392215f59db14f1f5c39b52fdafaf5", size = 33835, upload-time = "2025-12-06T13:24:31.767Z" }, + { url = "https://files.pythonhosted.org/packages/45/60/a94d94cc1e3057f602e0b483c9ebdaef40911d84a232647a2fe593ab77bb/pybase64-1.4.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:d101e3a516f837c3dcc0e5a0b7db09582ebf99ed670865223123fb2e5839c6c0", size = 40673, upload-time = "2025-12-06T13:24:32.82Z" }, + { url = "https://files.pythonhosted.org/packages/e3/71/cf62b261d431857e8e054537a5c3c24caafa331de30daede7b2c6c558501/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8f183ac925a48046abe047360fe3a1b28327afb35309892132fe1915d62fb282", size = 30939, upload-time = "2025-12-06T13:24:34.001Z" }, + { url = "https://files.pythonhosted.org/packages/24/3e/d12f92a3c1f7c6ab5d53c155bff9f1084ba997a37a39a4f781ccba9455f3/pybase64-1.4.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30bf3558e24dcce4da5248dcf6d73792adfcf4f504246967e9db155be4c439ad", size = 31401, upload-time = "2025-12-06T13:24:35.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3d/9c27440031fea0d05146f8b70a460feb95d8b4e3d9ca8f45c972efb4c3d3/pybase64-1.4.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a674b419de318d2ce54387dd62646731efa32b4b590907800f0bd40675c1771d", size = 38075, upload-time = "2025-12-06T13:24:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d4/6c0e0cf0efd53c254173fbcd84a3d8fcbf5e0f66622473da425becec32a5/pybase64-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:720104fd7303d07bac302be0ff8f7f9f126f2f45c1edb4f48fdb0ff267e69fe1", size = 38257, upload-time = "2025-12-06T13:24:38.049Z" }, + { url = "https://files.pythonhosted.org/packages/50/eb/27cb0b610d5cd70f5ad0d66c14ad21c04b8db930f7139818e8fbdc14df4d/pybase64-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:83f1067f73fa5afbc3efc0565cecc6ed53260eccddef2ebe43a8ce2b99ea0e0a", size = 31685, upload-time = "2025-12-06T13:24:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/b136a4b65e5c94ff06217f7726478df3f31ab1c777c2c02cf698e748183f/pybase64-1.4.3-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b51204d349a4b208287a8aa5b5422be3baa88abf6cc8ff97ccbda34919bbc857", size = 68460, upload-time = "2025-12-06T13:24:41.735Z" }, + { url = "https://files.pythonhosted.org/packages/68/6d/84ce50e7ee1ae79984d689e05a9937b2460d4efa1e5b202b46762fb9036c/pybase64-1.4.3-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30f2fd53efecbdde4bdca73a872a68dcb0d1bf8a4560c70a3e7746df973e1ef3", size = 71688, upload-time = "2025-12-06T13:24:42.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/6743e420416c3ff1b004041c85eb0ebd9c50e9cf05624664bfa1dc8b5625/pybase64-1.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0932b0c5cfa617091fd74f17d24549ce5de3628791998c94ba57be808078eeaf", size = 60040, upload-time = "2025-12-06T13:24:44.37Z" }, + { url = "https://files.pythonhosted.org/packages/3b/68/733324e28068a89119af2921ce548e1c607cc5c17d354690fc51c302e326/pybase64-1.4.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:acb61f5ab72bec808eb0d4ce8b87ec9f38d7d750cb89b1371c35eb8052a29f11", size = 56478, upload-time = "2025-12-06T13:24:45.815Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9e/f3f4aa8cfe3357a3cdb0535b78eb032b671519d3ecc08c58c4c6b72b5a91/pybase64-1.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:2bc2d5bc15168f5c04c53bdfe5a1e543b2155f456ed1e16d7edce9ce73842021", size = 59463, upload-time = "2025-12-06T13:24:46.938Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/53286038e1f0df1cf58abcf4a4a91b0f74ab44539c2547b6c31001ddd054/pybase64-1.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8a7bc3cd23880bdca59758bcdd6f4ef0674f2393782763910a7466fab35ccb98", size = 60360, upload-time = "2025-12-06T13:24:48.039Z" }, + { url = "https://files.pythonhosted.org/packages/00/9a/5cc6ce95db2383d27ff4d790b8f8b46704d360d701ab77c4f655bcfaa6a7/pybase64-1.4.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ad15acf618880d99792d71e3905b0e2508e6e331b76a1b34212fa0f11e01ad28", size = 54999, upload-time = "2025-12-06T13:24:49.547Z" }, + { url = "https://files.pythonhosted.org/packages/64/e7/c3c1d09c3d7ae79e3aa1358c6d912d6b85f29281e47aa94fc0122a415a2f/pybase64-1.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448158d417139cb4851200e5fee62677ae51f56a865d50cda9e0d61bda91b116", size = 58736, upload-time = "2025-12-06T13:24:50.641Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/0baa08e3d8119b15b588c39f0d39fd10472f0372e3c54ca44649cbefa256/pybase64-1.4.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9058c49b5a2f3e691b9db21d37eb349e62540f9f5fc4beabf8cbe3c732bead86", size = 52298, upload-time = "2025-12-06T13:24:51.791Z" }, + { url = "https://files.pythonhosted.org/packages/00/87/fc6f11474a1de7e27cd2acbb8d0d7508bda3efa73dfe91c63f968728b2a3/pybase64-1.4.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ce561724f6522907a66303aca27dce252d363fcd85884972d348f4403ba3011a", size = 69049, upload-time = "2025-12-06T13:24:53.253Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/7fb5566f669ac18b40aa5fc1c438e24df52b843c1bdc5da47d46d4c1c630/pybase64-1.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:63316560a94ac449fe86cb8b9e0a13714c659417e92e26a5cbf085cd0a0c838d", size = 57952, upload-time = "2025-12-06T13:24:54.342Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/ceb949232dbbd3ec4ee0190d1df4361296beceee9840390a63df8bc31784/pybase64-1.4.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7ecd796f2ac0be7b73e7e4e232b8c16422014de3295d43e71d2b19fd4a4f5368", size = 54484, upload-time = "2025-12-06T13:24:55.774Z" }, + { url = "https://files.pythonhosted.org/packages/a7/69/659f3c8e6a5d7b753b9c42a4bd9c42892a0f10044e9c7351a4148d413a33/pybase64-1.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d01e102a12fb2e1ed3dc11611c2818448626637857ec3994a9cf4809dfd23477", size = 56542, upload-time = "2025-12-06T13:24:57Z" }, + { url = "https://files.pythonhosted.org/packages/85/2c/29c9e6c9c82b72025f9676f9e82eb1fd2339ad038cbcbf8b9e2ac02798fc/pybase64-1.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ebff797a93c2345f22183f454fd8607a34d75eca5a3a4a969c1c75b304cee39d", size = 71045, upload-time = "2025-12-06T13:24:58.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/84/5a3dce8d7a0040a5c0c14f0fe1311cd8db872913fa04438071b26b0dac04/pybase64-1.4.3-cp314-cp314-win32.whl", hash = "sha256:28b2a1bb0828c0595dc1ea3336305cd97ff85b01c00d81cfce4f92a95fb88f56", size = 34200, upload-time = "2025-12-06T13:24:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/57/bc/ce7427c12384adee115b347b287f8f3cf65860b824d74fe2c43e37e81c1f/pybase64-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:33338d3888700ff68c3dedfcd49f99bfc3b887570206130926791e26b316b029", size = 36323, upload-time = "2025-12-06T13:25:01.708Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1b/2b8ffbe9a96eef7e3f6a5a7be75995eebfb6faaedc85b6da6b233e50c778/pybase64-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:62725669feb5acb186458da2f9353e88ae28ef66bb9c4c8d1568b12a790dfa94", size = 31584, upload-time = "2025-12-06T13:25:02.801Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d8/6824c2e6fb45b8fa4e7d92e3c6805432d5edc7b855e3e8e1eedaaf6efb7c/pybase64-1.4.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:153fe29be038948d9372c3e77ae7d1cab44e4ba7d9aaf6f064dbeea36e45b092", size = 38601, upload-time = "2025-12-06T13:25:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e5/10d2b3a4ad3a4850be2704a2f70cd9c0cf55725c8885679872d3bc846c67/pybase64-1.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7fe3decaa7c4a9e162327ec7bd81ce183d2b16f23c6d53b606649c6e0203e9e", size = 32078, upload-time = "2025-12-06T13:25:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/43/04/8b15c34d3c2282f1c1b0850f1113a249401b618a382646a895170bc9b5e7/pybase64-1.4.3-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a5ae04ea114c86eb1da1f6e18d75f19e3b5ae39cb1d8d3cd87c29751a6a22780", size = 72474, upload-time = "2025-12-06T13:25:06.434Z" }, + { url = "https://files.pythonhosted.org/packages/42/00/f34b4d11278f8fdc68bc38f694a91492aa318f7c6f1bd7396197ac0f8b12/pybase64-1.4.3-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1755b3dce3a2a5c7d17ff6d4115e8bee4a1d5aeae74469db02e47c8f477147da", size = 75706, upload-time = "2025-12-06T13:25:07.636Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5d/71747d4ad7fe16df4c4c852bdbdeb1f2cf35677b48d7c34d3011a7a6ad3a/pybase64-1.4.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb852f900e27ffc4ec1896817535a0fa19610ef8875a096b59f21d0aa42ff172", size = 65589, upload-time = "2025-12-06T13:25:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/49/b1/d1e82bd58805bb5a3a662864800bab83a83a36ba56e7e3b1706c708002a5/pybase64-1.4.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:9cf21ea8c70c61eddab3421fbfce061fac4f2fb21f7031383005a1efdb13d0b9", size = 60670, upload-time = "2025-12-06T13:25:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/15/67/16c609b7a13d1d9fc87eca12ba2dce5e67f949eeaab61a41bddff843cbb0/pybase64-1.4.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:afff11b331fdc27692fc75e85ae083340a35105cea1a3c4552139e2f0e0d174f", size = 64194, upload-time = "2025-12-06T13:25:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/37bc724e42960f0106c2d33dc957dcec8f760c91a908cc6c0df7718bc1a8/pybase64-1.4.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9a5143df542c1ce5c1f423874b948c4d689b3f05ec571f8792286197a39ba02", size = 64984, upload-time = "2025-12-06T13:25:12.645Z" }, + { url = "https://files.pythonhosted.org/packages/6e/66/b2b962a6a480dd5dae3029becf03ea1a650d326e39bf1c44ea3db78bb010/pybase64-1.4.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:d62e9861019ad63624b4a7914dff155af1cc5d6d79df3be14edcaedb5fdad6f9", size = 58750, upload-time = "2025-12-06T13:25:13.848Z" }, + { url = "https://files.pythonhosted.org/packages/2b/15/9b6d711035e29b18b2e1c03d47f41396d803d06ef15b6c97f45b75f73f04/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84cfd4d92668ef5766cc42a9c9474b88960ac2b860767e6e7be255c6fddbd34a", size = 63816, upload-time = "2025-12-06T13:25:15.356Z" }, + { url = "https://files.pythonhosted.org/packages/b4/21/e2901381ed0df62e2308380f30d9c4d87d6b74e33a84faed3478d33a7197/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60fc025437f9a7c2cc45e0c19ed68ed08ba672be2c5575fd9d98bdd8f01dd61f", size = 56348, upload-time = "2025-12-06T13:25:16.559Z" }, + { url = "https://files.pythonhosted.org/packages/c4/16/3d788388a178a0407aa814b976fe61bfa4af6760d9aac566e59da6e4a8b4/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edc8446196f04b71d3af76c0bd1fe0a45066ac5bffecca88adb9626ee28c266f", size = 72842, upload-time = "2025-12-06T13:25:18.055Z" }, + { url = "https://files.pythonhosted.org/packages/a6/63/c15b1f8bd47ea48a5a2d52a4ec61f037062932ea6434ab916107b58e861e/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e99f6fa6509c037794da57f906ade271f52276c956d00f748e5b118462021d48", size = 62651, upload-time = "2025-12-06T13:25:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b8/f544a2e37c778d59208966d4ef19742a0be37c12fc8149ff34483c176616/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d94020ef09f624d841aa9a3a6029df8cf65d60d7a6d5c8687579fa68bd679b65", size = 58295, upload-time = "2025-12-06T13:25:20.822Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/1fae8a3b7ac181e36f6e7864a62d42d5b1f4fa7edf408c6711e28fba6b4d/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f64ce70d89942a23602dee910dec9b48e5edf94351e1b378186b74fcc00d7f66", size = 60960, upload-time = "2025-12-06T13:25:22.099Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9e/cd4c727742345ad8384569a4466f1a1428f4e5cc94d9c2ab2f53d30be3fe/pybase64-1.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8ea99f56e45c469818b9781903be86ba4153769f007ba0655fa3b46dc332803d", size = 74863, upload-time = "2025-12-06T13:25:23.442Z" }, + { url = "https://files.pythonhosted.org/packages/28/86/a236ecfc5b494e1e922da149689f690abc84248c7c1358f5605b8c9fdd60/pybase64-1.4.3-cp314-cp314t-win32.whl", hash = "sha256:343b1901103cc72362fd1f842524e3bb24978e31aea7ff11e033af7f373f66ab", size = 34513, upload-time = "2025-12-06T13:25:24.592Z" }, + { url = "https://files.pythonhosted.org/packages/56/ce/ca8675f8d1352e245eb012bfc75429ee9cf1f21c3256b98d9a329d44bf0f/pybase64-1.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:57aff6f7f9dea6705afac9d706432049642de5b01080d3718acc23af87c5af76", size = 36702, upload-time = "2025-12-06T13:25:25.72Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/4a675864877397179b09b720ee5fcb1cf772cf7bebc831989aff0a5f79c1/pybase64-1.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e906aa08d4331e799400829e0f5e4177e76a3281e8a4bc82ba114c6b30e405c9", size = 31904, upload-time = "2025-12-06T13:25:26.826Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7c/545fd4935a0e1ddd7147f557bf8157c73eecec9cffd523382fa7af2557de/pybase64-1.4.3-graalpy311-graalpy242_311_native-macosx_10_9_x86_64.whl", hash = "sha256:d27c1dfdb0c59a5e758e7a98bd78eaca5983c22f4a811a36f4f980d245df4611", size = 38393, upload-time = "2025-12-06T13:26:19.535Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/ae7a96be9ddc96030d4e9dffc43635d4e136b12058b387fd47eb8301b60f/pybase64-1.4.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0f1a0c51d6f159511e3431b73c25db31095ee36c394e26a4349e067c62f434e5", size = 32109, upload-time = "2025-12-06T13:26:20.72Z" }, + { url = "https://files.pythonhosted.org/packages/bf/44/d4b7adc7bf4fd5b52d8d099121760c450a52c390223806b873f0b6a2d551/pybase64-1.4.3-graalpy311-graalpy242_311_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a492518f3078a4e3faaef310697d21df9c6bc71908cebc8c2f6fbfa16d7d6b1f", size = 43227, upload-time = "2025-12-06T13:26:21.845Z" }, + { url = "https://files.pythonhosted.org/packages/08/86/2ba2d8734ef7939debeb52cf9952e457ba7aa226cae5c0e6dd631f9b851f/pybase64-1.4.3-graalpy311-graalpy242_311_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae1a0f47784fd16df90d8acc32011c8d5fcdd9ab392c9ec49543e5f6a9c43a4", size = 35804, upload-time = "2025-12-06T13:26:23.149Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5b/19c725dc3aaa6281f2ce3ea4c1628d154a40dd99657d1381995f8096768b/pybase64-1.4.3-graalpy311-graalpy242_311_native-win_amd64.whl", hash = "sha256:03cea70676ffbd39a1ab7930a2d24c625b416cacc9d401599b1d29415a43ab6a", size = 35880, upload-time = "2025-12-06T13:26:24.663Z" }, + { url = "https://files.pythonhosted.org/packages/17/45/92322aec1b6979e789b5710f73c59f2172bc37c8ce835305434796824b7b/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:2baaa092f3475f3a9c87ac5198023918ea8b6c125f4c930752ab2cbe3cd1d520", size = 38746, upload-time = "2025-12-06T13:26:25.869Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/f1a07402870388fdfc2ecec0c718111189732f7d0f2d7fe1386e19e8fad0/pybase64-1.4.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:cde13c0764b1af07a631729f26df019070dad759981d6975527b7e8ecb465b6c", size = 32573, upload-time = "2025-12-06T13:26:27.792Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8f/43c3bb11ca9bacf81cb0b7a71500bb65b2eda6d5fe07433c09b543de97f3/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5c29a582b0ea3936d02bd6fe9bf674ab6059e6e45ab71c78404ab2c913224414", size = 43461, upload-time = "2025-12-06T13:26:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4c/2a5258329200be57497d3972b5308558c6de42e3749c6cc2aa1cbe34b25a/pybase64-1.4.3-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6b664758c804fa919b4f1257aa8cf68e95db76fc331de5f70bfc3a34655afe1", size = 36058, upload-time = "2025-12-06T13:26:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/41faa414cde66ec023b0ca8402a8f11cb61731c3dc27c082909cbbd1f929/pybase64-1.4.3-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:f7537fa22ae56a0bf51e4b0ffc075926ad91c618e1416330939f7ef366b58e3b", size = 36231, upload-time = "2025-12-06T13:26:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/2a/cf/6e712491bd665ea8633efb0b484121893ea838d8e830e06f39f2aae37e58/pybase64-1.4.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94cf50c36bb2f8618982ee5a978c4beed9db97d35944fa96e8586dd953c7994a", size = 38007, upload-time = "2025-12-06T13:26:32.804Z" }, + { url = "https://files.pythonhosted.org/packages/38/c0/9272cae1c49176337dcdbd97511e2843faae1aaf5a5fb48569093c6cd4ce/pybase64-1.4.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:01bc3ff5ca1341685c6d2d945b035f442f7b9c3b068a5c6ee8408a41fda5754e", size = 31538, upload-time = "2025-12-06T13:26:34.001Z" }, + { url = "https://files.pythonhosted.org/packages/20/f2/17546f97befe429c73f622bbd869ceebb518c40fdb0dec4c4f98312e80a5/pybase64-1.4.3-pp310-pypy310_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:03d0aa3761a99034960496280c02aa063f856a3cc9b33771bc4eab0e4e72b5c2", size = 40682, upload-time = "2025-12-06T13:26:35.168Z" }, + { url = "https://files.pythonhosted.org/packages/92/a0/464b36d5dfb61f3da17858afaeaa876a9342d58e9f17803ce7f28b5de9e8/pybase64-1.4.3-pp310-pypy310_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ca5b1ce768520acd6440280cdab35235b27ad2faacfcec064bc9c3377066ef1", size = 41306, upload-time = "2025-12-06T13:26:36.351Z" }, + { url = "https://files.pythonhosted.org/packages/07/c9/a748dfc0969a8d960ecf1e82c8a2a16046ffec22f8e7ece582aa3b1c6cf9/pybase64-1.4.3-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3caa1e2ddad1c50553ffaaa1c86b74b3f9fbd505bea9970326ab88fc68c4c184", size = 35452, upload-time = "2025-12-06T13:26:37.772Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/4d37bd3577d1aa6c732dc099087fe027c48873e223de3784b095e5653f8b/pybase64-1.4.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd47076f736b27a8b0f9b30d93b6bb4f5af01b0dc8971f883ed3b75934f39a99", size = 36125, upload-time = "2025-12-06T13:26:39.78Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/160dded493c00d3376d4ad0f38a2119c5345de4a6693419ad39c3565959b/pybase64-1.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:277de6e03cc9090fb359365c686a2a3036d23aee6cd20d45d22b8c89d1247f17", size = 37939, upload-time = "2025-12-06T13:26:41.014Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b8/a0f10be8d648d6f8f26e560d6e6955efa7df0ff1e009155717454d76f601/pybase64-1.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab1dd8b1ed2d1d750260ed58ab40defaa5ba83f76a30e18b9ebd5646f6247ae5", size = 31466, upload-time = "2025-12-06T13:26:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/22/832a2f9e76cdf39b52e01e40d8feeb6a04cf105494f2c3e3126d0149717f/pybase64-1.4.3-pp311-pypy311_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:bd4d2293de9fd212e294c136cec85892460b17d24e8c18a6ba18750928037750", size = 40681, upload-time = "2025-12-06T13:26:43.782Z" }, + { url = "https://files.pythonhosted.org/packages/12/d7/6610f34a8972415fab3bb4704c174a1cc477bffbc3c36e526428d0f3957d/pybase64-1.4.3-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af6d0d3a691911cc4c9a625f3ddcd3af720738c21be3d5c72de05629139d393", size = 41294, upload-time = "2025-12-06T13:26:44.936Z" }, + { url = "https://files.pythonhosted.org/packages/64/25/ed24400948a6c974ab1374a233cb7e8af0a5373cea0dd8a944627d17c34a/pybase64-1.4.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfc8c49a28322d82242088378f8542ce97459866ba73150b062a7073e82629d", size = 35447, upload-time = "2025-12-06T13:26:46.098Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2b/e18ee7c5ee508a82897f021c1981533eca2940b5f072fc6ed0906c03a7a7/pybase64-1.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:debf737e09b8bf832ba86f5ecc3d3dbd0e3021d6cd86ba4abe962d6a5a77adb3", size = 36134, upload-time = "2025-12-06T13:26:47.35Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "11.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/a9/5f0c535ba3b08fe09270c16808e053a968868242ecbd5676d4e3a488bf28/pymdown_extensions-11.0.1.tar.gz", hash = "sha256:dd2905ae6fc5b75582fafb139a1266ffc754705efa902aa50067fa7ff4f94ec0", size = 857113, upload-time = "2026-07-02T17:59:22.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/54/da572c98c0b77626a91b5d3b89f0231d8bff5125c225420908632f8b342d/pymdown_extensions-11.0.1-py3-none-any.whl", hash = "sha256:db3943a62bab7e03af1364f0c4083e64b91fb097675a4b6cceccfbe9a77e5eb2", size = 269455, upload-time = "2026-07-02T17:59:21.271Z" }, +] + +[[package]] +name = "pymilvus" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "grpcio" }, + { name = "orjson" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "protobuf" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "setuptools" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/c1/01647e61f3a82fd881382746b6dde3401d65b88cd4f75bd059901fb2392b/pymilvus-3.0.0-1-py3-none-any.whl", hash = "sha256:57c8e7c87fbbf579f122b4df893949bc78e50bca2988527864891bd544817b05", size = 344817, upload-time = "2026-05-07T14:57:45.235Z" }, +] + +[[package]] +name = "pymongo" +version = "4.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/64/50be6fbac9c79fe2e4c17401a467da2d8764d82833d83cec325afe5cab32/pymongo-4.17.0.tar.gz", hash = "sha256:70ffa08ba641468cc068cf46c06b34f01a8ce3489f6411309fcb5ceabe6b2fc0", size = 2523370, upload-time = "2026-04-20T16:39:53.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/77/28ebbf69772a4341d530831c7a006cdb06877ac23075cb53b0a227df4fe1/pymongo-4.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47b021363cd923ace5edc7a1d63c0ff8a6d9d43859b8a1ba23645f5afae63221", size = 819234, upload-time = "2026-04-20T16:37:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/88/cf/5a70cee503ff9a2fea20607607f14d189f4d975960ac0945ec306ee7b695/pymongo-4.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:422fa50d7d7f5c22ea0953554396c9ef95684a2d775f860bd75a7b510538dfca", size = 819969, upload-time = "2026-04-20T16:37:24.187Z" }, + { url = "https://files.pythonhosted.org/packages/23/d5/07b7e27e662c58d872efd104a0e8055eb6569aa1b6d4da436f3fdee7f897/pymongo-4.17.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:addd0498ebbdc6354227f6ed457ed9fce442d48a3bb30d5b5bad33e104996561", size = 1244510, upload-time = "2026-04-20T16:37:26.069Z" }, + { url = "https://files.pythonhosted.org/packages/fb/be/7cac5b1e89bd5a8e395067648241390321593a7c29243e36f91343c02a90/pymongo-4.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5c8e180cb2cabe37300e1e36c60aa4f2ff956cc579f0142135a5d2cba252243", size = 1263245, upload-time = "2026-04-20T16:37:28.003Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/40e8e99824c1fda18261411e65ce3b0cd3d9a6ed3c056cdd0a569adc870b/pymongo-4.17.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bd835cdb37a1adec359dd072c24f8bb14809e2644fde86fab4ee2fc9719b9483", size = 1304113, upload-time = "2026-04-20T16:37:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/3a/94/fb7e25441dd66f2069a9b172380849b0eaa5881c18b3db217bf64a6d393c/pymongo-4.17.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4979e7e8887862bbb44d203f00cc8263a3f27237876fa691b6beba23e40e6d8", size = 1297046, upload-time = "2026-04-20T16:37:32.054Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c9/7352e0c20fe772541556e4d283c05e07ec48f8b0d2737ad930ac4a1b6655/pymongo-4.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77aa4bc164b4de60d5db193b322f0f5b6ead716e831031bfdef8e8bd92205556", size = 1265708, upload-time = "2026-04-20T16:37:33.934Z" }, + { url = "https://files.pythonhosted.org/packages/8d/e4/3df15494c2015ed297958517f0e4f6493e21b00990748068a973e66d45e0/pymongo-4.17.0-cp310-cp310-win32.whl", hash = "sha256:48bbc576677b50af043df870d84ded67cc3a9b4aa7553201beef4da5dc050a0a", size = 805533, upload-time = "2026-04-20T16:37:35.744Z" }, + { url = "https://files.pythonhosted.org/packages/22/fa/b4e71bb8cb82ad7d21bb4e8c476f2d573ba68b20368aac36ef06e4a196b4/pymongo-4.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46767f28dea610e02edf6c5d956ce615c3c7790ea396660b9b1efd5c5ead2e0", size = 815677, upload-time = "2026-04-20T16:37:37.808Z" }, + { url = "https://files.pythonhosted.org/packages/22/e2/0a4bba644f1cda3970ea1012149eeae3594ebfeed3f81fdaf32b61d90c95/pymongo-4.17.0-cp310-cp310-win_arm64.whl", hash = "sha256:757f2a4c0c2c46cab87df0333681ce69e86c9d5b45bc5203ceba5410b3489e59", size = 807293, upload-time = "2026-04-20T16:37:39.707Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e2/336d86f221cf1b56b2ed9330d4a3b98f9f38f0b37829ae9a9184617d5419/pymongo-4.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4141e6c6a339789b2974efa00ecd9409101672d77a0e3ee2cc3839eedf8ec4df", size = 874668, upload-time = "2026-04-20T16:37:41.39Z" }, + { url = "https://files.pythonhosted.org/packages/34/8e/75d3c6c935d187ab59c61e9c15d9aab3f274b563eaf1706e8cae5f508dec/pymongo-4.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e68c76b84e0c132d9dbf9307f12ff8185702328187a87b9aca8c941303873433", size = 875294, upload-time = "2026-04-20T16:37:43.432Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ec/62e855744489dbcd54fd778aae4d80fa4c4819e8fb228ca0cf6f21a03997/pymongo-4.17.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba2195d4f386f839a52a23ea1cfd60ffaaba78a3d7841db51b7e433001139918", size = 1496233, upload-time = "2026-04-20T16:37:45.518Z" }, + { url = "https://files.pythonhosted.org/packages/82/e8/93e4e5e5ce8fdf8929dabeefe24aafa5ce046028eed0dfa8eeb936e72c49/pymongo-4.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446ff4bfcb6ec2a2e50998c860986a1e992136f998b7f53e7a717fb8aa5a0b9", size = 1522927, upload-time = "2026-04-20T16:37:47.492Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ca/425dc1d21e0f17bdea0072fc463f662f7fa06d2852af52975c9eced3c07c/pymongo-4.17.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2a0d5ac205728c86e0a02192f1aa5f865b0d7d51f8df6101c01a69a7fc620d72", size = 1583468, upload-time = "2026-04-20T16:37:49.221Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9d/f08b07eeffda1a43c1759f0fa625e88ae12360996eb56d42aad832fa7dff/pymongo-4.17.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:485c8a8eaa4c739f00a331fc73757898ee7c092c214a79e63866ff76aaf282ff", size = 1572787, upload-time = "2026-04-20T16:37:51.061Z" }, + { url = "https://files.pythonhosted.org/packages/e9/c2/6855a07aafa7b894929af23675b6fb9634800ce43122b76a62f6eeb8da2a/pymongo-4.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2dfcc795f5b9fedbe179a11fdf6051581479d196582a3fe819a92a00e9b9969", size = 1526184, upload-time = "2026-04-20T16:37:53.358Z" }, + { url = "https://files.pythonhosted.org/packages/4e/05/c952bac7db71c1942ea3559fcd308b49754cc5004b455935fb4000d1f37b/pymongo-4.17.0-cp311-cp311-win32.whl", hash = "sha256:c2292144505fb12156b981bd440f3dc994a883da06ac726c0c8692ccdbc1c510", size = 852621, upload-time = "2026-04-20T16:37:55.28Z" }, + { url = "https://files.pythonhosted.org/packages/11/c0/c04da9f4c0c6252404598f4e394b862a58a9e866822a70ae261c8a018fdf/pymongo-4.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:2e190827834fce70ecdf9d46796c6dbc0ce08ea87dc2ff5bc6f3f5579b605cb9", size = 867852, upload-time = "2026-04-20T16:37:57.233Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b2/c7b4870fbeef471e947d3e014676f5910d02e0197074d692ebcf24ec049a/pymongo-4.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:a8f9c40a09bb7d4b9fc8b1da65ecf6efa79bda5cb2756f39d9b6940fac1d19ae", size = 855019, upload-time = "2026-04-20T16:37:58.983Z" }, + { url = "https://files.pythonhosted.org/packages/98/90/60bcb508840135d5ee46b51b1a950f548338aa8145a8366dbe6639ae51ac/pymongo-4.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53ffa94b2340dbf6b055e09a0090618c60482c158ecfc9565642fc996bf0944", size = 930529, upload-time = "2026-04-20T16:38:00.936Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e9/313840f1e52c6dfac47f704428cbfbce59956ebe7633bffc92b03f74f0ad/pymongo-4.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6fe0de9d0f6791abce3471230b32b4817bf89d27b1182b6a550e1ec0fa72aa9a", size = 930665, upload-time = "2026-04-20T16:38:02.915Z" }, + { url = "https://files.pythonhosted.org/packages/78/35/9d3565ea45b1606f635c1e2cd2563c28d66caafdc50f7ad7d979fcd1b363/pymongo-4.17.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e537e95514dae1aaa718f481ec03151a0f0394bcd05f1322896d8fc1330cb729", size = 1762369, upload-time = "2026-04-20T16:38:05.375Z" }, + { url = "https://files.pythonhosted.org/packages/95/ee/149b0d4b1a11c38bff6f14c23d5814c9b0843fd6dc38ad40596bdb1a62d2/pymongo-4.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37a8385c29881b43eab31f584100fa0eaddedd5607adf010147ba1810118be90", size = 1798044, upload-time = "2026-04-20T16:38:07.195Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d4/4cee4a7b8d8f6f0550ef6cd2fea42455c5ed619a220cb6ba4fb40d6a5bc8/pymongo-4.17.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3ee3d241ed77a4fc99ce3cff3b289c3ebce37f61fdd7349d3592c23b82c8784", size = 1878567, upload-time = "2026-04-20T16:38:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/45/ef/7fe366c84952619ee2f69973566c214775e083dd4df465751912153e4b72/pymongo-4.17.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9eb5d63a3c518cb0804ed678f5e2b875af032d89a7cf57a57360322cf6a4d222", size = 1864881, upload-time = "2026-04-20T16:38:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e97e03fa13327c87e3fdc5656acd01e71817f0c1dc3221cd8f30de136bf4ec3", size = 1800349, upload-time = "2026-04-20T16:38:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/b8/69/dafcf04f66e130ddd91aeb92e7a692480eda46dcd04ec1dbe82c06619e10/pymongo-4.17.0-cp312-cp312-win32.whl", hash = "sha256:6877214bff5f06f6884a9fc8d9016a4a7a5f51f537f5c51ac3a576f93e7dfb32", size = 900518, upload-time = "2026-04-20T16:38:15.541Z" }, + { url = "https://files.pythonhosted.org/packages/11/35/5c9262a459f988b4eb2605f70815240b77a0d4131136c4326d18f1822b89/pymongo-4.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9828485f72f63c7d802e0ec41f71906f633c2692621ab3af55ca990186b091b1", size = 920335, upload-time = "2026-04-20T16:38:17.665Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/e9c7265ee176faccf4e52c4797837e794d93569a1046f6b19a4acc36e5ad/pymongo-4.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:1195370a77baf003b59b10e91ecc4706297197f0dd9d29c840cc556dc08f7cee", size = 903289, upload-time = "2026-04-20T16:38:19.33Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6b/c1206879708b94e82fcd8b9653440ec271f79a3674d122192df383047f5a/pymongo-4.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:809ec74de3b9148ae43fa8df9faf53470f511c8d384f13b99d6f671f2a379f15", size = 985829, upload-time = "2026-04-20T16:38:21.031Z" }, + { url = "https://files.pythonhosted.org/packages/cb/cf/bb044ed85160e5c40f568c7c4f4e8ea16f40764ff5d302e5befbe8f6f814/pymongo-4.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a431b737816bf4cddd4fa0fcef04e424ad36b7692734a64150f872fb8f3208be", size = 985899, upload-time = "2026-04-20T16:38:23.409Z" }, + { url = "https://files.pythonhosted.org/packages/74/0a/f6dfd5ea3901e5d6888da8de8ba728971a1d447debab681cfc56f90d1208/pymongo-4.17.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4fab10f8403169ce92f3cea921609d9ee81107306caae06c08f592d4b8ad2b5", size = 2028569, upload-time = "2026-04-20T16:38:25.343Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c5/081f59a1c02ae8c0dc73ae58e563838c44eec81aeafa7d0b93a637841c9b/pymongo-4.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20323b0b1c1d33770ad1fc68d429c757734ce9ad3594421c3d6618f10572b1b9", size = 2072916, upload-time = "2026-04-20T16:38:27.291Z" }, + { url = "https://files.pythonhosted.org/packages/31/42/6e41d434297ffe8b30d9c3717916591a4a7be9075a0dcc2fafdfaaaa62ed/pymongo-4.17.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5a5de048e6da5c18e27cc2437e8c15b3b0cdc8385c15b41178b0caa3322a09c2", size = 2173234, upload-time = "2026-04-20T16:38:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/3d/cf/1e4a7db352ef9485831c7268dfe8402f0117b32a9ad54b16e810699e3617/pymongo-4.17.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dff3de1294fbbc1db0ba6b511f77b8e540601d092538a31312e99c8a91a78b1e", size = 2156784, upload-time = "2026-04-20T16:38:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/12/10/6195be29962a61ebb5f4bd9e4c7519890b172f7968a0a0d880398c6ddb02/pymongo-4.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faf03e4c2aafd6de626dbd30ba246d369ae33f47f10629d1bbe40f72115027a6", size = 2074446, upload-time = "2026-04-20T16:38:34.004Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/33410b8819837ed370c738587306bdf060b59cef11823be212f4a07703c5/pymongo-4.17.0-cp313-cp313-win32.whl", hash = "sha256:c9786665926a09630c5d420c79762cfadbff35a9438bcbc4c81a9fb5ab9228b7", size = 948435, upload-time = "2026-04-20T16:38:35.922Z" }, + { url = "https://files.pythonhosted.org/packages/6f/77/c0ed522f798a286b99acaa7914ed8d9c80ab091f97f57c59ffed72906e5e/pymongo-4.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:5960519b4d7168f1ecdd3ea10c81b2aedeb9423651aca953cfbc8e76705d3b38", size = 972847, upload-time = "2026-04-20T16:38:37.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/f0/c39480a2db385fde23861d0c8acda41cdaf1d43e46579db72c5c013a2e81/pymongo-4.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:0ff6bd2f735ab5356541e3e57d5b7dbfbc3f2ee1ccb10b6b0f82d58af69d1d8e", size = 951575, upload-time = "2026-04-20T16:38:40.544Z" }, + { url = "https://files.pythonhosted.org/packages/da/49/2b0250762a89737ed6f9cea238331baca061b89a8ddd10dd17fee52c3970/pymongo-4.17.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff5aa3f1c7e3f08eb0e7a016c91ba468b1850ccfd63d9b1f12f56350f4974cef", size = 1040945, upload-time = "2026-04-20T16:38:42.783Z" }, + { url = "https://files.pythonhosted.org/packages/89/1c/7a9b5447a08be20e84b6e5b17330917e8d6d9507daa3cd099a9309f11ad7/pymongo-4.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e816db649ba5d7de0568cf3a9f287a9dc9aad21cf0ca667ab156a7ef47fca0b0", size = 1041187, upload-time = "2026-04-20T16:38:45.358Z" }, + { url = "https://files.pythonhosted.org/packages/78/a1/71704f61632dfc90407a5834fe5f6132854937c4a3648f6c05c351d85a45/pymongo-4.17.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:12c4fded3a9f1d6a687e36ebd384ac6d00b9b00de1969aa74048e7051ec2a713", size = 2294806, upload-time = "2026-04-20T16:38:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b9/aff42be75108b96c2469b1d9329b912c15108f3e7ef32fdc86da8423c330/pymongo-4.17.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2db66aa8dd253a0fc1fad3b0d23d5b3993f7ebde02fbbd7727128debf2853675", size = 2348231, upload-time = "2026-04-20T16:38:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/f2/30/44c115b8ba1479942c15fd9480eb29a7da0ba68acd56983423ba0deb4a94/pymongo-4.17.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3987e96e7c7be4083d42e8ac2cc6c0d5b78db9973c90fce42ae800b616ca6b20", size = 2467614, upload-time = "2026-04-20T16:38:52.665Z" }, + { url = "https://files.pythonhosted.org/packages/d2/84/21ee95c8bf0ca7acae7ec7eb365d740bf8fc0156c194baf2c3bdfcb85ec0/pymongo-4.17.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cee36b3c0d0354f880fa7a7fdcdaf2bb5e542c2281e25c1bfadf8cfe21eba7d2", size = 2445970, upload-time = "2026-04-20T16:38:55.175Z" }, + { url = "https://files.pythonhosted.org/packages/06/89/081d7f1809d5ca09d1e47e49f2111b245f5694de3a7af32cd3a353a6f43f/pymongo-4.17.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:320b34457b20bbcc79997801f95d25ce00472915ca5241167242b42c4359e027", size = 2348605, upload-time = "2026-04-20T16:38:57.557Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c3/0d949f9d3f2a341c1f635c398c16615e96f89f51ff424ed81e914cf1a4de/pymongo-4.17.0-cp314-cp314-win32.whl", hash = "sha256:df4a644af9ae132d4bfdb2e9516ea51a615fd881caddfbfbd071cf1354844479", size = 1004119, upload-time = "2026-04-20T16:39:00.309Z" }, + { url = "https://files.pythonhosted.org/packages/f7/55/5c3a3db1048054c695c75c5964cc8bedc2247fdb5a75ef6fab4ec8bb013e/pymongo-4.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:c797f8a80957134f6dd9690367a0f8f5906d672119af2c6aa55f0c527b656bed", size = 1032314, upload-time = "2026-04-20T16:39:02.665Z" }, + { url = "https://files.pythonhosted.org/packages/e0/19/e235f39906134cb0ffd5574c5a59c355ef5380f0499644ab94994afbb109/pymongo-4.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:68fca71e05ee5da23a8d73cee8379dfb3d26e609a377cae731d742771ed96946", size = 1007627, upload-time = "2026-04-20T16:39:04.678Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e0/c4c1a86791415b14c684fa0908f9da96de91594a3fd1fa1b8dc689fbb800/pymongo-4.17.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b4384700cffc3f1dd98e088bc0072dedf6d7d68a230bb4b972665cf69c071c1e", size = 1099151, upload-time = "2026-04-20T16:39:06.969Z" }, + { url = "https://files.pythonhosted.org/packages/81/4b/69c67f3e23fd9b23b9bedc7ebd23754881cc9d5c5d5b2a9811e96b07f475/pymongo-4.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:93641192644fa1ee0f34030e774fd31022a27ad11ba22cb1716142231524f8bd", size = 1099346, upload-time = "2026-04-20T16:39:08.996Z" }, + { url = "https://files.pythonhosted.org/packages/a2/19/a5208f62f9508a26d73acc69bd3821b8c8adae253679a3c26d2f9652f0d5/pymongo-4.17.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75bc3aa5b94fdb7138d357ec6ca61cd97e0c79f4f7f0bd3efe9639b15cc50942", size = 2619034, upload-time = "2026-04-20T16:39:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/77/27/426cba1ec5973082a56d4150798529bfdf4151c31391ed1fbbecb23ef2ac/pymongo-4.17.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e8f8e23c6df7c6d6929f5e734980b227706e73ee847517c9ba5af90f7fc466", size = 2689939, upload-time = "2026-04-20T16:39:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2e/f70993d1255e33f6ee59a4ec4371cc65bff7a7e3fda7d55c3386f25287e8/pymongo-4.17.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:15d3f3d732aecac1f8d481bde4029755615639bd3076f258a2147210aec8515a", size = 2824994, upload-time = "2026-04-20T16:39:16.057Z" }, + { url = "https://files.pythonhosted.org/packages/b3/eb/87b0e988ba889e1fcc3430c2cfc166b251872c813e92b43174298bee17ff/pymongo-4.17.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5f62862d0f87be481fa1fe8cb811994486773c94a2b61e509285e3f2890763", size = 2801745, upload-time = "2026-04-20T16:39:18.476Z" }, + { url = "https://files.pythonhosted.org/packages/67/4c/3f83412d086f682d4d468761d66ddc49cf161e786ea74073045eb4491c60/pymongo-4.17.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64837adbbd72073301af51bb0fc80e3d7707fe5527cea1033ba0320f0b2f881b", size = 2684636, upload-time = "2026-04-20T16:39:20.878Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/b75f6f4ab6c8beb50b0270a4f1e2530b5774f5e116563440e1677ca1820f/pymongo-4.17.0-cp314-cp314t-win32.whl", hash = "sha256:b93b22eedc62598cf5ee9d8c8007a8e9121c50fd88137012d8985500e9dc3151", size = 1056356, upload-time = "2026-04-20T16:39:22.996Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5e/648c8a238eef18a25ed8a169ea6542d4a860bbec3e95b3d9badac2935c71/pymongo-4.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3689ea34f6b647c7d1e7bdc60fcfb214b2789ed1359a7fb96569c69f50e5f18f", size = 1090964, upload-time = "2026-04-20T16:39:24.989Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cb/d9780b66939c4fc1f024bcc7be23a2abcfe06a9745ca8fa76dc73395482e/pymongo-4.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9543d8f84c2e5608565c08ac679774811e6730770d8a645439b073422a4276fb", size = 1058526, upload-time = "2026-04-20T16:39:27.924Z" }, +] + +[[package]] +name = "pymssql" +version = "2.3.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/cc/843c044b7f71ee329436b7327c578383e2f2499313899f88ad267cdf1f33/pymssql-2.3.13.tar.gz", hash = "sha256:2137e904b1a65546be4ccb96730a391fcd5a85aab8a0632721feb5d7e39cfbce", size = 203153, upload-time = "2026-02-14T05:00:36.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b5/77d2af3cab1a129891f75a4223a145664ec0443a340ee737518b499b4edb/pymssql-2.3.13-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:476f6f06b2ae5dfbfa0b169a6ecdd0d9ddfedb07f2d6dc97d2dd630ff2d6789a", size = 3173361, upload-time = "2026-02-14T04:59:16.582Z" }, + { url = "https://files.pythonhosted.org/packages/93/e4/d7833727a19cd1aca2e6de4d59b9ecd3c7c15c1b9c9f0fef8c6a38176f76/pymssql-2.3.13-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:17942dc9474693ab2229a8a6013e5b9cb1312a5251207552141bb85fcce8c131", size = 2976106, upload-time = "2026-02-14T04:59:19.833Z" }, + { url = "https://files.pythonhosted.org/packages/e9/16/121a3d77530ccfdec3cfdf30342d4ca67283441a0adc4f4daaf963f57454/pymssql-2.3.13-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d87237500def5f743a52e415cd369d632907212154fcc7b4e13f264b4e30021", size = 3043228, upload-time = "2026-02-14T04:59:21.603Z" }, + { url = "https://files.pythonhosted.org/packages/85/34/178a4293329c9cf61d438262a52553a92b4421a40419a9ef58f7783ce444/pymssql-2.3.13-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:612ac062027d2118879f11a5986e9d9d82d07ca3545bb98c93200b68826ea687", size = 3175449, upload-time = "2026-02-14T04:59:24.047Z" }, + { url = "https://files.pythonhosted.org/packages/00/aa/bcfc4b7488a19026bac62a819dd6ee389bc2e706f72d7c021d7c6fa8d08e/pymssql-2.3.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1897c1b767cc143e77d285123ae5fd4fa7379a1bfec5c515d38826caf084eb6", size = 3689919, upload-time = "2026-02-14T04:59:26.345Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/036bdbb7f036d97a3d502bf285f3530a9e800b07922ab6227ef69ce15da7/pymssql-2.3.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:48631c7b9fd14a1bd5675c521b6082590bf700b7961c65638d237817b3fde735", size = 3436776, upload-time = "2026-02-14T04:59:28.942Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/92ef7f032c9ce46c5adcf1fa1513e99a9b533ca673bbf01748574ca0f2cc/pymssql-2.3.13-cp310-cp310-win_amd64.whl", hash = "sha256:79c759db6e991eeae473b000c2e0a7fb8da799b2da469fe5a10d30916315e0b5", size = 2009253, upload-time = "2026-02-14T04:59:31.034Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/3d270f2bcfe7cf73b6d17719998316856550ca719791ac00be07e5be7a47/pymssql-2.3.13-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:152be40c0d7f5e4b1323f7728b0a01f3ee0082190cfbadf84b2c2e930d57e00e", size = 3171876, upload-time = "2026-02-14T04:59:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b0/b4f1c7879a4fdac688f227cc55f31a5cfc8c36dc63117a10af472a399fc1/pymssql-2.3.13-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:d94da3a55545c5b6926cb4d1c6469396f0ae32ad5d6932c513f7a0bf569b4799", size = 2973968, upload-time = "2026-02-14T04:59:35.462Z" }, + { url = "https://files.pythonhosted.org/packages/3c/68/45157f1bb9b8499e4abe7b64195f44aaa2d6bf6aae305d4e8cf5df522424/pymssql-2.3.13-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51e42c5defc3667f0803c7ade85db0e6f24b9a1c5a18fcdfa2d09c36bff9b065", size = 3035519, upload-time = "2026-02-14T04:59:37.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/f3/3aeffc2b3683c135d75e0536657090ddb5f07114eb5e528303e1f4880393/pymssql-2.3.13-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4aa18944a121f996178e26cadc598abdbf73759f03dc3cd74263fdab1b28cd96", size = 3162703, upload-time = "2026-02-14T04:59:39.571Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c0/bd35090a223961d9190dfb884be14529358d561cad1b4211dc351b20dcfd/pymssql-2.3.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:910404e0ec85c4cc7c633ec3df9b04a35f23bb74a844dd377a387026ae635e3a", size = 3681162, upload-time = "2026-02-14T04:59:42.181Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d4/d6a5d74c9942d1554538087cfd6ff489d3645bce484c53339f25c4cf6077/pymssql-2.3.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4b834c34e7600369eee7bc877948b53eb0fe6f3689f0888d005ae47dd53c0a66", size = 3424100, upload-time = "2026-02-14T04:59:43.795Z" }, + { url = "https://files.pythonhosted.org/packages/27/38/7f1eff7dbbd286a16e341b813fd55c88258e07268a3ccebbfb0e9c46ca74/pymssql-2.3.13-cp311-cp311-win_amd64.whl", hash = "sha256:5c2e55b6513f9c5a2f58543233ed40baaa7f91c79e64a5f961ea3fc57a700b80", size = 2010201, upload-time = "2026-02-14T04:59:45.404Z" }, + { url = "https://files.pythonhosted.org/packages/ba/60/a2e8a8a38f7be21d54402e2b3365cd56f1761ce9f2706c97f864e8aa8300/pymssql-2.3.13-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cf4f32b4a05b66f02cb7d55a0f3bcb0574a6f8cf0bee4bea6f7b104038364733", size = 3158689, upload-time = "2026-02-14T04:59:46.982Z" }, + { url = "https://files.pythonhosted.org/packages/43/9e/0cf0ffb9e2f73238baf766d8e31d7237b5bee3cc1bb29a376b404610994a/pymssql-2.3.13-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:2b056eb175955f7fb715b60dc1c0c624969f4d24dbdcf804b41ab1e640a2b131", size = 2960018, upload-time = "2026-02-14T04:59:48.668Z" }, + { url = "https://files.pythonhosted.org/packages/93/ea/bc27354feaca717faa4626911f6b19bb62985c87dda28957c63de4de5895/pymssql-2.3.13-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:319810b89aa64b99d9c5c01518752c813938df230496fa2c4c6dda0603f04c4c", size = 3065719, upload-time = "2026-02-14T04:59:50.369Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7a/8028681c96241fb5fc850b87c8959402c353e4b83c6e049a99ffa67ded54/pymssql-2.3.13-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0ea72641cb0f8bce7ad8565dbdbda4a7437aa58bce045f2a3a788d71af2e4be", size = 3190567, upload-time = "2026-02-14T04:59:52.202Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f1/ab5b76adbbd6db9ce746d448db34b044683522e7e7b95053f9dd0165297b/pymssql-2.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1493f63d213607f708a5722aa230776ada726ccdb94097fab090a1717a2534e0", size = 3710481, upload-time = "2026-02-14T04:59:54.01Z" }, + { url = "https://files.pythonhosted.org/packages/59/aa/2fa0951475cd0a1829e0b8bfbe334d04ece4bce11546a556b005c4100689/pymssql-2.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb3275985c23479e952d6462ae6c8b2b6993ab6b99a92805a9c17942cf3d5b3d", size = 3453789, upload-time = "2026-02-14T04:59:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/78/08/8cd2af9003f9fc03912b658a64f5a4919dcd68f0dd3bbc822b49a3d14fd9/pymssql-2.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:a930adda87bdd8351a5637cf73d6491936f34e525a5e513068a6eac742f69cdb", size = 1994709, upload-time = "2026-02-14T04:59:58.972Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4f/ee15b1f6b11e7c3accdc7da7840a019b63f12ba09eaa008acc601182f516/pymssql-2.3.13-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:30918bb044242865c01838909777ef5e0f1b9ecd7f5882346aefa57f4414b29c", size = 3156333, upload-time = "2026-02-14T05:00:01.21Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/aea5c77bad4a52649a1d9f786a1d9ce1c83d50f1a75df288e292737b6d80/pymssql-2.3.13-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:1c6d0b2d7961f159a07e4f0d8cc81f70ceab83f5e7fd1e832a2d069e1d67ee4e", size = 2957990, upload-time = "2026-02-14T05:00:03.11Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f8/30ac16fba32ff066b05f12c392d7b812fe11f06cb62d1d86ca5177c50a8b/pymssql-2.3.13-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16c5957a3c9e51a03276bfd76a22431e2bc4c565e2e95f2cbb3559312edda230", size = 3065264, upload-time = "2026-02-14T05:00:05.377Z" }, + { url = "https://files.pythonhosted.org/packages/a9/98/7568447bf85921d21453fd56e19b6c9591d595fde0546c5a569f3ae937a8/pymssql-2.3.13-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fddd24efe9d18bbf174fab7c6745b0927773718387f5517cf8082241f721a68", size = 3190039, upload-time = "2026-02-14T05:00:06.925Z" }, + { url = "https://files.pythonhosted.org/packages/35/f1/4d9d275ebaac42cdd49d40d504ccb648f27710660c8b60cc427752438c09/pymssql-2.3.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:123c55ee41bc7a82c76db12e2eb189b50d0d7a11222b4f8789206d1cda3b33b9", size = 3710151, upload-time = "2026-02-14T05:00:08.424Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bd/a5cc6244fd27d3ea0cc82f12a7d38a24d7fd90b0022afd250014e8bfba15/pymssql-2.3.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e053b443e842f9e1698fcb2b23a4bff1ff3d410894d880064e754ad823d541e5", size = 3453156, upload-time = "2026-02-14T05:00:09.978Z" }, + { url = "https://files.pythonhosted.org/packages/26/d0/c20ff0bbffd18db528bcc7b0c68b25c12ad563ed67c56ceca87c58f7399e/pymssql-2.3.13-cp313-cp313-win_amd64.whl", hash = "sha256:5c045c0f1977a679cc30d5acd9da3f8aeb2dc6e744895b26444b4a2f20dad9a0", size = 1995236, upload-time = "2026-02-14T05:00:11.495Z" }, + { url = "https://files.pythonhosted.org/packages/ec/5f/6b64f78181d680f655ab40ba7b34cb68c045a2f4e04a10a70d768cd383b7/pymssql-2.3.13-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:fc5482969c813b0a45ce51c41844ae5bfa8044ad5ef8b4820ef6de7d4545b7f2", size = 3158377, upload-time = "2026-02-14T05:00:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/ff/24/155dbb0992c431496d440f47fb9d587cd0059ee20baf65e3d891794d862a/pymssql-2.3.13-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:ff5be7ab1d643dbce2ee3424d2ef9ae8e4146cf75bd20946bc7a6108e3ad1e47", size = 2959039, upload-time = "2026-02-14T05:00:15.883Z" }, + { url = "https://files.pythonhosted.org/packages/c9/89/b453dd1b1188779621fb974ac715ab2e738f4a0b69f7291ab014298bd80d/pymssql-2.3.13-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d66ce0a249d2e3b57369048d71e1f00d08dfb90a758d134da0250ae7bc739c1", size = 3063862, upload-time = "2026-02-14T05:00:17.537Z" }, + { url = "https://files.pythonhosted.org/packages/02/e5/96f57c78162013678ecc3f3f7e5fb52c83ee07beef26906d0870770c3ef6/pymssql-2.3.13-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d663c908414a6a032f04d17628138b1782af916afc0df9fefac4751fa394c3ac", size = 3188155, upload-time = "2026-02-14T05:00:19.011Z" }, + { url = "https://files.pythonhosted.org/packages/cd/a2/4bee9484734ae0c55d10a2f6ff82dd4e416f52420755161b8760c817ad64/pymssql-2.3.13-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aa5e07eff7e6e8bd4ba22c30e4cb8dd073e138cd272090603609a15cc5dbc75b", size = 3709344, upload-time = "2026-02-14T05:00:21.139Z" }, + { url = "https://files.pythonhosted.org/packages/37/cf/3520d96afa213c88db4f4a1988199db476d869a62afdd5d9c4635c184631/pymssql-2.3.13-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:db77da1a3fc9b5b5c5400639d79d7658ba7ad620957100c5b025be608b562193", size = 3451799, upload-time = "2026-02-14T05:00:22.504Z" }, + { url = "https://files.pythonhosted.org/packages/25/50/4be9bd9cf4b43208a7175117a533ece200cfe4131a39f9909bdc7560ddeb/pymssql-2.3.13-cp314-cp314-win_amd64.whl", hash = "sha256:7d7037d2b5b907acc7906d0479924db2935a70c720450c41339146a4ada2b93d", size = 2049139, upload-time = "2026-02-14T05:00:23.951Z" }, +] + +[[package]] +name = "pymysql" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/bc/1c6a92f385940f727daeecf3bacaf186e03875dff57197801046c583bcf0/pymysql-1.2.0.tar.gz", hash = "sha256:6c7b17ca686988104d7426c27895b455cdeea3e9d3ceb1270f0c3704fead8c33", size = 49021, upload-time = "2026-05-19T08:26:22.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/bd/2534e130295c8cfd4f0a2e31623baab7502278f1e97bcfe61db75656a77f/pymysql-1.2.0-py3-none-any.whl", hash = "sha256:62169ce6d5510f08e140c5e7990ee884a9764024e4a9a27b2cc11f1099322ae0", size = 45716, upload-time = "2026-05-19T08:26:20.974Z" }, +] + +[package.optional-dependencies] +rsa = [ + { name = "cryptography" }, +] + +[[package]] +name = "pynacl" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/9a/4019b524b03a13438637b11538c82781a5eda427394380381af8f04f467a/pynacl-1.6.2.tar.gz", hash = "sha256:018494d6d696ae03c7e656e5e74cdfd8ea1326962cc401bcf018f1ed8436811c", size = 3511692, upload-time = "2026-01-01T17:48:10.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/79/0e3c34dc3c4671f67d251c07aa8eb100916f250ee470df230b0ab89551b4/pynacl-1.6.2-cp314-cp314t-macosx_10_10_universal2.whl", hash = "sha256:622d7b07cc5c02c666795792931b50c91f3ce3c2649762efb1ef0d5684c81594", size = 390064, upload-time = "2026-01-01T17:31:57.264Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/23a26e931736e13b16483795c8a6b2f641bf6a3d5238c22b070a5112722c/pynacl-1.6.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d071c6a9a4c94d79eb665db4ce5cedc537faf74f2355e4d502591d850d3913c0", size = 809370, upload-time = "2026-01-01T17:31:59.198Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/8d4b718f8a22aea9e8dcc8b95deb76d4aae380e2f5b570cc70b5fd0a852d/pynacl-1.6.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe9847ca47d287af41e82be1dd5e23023d3c31a951da134121ab02e42ac218c9", size = 1408304, upload-time = "2026-01-01T17:32:01.162Z" }, + { url = "https://files.pythonhosted.org/packages/fd/73/be4fdd3a6a87fe8a4553380c2b47fbd1f7f58292eb820902f5c8ac7de7b0/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04316d1fc625d860b6c162fff704eb8426b1a8bcd3abacea11142cbd99a6b574", size = 844871, upload-time = "2026-01-01T17:32:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/55/ad/6efc57ab75ee4422e96b5f2697d51bbcf6cdcc091e66310df91fbdc144a8/pynacl-1.6.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44081faff368d6c5553ccf55322ef2819abb40e25afaec7e740f159f74813634", size = 1446356, upload-time = "2026-01-01T17:32:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/928ee9c4779caa0a915844311ab9fb5f99585621c5d6e4574538a17dca07/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:a9f9932d8d2811ce1a8ffa79dcbdf3970e7355b5c8eb0c1a881a57e7f7d96e88", size = 826814, upload-time = "2026-01-01T17:32:06.078Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a9/1bdba746a2be20f8809fee75c10e3159d75864ef69c6b0dd168fc60e485d/pynacl-1.6.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:bc4a36b28dd72fb4845e5d8f9760610588a96d5a51f01d84d8c6ff9849968c14", size = 1411742, upload-time = "2026-01-01T17:32:07.651Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2f/5e7ea8d85f9f3ea5b6b87db1d8388daa3587eed181bdeb0306816fdbbe79/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bffb6d0f6becacb6526f8f42adfb5efb26337056ee0831fb9a7044d1a964444", size = 801714, upload-time = "2026-01-01T17:32:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/06/ea/43fe2f7eab5f200e40fb10d305bf6f87ea31b3bbc83443eac37cd34a9e1e/pynacl-1.6.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fef529ef3ee487ad8113d287a593fa26f48ee3620d92ecc6f1d09ea38e0709b", size = 1372257, upload-time = "2026-01-01T17:32:11.026Z" }, + { url = "https://files.pythonhosted.org/packages/4d/54/c9ea116412788629b1347e415f72195c25eb2f3809b2d3e7b25f5c79f13a/pynacl-1.6.2-cp314-cp314t-win32.whl", hash = "sha256:a84bf1c20339d06dc0c85d9aea9637a24f718f375d861b2668b2f9f96fa51145", size = 231319, upload-time = "2026-01-01T17:32:12.46Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/64e9d76646abac2dccf904fccba352a86e7d172647557f35b9fe2a5ee4a1/pynacl-1.6.2-cp314-cp314t-win_amd64.whl", hash = "sha256:320ef68a41c87547c91a8b58903c9caa641ab01e8512ce291085b5fe2fcb7590", size = 244044, upload-time = "2026-01-01T17:32:13.781Z" }, + { url = "https://files.pythonhosted.org/packages/33/33/7873dc161c6a06f43cda13dec67b6fe152cb2f982581151956fa5e5cdb47/pynacl-1.6.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d29bfe37e20e015a7d8b23cfc8bd6aa7909c92a1b8f41ee416bbb3e79ef182b2", size = 188740, upload-time = "2026-01-01T17:32:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/4845bbf88e94586ec47a432da4e9107e3fc3ce37eb412b1398630a37f7dd/pynacl-1.6.2-cp38-abi3-macosx_10_10_universal2.whl", hash = "sha256:c949ea47e4206af7c8f604b8278093b674f7c79ed0d4719cc836902bf4517465", size = 388458, upload-time = "2026-01-01T17:32:16.829Z" }, + { url = "https://files.pythonhosted.org/packages/1e/b4/e927e0653ba63b02a4ca5b4d852a8d1d678afbf69b3dbf9c4d0785ac905c/pynacl-1.6.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8845c0631c0be43abdd865511c41eab235e0be69c81dc66a50911594198679b0", size = 800020, upload-time = "2026-01-01T17:32:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/7f/81/d60984052df5c97b1d24365bc1e30024379b42c4edcd79d2436b1b9806f2/pynacl-1.6.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22de65bb9010a725b0dac248f353bb072969c94fa8d6b1f34b87d7953cf7bbe4", size = 1399174, upload-time = "2026-01-01T17:32:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/f7/322f2f9915c4ef27d140101dd0ed26b479f7e6f5f183590fd32dfc48c4d3/pynacl-1.6.2-cp38-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46065496ab748469cdd999246d17e301b2c24ae2fdf739132e580a0e94c94a87", size = 835085, upload-time = "2026-01-01T17:32:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d0/f301f83ac8dbe53442c5a43f6a39016f94f754d7a9815a875b65e218a307/pynacl-1.6.2-cp38-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a66d6fb6ae7661c58995f9c6435bda2b1e68b54b598a6a10247bfcdadac996c", size = 1437614, upload-time = "2026-01-01T17:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/c4/58/fc6e649762b029315325ace1a8c6be66125e42f67416d3dbd47b69563d61/pynacl-1.6.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:26bfcd00dcf2cf160f122186af731ae30ab120c18e8375684ec2670dccd28130", size = 818251, upload-time = "2026-01-01T17:32:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a8/b917096b1accc9acd878819a49d3d84875731a41eb665f6ebc826b1af99e/pynacl-1.6.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c8a231e36ec2cab018c4ad4358c386e36eede0319a0c41fed24f840b1dac59f6", size = 1402859, upload-time = "2026-01-01T17:32:27.215Z" }, + { url = "https://files.pythonhosted.org/packages/85/42/fe60b5f4473e12c72f977548e4028156f4d340b884c635ec6b063fe7e9a5/pynacl-1.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:68be3a09455743ff9505491220b64440ced8973fe930f270c8e07ccfa25b1f9e", size = 791926, upload-time = "2026-01-01T17:32:29.314Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/e40e318c604259301cc091a2a63f237d9e7b424c4851cafaea4ea7c4834e/pynacl-1.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b097553b380236d51ed11356c953bf8ce36a29a3e596e934ecabe76c985a577", size = 1363101, upload-time = "2026-01-01T17:32:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/47/e761c254f410c023a469284a9bc210933e18588ca87706ae93002c05114c/pynacl-1.6.2-cp38-abi3-win32.whl", hash = "sha256:5811c72b473b2f38f7e2a3dc4f8642e3a3e9b5e7317266e4ced1fba85cae41aa", size = 227421, upload-time = "2026-01-01T17:32:33.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/ad/334600e8cacc7d86587fe5f565480fde569dfb487389c8e1be56ac21d8ac/pynacl-1.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:62985f233210dee6548c223301b6c25440852e13d59a8b81490203c3227c5ba0", size = 239754, upload-time = "2026-01-01T17:32:34.557Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/5945b5af29534641820d3bd7b00962abbbdfee84ec7e19f0d5b3175f9a31/pynacl-1.6.2-cp38-abi3-win_arm64.whl", hash = "sha256:834a43af110f743a754448463e8fd61259cd4ab5bbedcf70f9dabad1d28a394c", size = 184801, upload-time = "2026-01-01T17:32:36.309Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-arango" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pyjwt" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/c6/482b66f5acede5556ebc3723fc15b386b1d3bb119bc37e275e46b9735fad/python_arango-8.3.3.tar.gz", hash = "sha256:399df2edb2f52d50ec41c9115ac20b46550a4808edf76450d77cdf36efeb0cf2", size = 155272, upload-time = "2026-06-01T11:18:39.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/b0/99c4e9ddaa192dab822718fbc5ef558d240fee7bf576142b19a6ba207937/python_arango-8.3.3-py3-none-any.whl", hash = "sha256:c09bc68a9a331a772d6e4fdcbf16a5ef64c3038f90833e834492da6041463408", size = 116346, upload-time = "2026-06-01T11:18:38.684Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/12/38c1a0b1e64806780c9563e3fc9f6e472251839662587cfbe9bfaf2ae10a/python_discovery-1.4.0.tar.gz", hash = "sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3", size = 68455, upload-time = "2026-05-28T01:15:37.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/8d/3d316429f65029532bb1e28ff77b797d86b5ac3915bb44ca4e19aa283d43/python_discovery-1.4.0-py3-none-any.whl", hash = "sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da", size = 33217, upload-time = "2026-05-28T01:15:36.573Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-keycloak" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "deprecation" }, + { name = "httpx" }, + { name = "jwcrypto" }, + { name = "requests" }, + { name = "requests-toolbelt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/e3/963aae33e9177b496def0ba47d2290391e9391d9f76a3f69cfde8c44c8b8/python_keycloak-7.1.1.tar.gz", hash = "sha256:38973e54694a656fe6f3f8fc2af0b89c78136863f928261f0d243445e9e486bc", size = 78907, upload-time = "2026-02-15T08:45:58.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/9f/569a8bbdb0859498d33d8b86273bc82849bd0b445ac01416ad34996ca3d8/python_keycloak-7.1.1-py3-none-any.whl", hash = "sha256:d8295bec6c4805ab7335b03bc92753c8c6258d5511b080cac061da20ae77f61c", size = 87607, upload-time = "2026-02-15T08:45:57.054Z" }, +] + +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, + { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "qdrant-client" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "httpx", extra = ["http2"] }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "portalocker" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/45/5b1bdd15a3c7730eefb9c113600829e20d689b82b5a23f9e07d107094004/qdrant_client-1.18.0.tar.gz", hash = "sha256:52e8ece1a7d40519801bf0b70713bfa0f6b7ae28c7275bbe0b0286fbed7f6db4", size = 352580, upload-time = "2026-05-11T14:12:38.702Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/10/c437bd2ac41ef30d3019063e6ce537dc111e9214473b337ee88f7fa6359a/qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd", size = 398126, upload-time = "2026-05-11T14:12:36.998Z" }, +] + +[[package]] +name = "reactivex" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/af/38a4b62468e4c5bd50acf511d86fe62e65a466aa6abb55b1d59a4a9e57f3/reactivex-4.1.0.tar.gz", hash = "sha256:c7499e3c802bccaa20839b3e17355a7d939573fded3f38ba3d4796278a169a3d", size = 113482, upload-time = "2025-11-05T21:44:24.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/9e/3c2f5d3abb6c5d82f7696e1e3c69b7279049e928596ce82ed25ca97a08f3/reactivex-4.1.0-py3-none-any.whl", hash = "sha256:485750ec8d9b34bcc8ff4318971d234dc4f595058a1b4435a74aefef4b2bc9bd", size = 218588, upload-time = "2025-11-05T21:44:23.015Z" }, +] + +[[package]] +name = "readme-renderer" +version = "44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nh3" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/a9/104ec9234c8448c4379768221ea6df01260cd6c2ce13182d4eac531c8342/readme_renderer-44.0.tar.gz", hash = "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1", size = 32056, upload-time = "2024-07-08T15:00:57.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl", hash = "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", size = 13310, upload-time = "2024-07-08T15:00:56.577Z" }, +] + +[[package]] +name = "redis" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c3/928b290c2c0ca99ab96eea5b4ff8f30be8112b075301a7d3ba214a3c8c12/redis-8.0.1.tar.gz", hash = "sha256:afc5a7a2f5a084f5b1880dec548dd45be17db7e43c82a30d84f952aefb05cfb0", size = 5114170, upload-time = "2026-06-23T14:52:37.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/0a/c2345ebf1ebe70840ce3f6c6ee612f8fa749cfbd1b03069c53bf0c62aaad/redis-8.0.1-py3-none-any.whl", hash = "sha256:47daa35a058c23468d6437f17a8c76882cb316b838ef763036af99b96cedd743", size = 502406, upload-time = "2026-06-23T14:52:36.137Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "rpds-py", version = "2026.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/a0/acf8b6fc20bfdcd3a45bd3f57680fb198e157b7e997b9123b10763798bd2/rpds_py-2026.5.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3397a5ed7174dc2786bb214030232fc36fe8e5584fec43a9952cc542b1a12036", size = 355609, upload-time = "2026-05-28T11:58:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/b6/95/f8203fd997484b1690a6869cd0e503b6c3c6be55b0ecc36d1a491fe742f0/rpds_py-2026.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:99ab6ba7bfa2cb0f96a04e3652355bf04e3f51aceb1e943b8541dab7ba4828cc", size = 348460, upload-time = "2026-05-28T11:58:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/33/8c/b47326ad2f0be545a5e5c1a55937a12afaea7d392ba2837bb9680f57e6c9/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0efbe45632665e53e3db8fe1e5692db58fc5cb9bab4459d570b83efefe11164", size = 381031, upload-time = "2026-05-28T11:58:53.775Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/e83bbd97ffac6f6389b605cd4e1c8ac5761dc7e977769c9255d8c5adb7bd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:01d17b29c0c23d82b1f4751147ec49cf451f1fc2554eb9ef5f957e55d2656ead", size = 387121, upload-time = "2026-05-28T11:58:55.243Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/d285d1bc8864245919c61e1ca82263e4a66d337759c3a4cef72766ff9afc/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7559f72b94ae52659086c595dfa017cde03155f7832071d30959049052cb3ece", size = 501026, upload-time = "2026-05-28T11:58:56.788Z" }, + { url = "https://files.pythonhosted.org/packages/86/06/ccb2109a1e543437b5e43816f2b43b9554cc6783145528a4e3711e05c011/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e25b7088f9ccbfc0dfcaa52bf969300ca229e10ecf758974ebcbb080a4b37bb", size = 391865, upload-time = "2026-05-28T11:58:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/3d/33/237173db1cfef10105b3839a24de00eb8d2a523711add4632447cdf0aedd/rpds_py-2026.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613fc4ee9eaef26dc5840666214dd6fbcebcf32f46e76f4abc473059f4e13dda", size = 378012, upload-time = "2026-05-28T11:58:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/97/64/1eae54e34d5161f9969295e80bd6b62a55f2b6ac5f2a5b60d02c2140e758/rpds_py-2026.5.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:85264a90ff4c05c1568dd65f5921c837614b67c60358fb4c17df3b7f2e90690a", size = 391111, upload-time = "2026-05-28T11:59:01.104Z" }, + { url = "https://files.pythonhosted.org/packages/d8/34/5bb334a5a0f65d77869217c4654f34c78a7d11b93938a3c076a2edeafc52/rpds_py-2026.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe71bca7d547acb17027c7fd1624ff8aae623499c498d3e7011182c4de5c25e0", size = 409225, upload-time = "2026-05-28T11:59:02.433Z" }, + { url = "https://files.pythonhosted.org/packages/16/0f/007ec21283b5b040b4ec3bd95e0402591e22bfa7d5c93dfe01c465c2d2d7/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05fa4f41f37ec97c9c260441a940450a192f78d774d2b097eee1379f1e1246a", size = 556487, upload-time = "2026-05-28T11:59:04.012Z" }, + { url = "https://files.pythonhosted.org/packages/ff/10/5437c94508169b6b22d8418fef7a66e9ffb5f3b9e9c94460f2eedafe06ff/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df1d2a1996755b24b9ecee92cb4d36c28f86f464a6a173349c26bab41e94b8c2", size = 620798, upload-time = "2026-05-28T11:59:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d5/9937dce4d6bda74157b954e7d1460db05a22f5929dccfeeba1ed27a93df0/rpds_py-2026.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8895840ac4809e5f60c88fd07617cd71326e73d6e5a8aa783c5c0f7c24985de2", size = 584053, upload-time = "2026-05-28T11:59:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/750617dd0ae1752471bf43f9e41d263398fae7cde7849d23b8574a70e617/rpds_py-2026.5.1-cp311-cp311-win32.whl", hash = "sha256:3684a59b158a7683aaeb8e25352e9a9dd2122cec78f2d8530266e4f91b4c7b3f", size = 214390, upload-time = "2026-05-28T11:59:08.402Z" }, + { url = "https://files.pythonhosted.org/packages/3c/bb/3dcab0e1d9516303f2eb672a5d6f62eca5a69e2886301e9c8c54b520c39b/rpds_py-2026.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:7bd530e6a530bb3ea892f194fafa455f3516ac25ecf7143fd33c09be62b0470a", size = 231097, upload-time = "2026-05-28T11:59:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/49/d6/c6bbf5cb1cf12b9732df8074b57f6ef8341ba884c95d40632ae8bddb44e4/rpds_py-2026.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:0a5ae4dbe43c1076983b72616496919872ae7bbe7a1e21cc48336bc3154d130b", size = 226361, upload-time = "2026-05-28T11:59:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, + { url = "https://files.pythonhosted.org/packages/42/56/3fe0fb34820ff667be791b3a3c22b85e8bcba54e9c832f47438c191fa7be/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:edf2765d84e42447f112ad877af8fe1db0089aaec5b28e88d6eab45e7fe99cea", size = 357151, upload-time = "2026-05-28T12:01:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/3eb9ccdb9f143b8c9b003978898cb497f942a324c077401e6b8834238e63/rpds_py-2026.5.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad3773236e95f7f33991eb125224b7da66f206504d032a253a02da7e134519fb", size = 350195, upload-time = "2026-05-28T12:01:54.901Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/dbda232bc4f3ed732120692ab0d2c8402cb020516556d8bee622dcef2413/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04df86b3f0fade39ec8fd0e0aab089b1da9fbd2b48df778a57ef96f5e7d38df", size = 381850, upload-time = "2026-05-28T12:01:56.601Z" }, + { url = "https://files.pythonhosted.org/packages/40/30/32e769839a358f78810c234f160f2cc21d1e4e47e1c0e0e0d535be5a0219/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6142dbd80c4df62a5d899f0d616d417f84e0bc8d32526c8e5589019d75d028a7", size = 387899, upload-time = "2026-05-28T12:01:58.212Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/ec84d243aadb3b34b71dd26a010d0930b2d284ff5fc9a69fec53810ee6fd/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b35217adefe87f2fe4db7e9766cabe84744bfe9616d9667be18988928c7f2dc", size = 501618, upload-time = "2026-05-28T12:01:59.888Z" }, + { url = "https://files.pythonhosted.org/packages/74/25/b60e52686bbff777a64f9e4f4d3dd57980dc846913777177a2c92e4937aa/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b95d5e11fc712b752081183a55a244c03cd00570489edd7014d8899f8ceb8162", size = 394003, upload-time = "2026-05-28T12:02:01.482Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c7/b3a6a588cc2219510ef3f42e207483a93950bedd1e3a0fd4015c95cff9e5/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141c9498daf2ace9eda35d2b0e376f9ea8b058d84f2aef4f96fccfd449a2f251", size = 379778, upload-time = "2026-05-28T12:02:03.197Z" }, + { url = "https://files.pythonhosted.org/packages/31/00/c7dba3fc8a3da8cb3f6db1eb3386be4d79c2e97c6890d20eb9ac66ae8c43/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:6f249f8b860a200ad35193af961183ebe9132710484e6f6ce0cf89fd83c63a9a", size = 392359, upload-time = "2026-05-28T12:02:04.817Z" }, + { url = "https://files.pythonhosted.org/packages/93/dd/472ba494c70753f93745992c99855bee0636daf74e6984e5e003f150316f/rpds_py-2026.5.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e4abbf391a70be864920858bf360f4fb380577c9a0f732438a1996726e2c195b", size = 412820, upload-time = "2026-05-28T12:02:06.401Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6f/93831a3bfe789542ed0c1d0d74b78b440f055d6dc3ea4640eba2d95e6e23/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:c74005a7bb87752acf351c93897ec63ad77a07a0da7ecad9c050e32e7286ba34", size = 557243, upload-time = "2026-05-28T12:02:08.013Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ff/0b3d604614ffc77522c6b288fdbce68957eb583da1002aa65ba38ac0ee40/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:8213afbe8a3a906fb9acb2014423fe3359ee783d0bf90995f70623a3217bfa6c", size = 623541, upload-time = "2026-05-28T12:02:09.661Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ea/e7b0251441da9adfeaebcf29601d10f2a1455fcf0772fae9e7e19032bd96/rpds_py-2026.5.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8c43a8a973270fd173bf48cdf80bbe66312421cba68d40845034f174f2389049", size = 586326, upload-time = "2026-05-28T12:02:11.47Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, +] + +[[package]] +name = "scramp" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asn1crypto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/52/a866f1ac9ae9025ec7f9bea803bba9d54796f8a84236165a700831f61b27/scramp-1.4.8.tar.gz", hash = "sha256:bd018fabfe46343cceeb9f1c3e8d23f55770271e777e3accbfaee3ff0a316e71", size = 16630, upload-time = "2026-01-06T21:01:01.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/a962d2477331abfdb2c6a8251b65c673dbb07ad707d1882d61562b8b9147/scramp-1.4.8-py3-none-any.whl", hash = "sha256:87c2f15976845a2872fe5490a06097f0d01813cceb53774ea168c911f2ad025c", size = 13121, upload-time = "2026-01-06T21:00:59.474Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, +] + +[[package]] +name = "selenium" +version = "4.45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "trio" }, + { name = "trio-websocket" }, + { name = "typing-extensions" }, + { name = "urllib3", extra = ["socks"] }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/48/486aa67320f27452e9f551b8608f1a59ce7091c8fe7ebc9f4eba274775d4/selenium-4.45.0.tar.gz", hash = "sha256:563f0c4102f112df1cda30d46ce6d177b2e4a7a3d4b0756902d5dc84d3a8a365", size = 1005503, upload-time = "2026-06-16T04:43:57.915Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/8a/6ff6beb9c7c6cc642f628df9328a8b6637f86602eff8d28e70b5d4e8bca7/selenium-4.45.0-py3-none-any.whl", hash = "sha256:1fd9d0dc08192b2f8100e264ed720f83b05d2dd3a7feff673df04e0c7580df4b", size = 9536616, upload-time = "2026-06-16T04:43:55.968Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and sys_platform == 'win32'", + "python_full_version >= '3.15' and sys_platform == 'emscripten'", + "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'win32'", + "python_full_version == '3.14.*' and sys_platform == 'emscripten'", + "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" }, + { url = "https://files.pythonhosted.org/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" }, + { url = "https://files.pythonhosted.org/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" }, + { url = "https://files.pythonhosted.org/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" }, + { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" }, + { url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" }, + { url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" }, + { url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, +] + +[[package]] +name = "sqlalchemy-cockroachdb" +version = "2.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/46/1462dd4734c58e04b754db71a9607de6d748dedea86e30007b5ae7b4e3db/sqlalchemy_cockroachdb-2.0.4.tar.gz", hash = "sha256:f66354d2b93da89cb4524c5b78a44d0cf5a8943d328b7f0aee4aa1dfc6178b65", size = 31803, upload-time = "2026-04-23T18:12:28.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/f1/e08ec3fc57b6f890e8f6eb16734c4865626635f4863cc2fe7d259699ce7b/sqlalchemy_cockroachdb-2.0.4-py3-none-any.whl", hash = "sha256:0804306a733a1fe09b2d496eadf8bf35d878862b6138e69175136fa2ca0ce5d0", size = 22726, upload-time = "2026-04-23T18:12:26.936Z" }, +] + +[[package]] +name = "sqlalchemy-cratedb" +version = "0.43.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "crate" }, + { name = "geojson" }, + { name = "sqlalchemy" }, + { name = "verlib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/35/4fbea8c79f6df5d06ee1956648bb9ef4f08114cd9dae0329ab1cd2c45f20/sqlalchemy_cratedb-0.43.1.tar.gz", hash = "sha256:0466573af3dc5b796dc6df45ac4085d67eb4fba614a72a029c2b4671aead78a7", size = 48411, upload-time = "2026-06-22T08:54:22.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/3c/946062f221bde60332ac175aeb40a7426a6e076056b4ad60c7bf5af7ece8/sqlalchemy_cratedb-0.43.1-py3-none-any.whl", hash = "sha256:1db7a30a7f76a023f63b0c4d396e4c32becd45c66c72c327f58720685335199b", size = 46774, upload-time = "2026-06-22T08:54:21.293Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "testcontainers" +version = "4.15.0" +source = { editable = "." } +dependencies = [ + { name = "docker" }, + { name = "python-dotenv" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "wrapt" }, +] + +[package.optional-dependencies] +arangodb = [ + { name = "python-arango" }, +] +aws = [ + { name = "boto3" }, + { name = "httpx" }, +] +azurite = [ + { name = "azure-storage-blob" }, +] +chroma = [ + { name = "chromadb-client" }, +] +clickhouse = [ + { name = "clickhouse-driver" }, +] +cosmosdb = [ + { name = "azure-cosmos" }, +] +cratedb = [ + { name = "httpx" }, + { name = "sqlalchemy-cratedb" }, +] +db2 = [ + { name = "ibm-db-sa", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64'" }, + { name = "sqlalchemy" }, +] +generic = [ + { name = "httpx" }, + { name = "redis" }, + { name = "sqlalchemy" }, +] +google = [ + { name = "google-cloud-datastore" }, + { name = "google-cloud-pubsub" }, +] +influxdb = [ + { name = "influxdb" }, + { name = "influxdb-client" }, +] +k3s = [ + { name = "kubernetes" }, + { name = "pyyaml" }, +] +keycloak = [ + { name = "python-keycloak", marker = "python_full_version < '4'" }, +] +localstack = [ + { name = "boto3" }, +] +mailpit = [ + { name = "cryptography" }, +] +minio = [ + { name = "minio" }, +] +mongodb = [ + { name = "pymongo" }, +] +mssql = [ + { name = "pymssql" }, + { name = "sqlalchemy" }, +] +mysql = [ + { name = "pymysql", extra = ["rsa"] }, + { name = "sqlalchemy" }, +] +nats = [ + { name = "nats-py" }, +] +neo4j = [ + { name = "neo4j" }, +] +openfga = [ + { name = "openfga-sdk" }, +] +opensearch = [ + { name = "opensearch-py", marker = "python_full_version < '4'" }, +] +oracle = [ + { name = "oracledb" }, + { name = "sqlalchemy" }, +] +oracle-free = [ + { name = "oracledb" }, + { name = "sqlalchemy" }, +] +qdrant = [ + { name = "qdrant-client" }, +] +rabbitmq = [ + { name = "pika" }, +] +redis = [ + { name = "redis" }, +] +registry = [ + { name = "bcrypt" }, +] +scylla = [ + { name = "cassandra-driver", marker = "python_full_version < '3.14'" }, +] +selenium = [ + { name = "selenium" }, +] +sftp = [ + { name = "cryptography" }, +] +test-module-import = [ + { name = "httpx" }, +] +trino = [ + { name = "trino" }, +] +weaviate = [ + { name = "weaviate-client" }, +] + +[package.dev-dependencies] +dev = [ + { name = "anyio" }, + { name = "boto3-stubs-lite", extra = ["boto3"] }, + { name = "cassandra-driver" }, + { name = "hvac", marker = "python_full_version < '4'" }, + { name = "kafka-python-ng" }, + { name = "mkdocs" }, + { name = "mkdocs-codeinclude-plugin" }, + { name = "mkdocs-include-markdown-plugin" }, + { name = "mkdocs-markdownextradata-plugin" }, + { name = "mkdocs-material" }, + { name = "mypy" }, + { name = "paho-mqtt" }, + { name = "paramiko" }, + { name = "pg8000" }, + { name = "pre-commit" }, + { name = "psycopg" }, + { name = "psycopg2-binary" }, + { name = "pymilvus" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sqlalchemy" }, + { name = "sqlalchemy-cockroachdb" }, + { name = "sqlalchemy-cratedb" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "twine" }, + { name = "types-docker" }, + { name = "types-paramiko" }, +] +docs = [ + { name = "mkdocs" }, + { name = "mkdocs-codeinclude-plugin" }, + { name = "mkdocs-include-markdown-plugin" }, + { name = "mkdocs-markdownextradata-plugin" }, + { name = "mkdocs-material" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +lint = [ + { name = "boto3-stubs-lite", extra = ["boto3"] }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "ruff" }, + { name = "types-docker" }, + { name = "types-paramiko" }, +] +test = [ + { name = "anyio" }, + { name = "cassandra-driver" }, + { name = "hvac", marker = "python_full_version < '4'" }, + { name = "kafka-python-ng" }, + { name = "paho-mqtt" }, + { name = "paramiko" }, + { name = "pg8000" }, + { name = "psycopg" }, + { name = "psycopg2-binary" }, + { name = "pymilvus" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-mock" }, + { name = "pytest-xdist" }, + { name = "sqlalchemy" }, + { name = "sqlalchemy-cockroachdb" }, + { name = "sqlalchemy-cratedb" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "twine" }, +] + +[package.metadata] +requires-dist = [ + { name = "azure-cosmos", marker = "extra == 'cosmosdb'", specifier = ">=4" }, + { name = "azure-storage-blob", marker = "extra == 'azurite'", specifier = ">=12" }, + { name = "bcrypt", marker = "extra == 'registry'", specifier = ">=5" }, + { name = "boto3", marker = "extra == 'aws'", specifier = ">=1" }, + { name = "boto3", marker = "extra == 'localstack'", specifier = ">=1" }, + { name = "cassandra-driver", marker = "python_full_version < '3.14' and extra == 'scylla'", specifier = ">=3" }, + { name = "chromadb-client", marker = "extra == 'chroma'", specifier = ">=1" }, + { name = "clickhouse-driver", marker = "extra == 'clickhouse'" }, + { name = "cryptography", marker = "extra == 'mailpit'" }, + { name = "cryptography", marker = "extra == 'sftp'" }, + { name = "docker" }, + { name = "google-cloud-datastore", marker = "extra == 'google'", specifier = ">=2" }, + { name = "google-cloud-pubsub", marker = "extra == 'google'", specifier = ">=2" }, + { name = "httpx", marker = "extra == 'aws'" }, + { name = "httpx", marker = "extra == 'cratedb'" }, + { name = "httpx", marker = "extra == 'generic'" }, + { name = "httpx", marker = "extra == 'test-module-import'" }, + { name = "ibm-db-sa", marker = "platform_machine != 'aarch64' and platform_machine != 'arm64' and extra == 'db2'" }, + { name = "influxdb", marker = "extra == 'influxdb'", specifier = ">=5" }, + { name = "influxdb-client", marker = "extra == 'influxdb'", specifier = ">=1" }, + { name = "kubernetes", marker = "extra == 'k3s'" }, + { name = "minio", marker = "extra == 'minio'", specifier = ">=7" }, + { name = "nats-py", marker = "extra == 'nats'", specifier = ">=2" }, + { name = "neo4j", marker = "extra == 'neo4j'", specifier = ">=6" }, + { name = "openfga-sdk", marker = "extra == 'openfga'" }, + { name = "opensearch-py", marker = "python_full_version < '4' and extra == 'opensearch'", specifier = ">=3" }, + { name = "oracledb", marker = "extra == 'oracle'", specifier = ">=3" }, + { name = "oracledb", marker = "extra == 'oracle-free'", specifier = ">=3" }, + { name = "pika", marker = "extra == 'rabbitmq'", specifier = ">=1" }, + { name = "pymongo", marker = "extra == 'mongodb'", specifier = ">=4" }, + { name = "pymssql", marker = "extra == 'mssql'", specifier = ">=2" }, + { name = "pymysql", extras = ["rsa"], marker = "extra == 'mysql'", specifier = ">=1" }, + { name = "python-arango", marker = "extra == 'arangodb'", specifier = ">=8" }, + { name = "python-dotenv" }, + { name = "python-keycloak", marker = "python_full_version < '4' and extra == 'keycloak'", specifier = ">=6" }, + { name = "pyyaml", marker = "extra == 'k3s'", specifier = ">=6.0.3" }, + { name = "qdrant-client", marker = "extra == 'qdrant'", specifier = ">=1" }, + { name = "redis", marker = "extra == 'generic'", specifier = ">=7" }, + { name = "redis", marker = "extra == 'redis'", specifier = ">=7" }, + { name = "selenium", marker = "extra == 'selenium'", specifier = ">=4" }, + { name = "sqlalchemy", marker = "extra == 'db2'" }, + { name = "sqlalchemy", marker = "extra == 'generic'" }, + { name = "sqlalchemy", marker = "extra == 'mssql'" }, + { name = "sqlalchemy", marker = "extra == 'mysql'" }, + { name = "sqlalchemy", marker = "extra == 'oracle'" }, + { name = "sqlalchemy", marker = "extra == 'oracle-free'" }, + { name = "sqlalchemy-cratedb", marker = "extra == 'cratedb'" }, + { name = "trino", marker = "extra == 'trino'" }, + { name = "typing-extensions" }, + { name = "urllib3" }, + { name = "weaviate-client", marker = "extra == 'weaviate'", specifier = ">=4" }, + { name = "wrapt" }, +] +provides-extras = ["arangodb", "aws", "azurite", "cassandra", "clickhouse", "cosmosdb", "cockroachdb", "cratedb", "db2", "elasticsearch", "generic", "test-module-import", "google", "influxdb", "k3s", "kafka", "keycloak", "localstack", "mailpit", "memcached", "minio", "milvus", "mongodb", "mqtt", "mssql", "mysql", "nats", "neo4j", "nginx", "openfga", "opensearch", "ollama", "oracle", "oracle-free", "postgres", "qdrant", "rabbitmq", "redis", "registry", "selenium", "scylla", "sftp", "toxiproxy", "valkey", "vault", "weaviate", "chroma", "trino"] + +[package.metadata.requires-dev] +dev = [ + { name = "anyio", specifier = ">=4" }, + { name = "boto3-stubs-lite", extras = ["boto3"] }, + { name = "cassandra-driver", specifier = ">=3.30" }, + { name = "hvac", marker = "python_full_version < '4'", specifier = ">=2" }, + { name = "kafka-python-ng", specifier = ">=2" }, + { name = "mkdocs", specifier = ">=1.5.3,<2.0.0" }, + { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1,<0.3.0" }, + { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.8,<8.0.0" }, + { name = "mkdocs-markdownextradata-plugin", specifier = ">=0.2.6,<0.3.0" }, + { name = "mkdocs-material", specifier = ">=9.5.0,<10.0.0" }, + { name = "mypy", specifier = ">=1" }, + { name = "paho-mqtt", specifier = ">=2" }, + { name = "paramiko", specifier = ">=4" }, + { name = "pg8000", specifier = "==1.31.5" }, + { name = "pre-commit", specifier = ">=4" }, + { name = "psycopg", specifier = ">=3" }, + { name = "psycopg2-binary", specifier = "==2.9.11" }, + { name = "pymilvus", specifier = ">=2" }, + { name = "pytest", specifier = ">=9" }, + { name = "pytest-asyncio", specifier = ">=1" }, + { name = "pytest-cov", specifier = ">=7" }, + { name = "pytest-mock", specifier = ">=3" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, + { name = "ruff" }, + { name = "sphinx", marker = "python_full_version < '3.11'", specifier = ">=8" }, + { name = "sphinx", marker = "python_full_version >= '3.11'", specifier = ">=9" }, + { name = "sqlalchemy" }, + { name = "sqlalchemy-cockroachdb", specifier = ">=2" }, + { name = "sqlalchemy-cratedb" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, + { name = "twine", specifier = ">=7.0.0" }, + { name = "types-docker", specifier = ">=7.1.0.20260518" }, + { name = "types-paramiko", specifier = ">=4" }, +] +docs = [ + { name = "mkdocs", specifier = ">=1.5.3,<2.0.0" }, + { name = "mkdocs-codeinclude-plugin", specifier = ">=0.2.1,<0.3.0" }, + { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.8,<8.0.0" }, + { name = "mkdocs-markdownextradata-plugin", specifier = ">=0.2.6,<0.3.0" }, + { name = "mkdocs-material", specifier = ">=9.5.0,<10.0.0" }, + { name = "sphinx", marker = "python_full_version < '3.11'", specifier = ">=8" }, + { name = "sphinx", marker = "python_full_version >= '3.11'", specifier = ">=9" }, +] +lint = [ + { name = "boto3-stubs-lite", extras = ["boto3"] }, + { name = "mypy", specifier = ">=1" }, + { name = "pre-commit", specifier = ">=4" }, + { name = "ruff" }, + { name = "types-docker", specifier = ">=7.1.0.20260518" }, + { name = "types-paramiko", specifier = ">=4" }, +] +test = [ + { name = "anyio", specifier = ">=4" }, + { name = "cassandra-driver", specifier = ">=3.30" }, + { name = "hvac", marker = "python_full_version < '4'", specifier = ">=2" }, + { name = "kafka-python-ng", specifier = ">=2" }, + { name = "paho-mqtt", specifier = ">=2" }, + { name = "paramiko", specifier = ">=4" }, + { name = "pg8000", specifier = "==1.31.5" }, + { name = "psycopg", specifier = ">=3" }, + { name = "psycopg2-binary", specifier = "==2.9.11" }, + { name = "pymilvus", specifier = ">=2" }, + { name = "pytest", specifier = ">=9" }, + { name = "pytest-asyncio", specifier = ">=1" }, + { name = "pytest-cov", specifier = ">=7" }, + { name = "pytest-mock", specifier = ">=3" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, + { name = "sqlalchemy" }, + { name = "sqlalchemy-cockroachdb", specifier = ">=2" }, + { name = "sqlalchemy-cratedb" }, + { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0" }, + { name = "twine", specifier = ">=7.0.0" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "trino" +version = "0.338.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lz4" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "requests" }, + { name = "tzlocal" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/aa/4bc0539467c12d415e8f8870a79646be80d299c063c563a47fee0b6c9fa9/trino-0.338.0.tar.gz", hash = "sha256:1103d249cb96ba8f88f699588581965e2be088c16b9f8178e34cb64aa8a6c011", size = 57373, upload-time = "2026-06-29T19:55:37.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/b5/182b81d1b4cc2b306b174bd94efe5384f7d1253539eaf47a6b4d842b96a7/trino-0.338.0-py3-none-any.whl", hash = "sha256:093952f8a53b6f47ab357b77cbe92f1d66453290f7d8bcc4b74bd662e5ff2704", size = 59981, upload-time = "2026-06-29T19:55:36.449Z" }, +] + +[[package]] +name = "trio" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/b6/c744031c6f89b18b3f5f4f7338603ab381d740a7f45938c4607b2302481f/trio-0.33.0.tar.gz", hash = "sha256:a29b92b73f09d4b48ed249acd91073281a7f1063f09caba5dc70465b5c7aa970", size = 605109, upload-time = "2026-02-14T18:40:55.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/93/dab25dc87ac48da0fe0f6419e07d0bfd98799bed4e05e7b9e0f85a1a4b4b/trio-0.33.0-py3-none-any.whl", hash = "sha256:3bd5d87f781d9b0192d592aef28691f8951d6c2e41b7e1da4c25cde6c180ae9b", size = 510294, upload-time = "2026-02-14T18:40:53.313Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "outcome" }, + { name = "trio" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" }, +] + +[[package]] +name = "twine" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "id" }, + { name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" }, + { name = "packaging" }, + { name = "readme-renderer" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rfc3986" }, + { name = "rich" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz", hash = "sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size = 215032, upload-time = "2026-07-27T15:59:00.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/08/ddcdc06225eaad6de0e48e1002b06d919dbde20582d0662c7af51308e5d6/twine-7.0.0-py3-none-any.whl", hash = "sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7", size = 43204, upload-time = "2026-07-27T15:58:59.26Z" }, +] + +[[package]] +name = "types-awscrt" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/e3/40e8148a07a3c1c6fb151eddcfdd94083560e759e954b1f5a67ca0b36bcf/types_awscrt-0.33.0.tar.gz", hash = "sha256:803bc7e7e2f6172a0abd71df6593368f82fc23127ca15d287f360e9fcbd3a977", size = 19038, upload-time = "2026-05-25T06:56:04.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/a6/704fbf052edf2497d09292e5f9555b400594924f1154d6c86f2173c2fc11/types_awscrt-0.33.0-py3-none-any.whl", hash = "sha256:95adb57388e1cacc6e7e96fb7ddc735e60096a6151930640bdbe496d9400493a", size = 45687, upload-time = "2026-05-25T06:56:02.549Z" }, +] + +[[package]] +name = "types-docker" +version = "7.2.0.20260728" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/f9/7792217bb55b523f409e8699b41d849345fe673e7aae254e5402c8b6c391/types_docker-7.2.0.20260728.tar.gz", hash = "sha256:eeb780deddb000d3be7584cca16b02fc7f68b135b0039977be13c121861db197", size = 36538, upload-time = "2026-07-28T04:52:14.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/0b/733f7fb323bbf4394f69b7c6485b6b7d373c301261c26af08ad72bcfa456/types_docker-7.2.0.20260728-py3-none-any.whl", hash = "sha256:f8d7a80dbc12cb7d794bf22d42f7bb1545db6226ee204dc685812a4695ac9a23", size = 51168, upload-time = "2026-07-28T04:52:13.201Z" }, +] + +[[package]] +name = "types-paramiko" +version = "5.0.0.20260724" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c2/445549ed6f0944352f2b91d88d16bbdc3993a4d6dbbedb81c557b3cad85a/types_paramiko-5.0.0.20260724.tar.gz", hash = "sha256:37e7f3f2196cf187c89649ad836621c675bc318369d80fa78051507a4ae770c9", size = 28548, upload-time = "2026-07-24T04:59:59.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/16/fca52784f979821e2205ff514322d0e83452536acc4807a79ddb2085bbb2/types_paramiko-5.0.0.20260724-py3-none-any.whl", hash = "sha256:40c7083803a5a28ab7a8d2fe4cd9b7746d0a03538598582ce68f60967a2ad272", size = 37125, upload-time = "2026-07-24T04:59:58.955Z" }, +] + +[[package]] +name = "types-requests" +version = "2.33.0.20260518" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, +] + +[[package]] +name = "types-s3transfer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/64/42689150509eb3e6e82b33ee3d89045de1592488842ddf23c56957786d05/types_s3transfer-0.16.0.tar.gz", hash = "sha256:b4636472024c5e2b62278c5b759661efeb52a81851cde5f092f24100b1ecb443", size = 13557, upload-time = "2025-12-08T08:13:09.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/27/e88220fe6274eccd3bdf95d9382918716d312f6f6cef6a46332d1ee2feff/types_s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:1c0cd111ecf6e21437cb410f5cddb631bfb2263b77ad973e79b9c6d0cb24e0ef", size = 19247, upload-time = "2025-12-08T08:13:08.426Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "validators" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/66/a435d9ae49850b2f071f7ebd8119dd4e84872b01630d6736761e6e7fd847/validators-0.35.0.tar.gz", hash = "sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a", size = 73399, upload-time = "2025-05-01T05:42:06.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/6e/3e955517e22cbdd565f2f8b2e73d52528b14b8bcfdb04f62466b071de847/validators-0.35.0-py3-none-any.whl", hash = "sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd", size = 44712, upload-time = "2025-05-01T05:42:04.203Z" }, +] + +[[package]] +name = "verlib2" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/94/b3f689e44c14702ce67fdf24db649e8193958f8de971e69d024ef7e6b295/verlib2-0.3.2.tar.gz", hash = "sha256:a0a6af9838d8d26c18225a30c8d57fb70714462e3cd29f2451e7982d5a51e21e", size = 16987, upload-time = "2026-03-31T01:18:45.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/b2/5d2d39c1afb8b3904ed053dc4ec4a992f17c6a993e51e62a4af0d7878d00/verlib2-0.3.2-py3-none-any.whl", hash = "sha256:ddcc6f3a4f42019703ac5f6406faecefcd5f122e84a3d1a58190ddff23ec0426", size = 15054, upload-time = "2026-03-31T01:18:46.805Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0d/4e93c8e6d1001a75763f87d8f5ecda8ebc7f4aa2153dddfaf4ae8892821a/virtualenv-21.4.2.tar.gz", hash = "sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c", size = 7613326, upload-time = "2026-05-31T17:01:22.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/c4/557dc082be035381b85fdb2b74e21d3d21b57750b74f2b47a32f3a639ff9/virtualenv-21.4.2-py3-none-any.whl", hash = "sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae", size = 7594079, upload-time = "2026-05-31T17:01:20.735Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, +] + +[[package]] +name = "weaviate-client" +version = "4.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib" }, + { name = "grpcio" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "validators" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/2a/73cf7d6c7c6aa638738dfcb0d318e0404aab3c0673f82a1a4d89455b21a5/weaviate_client-4.22.0.tar.gz", hash = "sha256:0c50fbef546a522262a87d1138cde0509c7a8a48e702e967be33472e9f7fbae3", size = 860126, upload-time = "2026-06-18T06:08:30.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a3/27353ea3fbf9e7d4f03375176a838c632d009f5167d801adcbb5f6d3bd07/weaviate_client-4.22.0-py3-none-any.whl", hash = "sha256:ff2dbc8d1fc25739942402c22d1aba2350a16ba4a6b6ed0ba140689c70adf1d9", size = 652691, upload-time = "2026-06-18T06:08:28.622Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + +[[package]] +name = "wrapt" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/a4/282c8e64300a59fc834518a54bf0afabb4ff9218b5fa76958b450459a844/wrapt-2.2.2.tar.gz", hash = "sha256:0788e321027c999bf221b667bd4a54aaefd1a36283749a860ac3eb77daed0302", size = 129068, upload-time = "2026-06-20T23:49:44.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/8b/59781d0fe7b0adfbea37f600857de4be68921e454aeecf1a11bda35cdccc/wrapt-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:055e6fcfaa28e58c6a8c247d48b92be9d56f818b7068aa4f22b15b3343a09931", size = 80556, upload-time = "2026-06-20T23:47:28.473Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/66c61aca927230c9cf97a3cb005c803971a1076ff9f7d61085d035c20085/wrapt-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8374eb6b1a58809211e84ff835a182bb17ab2807a5bfef23204c8cff38178a00", size = 81648, upload-time = "2026-06-20T23:47:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/23/1b/545eee1c18f3af4cf140bb5822b6ef81ebe569df0a63ac109973103a30a5/wrapt-2.2.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:656593bb3f5529f03d27af4136c4d7b11990e470bcbc6fefa5ef218695bece55", size = 152956, upload-time = "2026-06-20T23:47:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/44/a7/6f42a3d03e44dc612a5dcff324e7366075a7857f0be2d49a8cb8a68279b8/wrapt-2.2.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfb00cb7bb22099e2f64b7340fb96113639aa7260c0972af3797ace2297b936c", size = 154771, upload-time = "2026-06-20T23:47:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/bf/55/4d76175aaa97523c38f1d28f79d18ab41a1b116814158a818bc0eba00571/wrapt-2.2.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7f10ee0bd53673bfd52b67cbce83336fe6cad90d2377b03baf66491d2bbfb91", size = 149460, upload-time = "2026-06-20T23:47:34.712Z" }, + { url = "https://files.pythonhosted.org/packages/84/9b/12e23264d8f4735e8483262f95c5a6b03c3665fd2a84bdf99a45b6a2f4ec/wrapt-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4402f57c5f0d0579599858ffbdd9bf4e3f0972f51096f2bd6cc7dab6b76ee49e", size = 153648, upload-time = "2026-06-20T23:47:36.092Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a3/bcd5ec37289dcd85ecd4d15395a6a6063d60bc45ff94a9d77814e1e54d64/wrapt-2.2.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3a4eb7964ff4643d333c84f880bcf554652b2a1050aebc54ae696327f61acfaf", size = 148502, upload-time = "2026-06-20T23:47:37.623Z" }, + { url = "https://files.pythonhosted.org/packages/f2/be/716d708f607fa70f8a6eb47dff8ee945d5278dfc89ffeeff33039d052e63/wrapt-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e542b7c5af91e2123a8aabf19894319d5ec4268d2a9ffd2f239386133fc47746", size = 152238, upload-time = "2026-06-20T23:47:39.118Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c0/1a48e7e54501274f5d906f18372221b13183b0afbb5b8bb4c7ca0392c0b4/wrapt-2.2.2-cp310-cp310-win32.whl", hash = "sha256:6e7e45b43d3c774d244fe7264378f5a3f0f383bc55a54a9866434e524540110f", size = 77278, upload-time = "2026-06-20T23:47:40.476Z" }, + { url = "https://files.pythonhosted.org/packages/b0/82/9cd69a1af288fbdedf01a10e3c8a0b6890b08c7f3f96d36a213699dbcd94/wrapt-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:955f1d6e72a352e478de8d8b503abe301c5e139a141b62eb0923bd694995025f", size = 80131, upload-time = "2026-06-20T23:47:41.785Z" }, + { url = "https://files.pythonhosted.org/packages/7f/73/8db7e27daef37ae70a53ea62bef7fe80cc51a8b5e9e9181a8be6eb9a999c/wrapt-2.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:b89d8d73c82db2bb7e6090b3afd7973f980d24e905cc34394eab60b884b3bf67", size = 79615, upload-time = "2026-06-20T23:47:43.109Z" }, + { url = "https://files.pythonhosted.org/packages/27/15/0c2d55168707465abfc41f33c0b23d792a5fa9b65c26983606940900a120/wrapt-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f1a2ff355ece6a111ca7a20dc86df6659c9205d3fcee674ca34f2a2854fd4e73", size = 80782, upload-time = "2026-06-20T23:47:44.367Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b5/5c0b093eb48f8a062ef6267d3cb36e9bb1b88440181f6545a383c60efdf8/wrapt-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55b9a899e6fff5444f229d30aa6e9ac92d2216d9d60f33c771b5d76a760d5f8e", size = 81678, upload-time = "2026-06-20T23:47:45.857Z" }, + { url = "https://files.pythonhosted.org/packages/34/f3/de70937472dd3e8a4e6811192f9c6075efdffd4a2cd9b4596bf160f89668/wrapt-2.2.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a2d78c363f97d8bd718ee40432c66395685e9e98528ccaa423c3355d1715a26d", size = 159671, upload-time = "2026-06-20T23:47:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/40aed2330e7f02ecf74386ffcfef9ccb7108c6a430f15b6a252b663b1bed/wrapt-2.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d619e1eed9bd4f6ed9f24cd61971aa086fa86505289628d464bcf8a2c2e3f328", size = 160785, upload-time = "2026-06-20T23:47:48.759Z" }, + { url = "https://files.pythonhosted.org/packages/45/04/aa5309beed5344b00220ae6b3b24055852192656194c27947bee1736306a/wrapt-2.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:518b0c5e323511ec56a38894802ddd5e1222626484e68efe63f201854ad788e5", size = 153699, upload-time = "2026-06-20T23:47:50.177Z" }, + { url = "https://files.pythonhosted.org/packages/01/df/2def7e99d1fe87eea413f95f671924cdddcb08823b1ffd212748dfa6d062/wrapt-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4bccea5cdecffa9dd70e343741f0e41e0a16619313d04b72f78bb525162ebcd0", size = 159695, upload-time = "2026-06-20T23:47:51.602Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f6/a906d01a2ce12157bad2404957b3e2140da354b8a70b2fa48bbf282871c0/wrapt-2.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:209112cafd963710a05d199aae431d79a28bc76eb8e6d1bbbb8ad24340722cae", size = 152813, upload-time = "2026-06-20T23:47:53.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/49/bc0086292d239575b4c08f4cf8a4079fa58abbad58ec23abf84833a283ed/wrapt-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5a5290e4bf2f332fc29ce72ffb9a2fff678aaac047e2e9f5f7165cd7792e099", size = 158809, upload-time = "2026-06-20T23:47:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/55/83/8fbd034de1f3e907edaa18786d5dd8f6932874edee0826c7cecb5cab03a1/wrapt-2.2.2-cp311-cp311-win32.whl", hash = "sha256:5499236ad1dc116012e2a5dd943f3f31af12fce452128e2bbcbd55a7d3d4d14c", size = 77414, upload-time = "2026-06-20T23:47:55.882Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9c/23695baa331c6de4e874c3d78b8e0bed92e1d2a274e665b29858f6841672/wrapt-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8636809939152be6ae20a6cef0fed9fe60f411b47847d0426a826884b469e971", size = 80368, upload-time = "2026-06-20T23:47:57.237Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/40cefc342bf89b234a4490d741290fce781774b831aefb39c25471da96c9/wrapt-2.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:5d0a142f7af07caeb5e5da87493162a7b8efa19ba919e550a746f7446e13fb30", size = 79489, upload-time = "2026-06-20T23:47:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/180b40628b23772692a0c76e8030114e1c0ae068470ed531919f0a5f2a4a/wrapt-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8417fd3c674d3c8023d080292d29301531a12daf8bd938dd419710dd2f464f2b", size = 81484, upload-time = "2026-06-20T23:47:59.924Z" }, + { url = "https://files.pythonhosted.org/packages/94/f2/21c90f2a16689702e2aaff45795b11018dff2c9b1242bac10d225483f676/wrapt-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e7070c7472582e31af3dfc2622b2381a0df7435110a9388ed8db5ffbce67efb", size = 82151, upload-time = "2026-06-20T23:48:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b3/7e6e9fcf4fe7e1b69a49fe6cc5a44e8224bab6283c5233c97e132f14908e/wrapt-2.2.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e096c9d39a59b35b63c9aacfbbbec2088ff51ff1fc31051acc60a07f42f273a", size = 169828, upload-time = "2026-06-20T23:48:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/894f132d857ed5a9904d937baf368badcbe5ea9e436e2f1930fe21c9f1f0/wrapt-2.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d1a6050405bf334be33bf66296f113563622972a34900ae6fa60fd283a1a900", size = 171544, upload-time = "2026-06-20T23:48:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/29/de/3c833e03725b477e9ea34028224dd21a48781830101e4e036f77e8b6b102/wrapt-2.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10adb01371408c6de504a6658b9886480f1a4919a83752748a387a504a21df79", size = 160663, upload-time = "2026-06-20T23:48:05.708Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/27edce350b24e3054d9d047f65f16d4c4d4c1f3f31c4278a1f8a95c723c8/wrapt-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3442eee2a5798f9b451f1b2cd7518ce8b7e28a2a364696c414460a0e295c012a", size = 169387, upload-time = "2026-06-20T23:48:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c4/9fd9679af8bf38e146652c7f47b6b352c3e5795b4ad1c0b7f94e15ac2aa7/wrapt-2.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6c99012a22f735a85eed7c4b86a3e99c30fdd57d9e115b2b45f796264b58d0bf", size = 158849, upload-time = "2026-06-20T23:48:08.91Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c2/aa6c0c2206803068c6859dabe01f8c84c43744da93d4c67b8946d21655ee/wrapt-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b686cfc008776a3952d6213cb296ed7f45d782a8453936406faa89eac0835ab", size = 168147, upload-time = "2026-06-20T23:48:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/42/63/3eb25da41049d20ae18fcab2dd8b056e02387c4bfa626cbdfb7c3b872e4f/wrapt-2.2.2-cp312-cp312-win32.whl", hash = "sha256:ef2cce266b5b0b07e19fa82e59673b81142b7a3607c8ed1254113d048ed668da", size = 77734, upload-time = "2026-06-20T23:48:11.769Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/0390e008a305360948fa9ce69507d041ac12cb2ee5d28e34467e2ee79391/wrapt-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:abf8c20a2d72ee69e16328b3c91342c446e723bfe48bfcc4dded3b9722ac027f", size = 80585, upload-time = "2026-06-20T23:48:13.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/84c445c66969f2d3457276b183a48c91097d59bbef9af6c075366b0f8c36/wrapt-2.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:c6c64c5d02578bc4c4bca4f0aef1504de933c1d5b4ac2710b9131111459506c8", size = 79553, upload-time = "2026-06-20T23:48:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/43/fc/f32f4b22c6511173c11d9e541ab4e7d8467a0f1b3455acaf784115d31ff8/wrapt-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e8b648270c613720a202d9a45ebabc33261b22c3a839b115ac5bce8c0bb0d69", size = 81296, upload-time = "2026-06-20T23:48:15.881Z" }, + { url = "https://files.pythonhosted.org/packages/72/06/4d117d5d77a9344776c0248b24dae3d3dd2f58e5f765fa08cf887072e719/wrapt-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6fb7e94e8fe3e4c3067bb1653a91cce7c5e83acc119fdd41501b1bf74654617", size = 81841, upload-time = "2026-06-20T23:48:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/15/ff/63ad96f98eb58a742b1a20d80f21da88924405910149950b912368150468/wrapt-2.2.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb18fc51e813df0d9c98049e3bf2298a5495a648602040e21fa3c7329371159e", size = 167882, upload-time = "2026-06-20T23:48:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/8bb62d8933df7acf3247194e6e9fc68edf9d2fa203252c89c94b319dd472/wrapt-2.2.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b00b00f806eb3ef2abe9049ed45994a81ee9284884d96e6b8314927c6cea3d", size = 167411, upload-time = "2026-06-20T23:48:20.315Z" }, + { url = "https://files.pythonhosted.org/packages/17/09/8789dcb09ee1de715727db7521aabbb68ffa68dfade3a49468440cfced49/wrapt-2.2.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:62415fd095bc590b842b6d092f2b5d9ccbaeb7e0b28535c03dcea2718b48636b", size = 158607, upload-time = "2026-06-20T23:48:21.728Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/66e02562d53ee67d841f175e38e3c993c2d78a3e104c576cad61c028b43c/wrapt-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a41e758d80dc0ab8c210f641ac892009d356cf1f955d97db544c8dd317b4d14c", size = 166367, upload-time = "2026-06-20T23:48:23.177Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a3/832ac4e41222fb263b3042d42c2f08d305db7d0f0c9b1d3a271a9eede8f6/wrapt-2.2.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b84cd4058001c9727b0e9980b7a9e66325b5ca748b1b578e822cade1bc6b304f", size = 157176, upload-time = "2026-06-20T23:48:24.711Z" }, + { url = "https://files.pythonhosted.org/packages/b7/01/1bd5e4d2df9c0178989ac8da9186543465388588ee2ef153e2591accebef/wrapt-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26fc73a1b15e0946d2942b9a4426d162b51676338327dc067ccd8d2d76385f94", size = 167025, upload-time = "2026-06-20T23:48:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/1c/69/583ed25291ab53e1ec117135fb1c33425e2f46d2bc8f29c17f7a94cf4274/wrapt-2.2.2-cp313-cp313-win32.whl", hash = "sha256:3c4095803491f6ef72128914c28ec05bbad9758433bb35f6715a3e9c8e46fb2d", size = 77605, upload-time = "2026-06-20T23:48:27.643Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/e69fc6d06e1523c68e0d00f95c9aed1158ce9908ee41603f7f2eae3d5db6/wrapt-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:2cb07f414fab25dbe6b5c7398e1491423a5c81a6209533639969a6c928d474a4", size = 80508, upload-time = "2026-06-20T23:48:29.013Z" }, + { url = "https://files.pythonhosted.org/packages/55/21/fe7a393d9e5dc0923bed8f5d857e9dcff210f1fa0888c02cc8f3ffaa55aa/wrapt-2.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:1fc7691f070220215cccb2a20836b9adbaecb8ff22ad47abe63de5f110994fac", size = 79565, upload-time = "2026-06-20T23:48:30.429Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e5/c120d13bf5091164f68c3c1657e84f16f57e71d978421b626393ac5bd7eb/wrapt-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ec8f83949028366531383603139403cac7a826e4011955813cdd640017845ce5", size = 83264, upload-time = "2026-06-20T23:48:31.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/d4a1eb97e0e286625bdf21bc7f702637f9607787ffbbdb5ec14d50c79dbf/wrapt-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b481fb0c40d9fd90a5809911208da700987d373a20a4709dc9e3944af7a6bec", size = 83791, upload-time = "2026-06-20T23:48:33.482Z" }, + { url = "https://files.pythonhosted.org/packages/18/1e/f060df47755e87b57684cee7bfc1362b204df55fac96ffebc0631b697b79/wrapt-2.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0065a3b657cec06813b4241d2462ccec287f6863103d7445b725fb3a889736f9", size = 203399, upload-time = "2026-06-20T23:48:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/c4/de/2316a757a1abb6453700b79d83e532146dcef2611348282d4d8889792161/wrapt-2.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30f7424af5c5c345b7f26490e097f74a2ef45b3d08b664dc33571aee3bd3b56c", size = 210461, upload-time = "2026-06-20T23:48:36.569Z" }, + { url = "https://files.pythonhosted.org/packages/ed/29/d1160785ae18ca2495a6d82a21154103d74f656c9fd457fb35f6b11b965a/wrapt-2.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07fdcb012821859168641acf68afad61ef9783cf37100af85f152550e9677194", size = 195313, upload-time = "2026-06-20T23:48:38.175Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2d/7caa9598ae61a9cf0989cc501739cbeeb7d650ab3193cca1407b9af0c6ab/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f90038ab58fafb584801ca62d72384d7d5225d93c76f7b773c22fae545bd8066", size = 206116, upload-time = "2026-06-20T23:48:39.804Z" }, + { url = "https://files.pythonhosted.org/packages/ac/02/281ea1088b8650d865f311b35cf86fd21df89128e2909714f1161e01c9d0/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c5d7825491bfa2d08b97e9557768987952c7b9ae687d06c3320b40a37ccb7f20", size = 192668, upload-time = "2026-06-20T23:48:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/be/7d/976e2d5b4b5c5babda40974edd54d0a5585cb60132ed86b46f4b80239b16/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ad520e6daa9bbf136f14de735474dbec7dcc0891f718e1d274ce8dc92e645af", size = 198891, upload-time = "2026-06-20T23:48:43.056Z" }, + { url = "https://files.pythonhosted.org/packages/59/b7/e47651797c097f75a37e2ce86dcf04048ff576f3a674f7c558df7b5e9622/wrapt-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:25904acb9475f46c24fe0423dbc8fda8cc5fbc282ab3dc6e72e919748c53f4e9", size = 78537, upload-time = "2026-06-20T23:48:44.509Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6f/9fa5d59fb06d890defb5a8f727ce6a14d2932c8760153f96956628559fee/wrapt-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:305d4c247d61c4115794a169141823c62f719525ddb90b23aa332741c77d2c28", size = 82005, upload-time = "2026-06-20T23:48:46.391Z" }, + { url = "https://files.pythonhosted.org/packages/15/80/4c7bd9873d1f9f7d138d93556b500469dbe24f42710b877519c2b9eb380d/wrapt-2.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c20279cd1a29800815d7b2d6338b60a6c6e78263f9d6e62e0eda251ba9cae2d0", size = 80762, upload-time = "2026-06-20T23:48:47.964Z" }, + { url = "https://files.pythonhosted.org/packages/24/05/7fd9c3f83b2c74cbfc572a0b88aa37431e04bd8aed70d2c0efd3464206de/wrapt-2.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0e64826f920c42d9d9f87e8cc09ffae66c51ede12d59061a5a426deb9aa71745", size = 81341, upload-time = "2026-06-20T23:48:49.39Z" }, + { url = "https://files.pythonhosted.org/packages/4b/68/1bfa43100dd90d4ef74a05897b86275cf57e1313ca14aae2545bc9f872c9/wrapt-2.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcaa5e1451bd8751d7bd1568dfa3321c78092a52a7ecb5d1a0f18a5791e1fd00", size = 81921, upload-time = "2026-06-20T23:48:50.986Z" }, + { url = "https://files.pythonhosted.org/packages/74/eb/df7b7f0b631dbbc750f39be27d8b55f65777d8ac86da80e12be41a644c4b/wrapt-2.2.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0abfd648dac9ac9c5b3aa9b523d27f1789046640b58dcd5652a720ddb325e1fc", size = 167713, upload-time = "2026-06-20T23:48:52.598Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9a/d1bd36f6d088c8e652a9383cabbd49af30b8c576302a7eccddbab6963e3f/wrapt-2.2.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4bfd8d1eb438153eff8b8cfe87f032ba65731e1ce06138b5090f745a33f6f95", size = 166779, upload-time = "2026-06-20T23:48:54.33Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ae/24ffacd4187fac2740a1972093929e836dea092d42c87d728cd98fee11a6/wrapt-2.2.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c427c9d06d859848a69f0d928fe28b5c33a941b2265d10a0e1f15cd244f1ee33", size = 158407, upload-time = "2026-06-20T23:48:55.944Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ed/974427668249a356051e8d67d47fa54ef6c777f0fcf3bae9d292c047d4b6/wrapt-2.2.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4250b43d1a129d947e083c4dc6baf333c9bb34edd26f912d5b0457841fc858ab", size = 166594, upload-time = "2026-06-20T23:48:57.617Z" }, + { url = "https://files.pythonhosted.org/packages/fb/5f/e1d7c6e4523f78db2fbd7826babd0348da1d5e0834c4f918b9ab5757dfae/wrapt-2.2.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:173e5bb5ca350a6e0abab60b7ec7cdd7992a814cb14b4de670a28f067f105663", size = 157068, upload-time = "2026-06-20T23:48:59.171Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c1/7ebd1027f00700c0b0233b20aceef2b4784294ed64971424c4a78e069e34/wrapt-2.2.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa14b01804bce36c6d63d7b6a4f55df390f29f8648cc13a1f40b166f4d54680d", size = 166470, upload-time = "2026-06-20T23:49:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/974e471a6a978b8180186b8a9dc5ae3361ce269a967190b709b8ce17abfb/wrapt-2.2.2-cp314-cp314-win32.whl", hash = "sha256:58f9f8d637c9a6e245c6ef5b109b67ec187d2faed23d1405656b51d96e0a5b56", size = 78062, upload-time = "2026-06-20T23:49:02.327Z" }, + { url = "https://files.pythonhosted.org/packages/49/ec/e1281156cdc7a66693838ad7a0865ad641c74abd337a957d668b575aaffb/wrapt-2.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:385cb1866f20479e83299af585375bfa0a4b0c6c9907a981483ea782ea8ae406", size = 80832, upload-time = "2026-06-20T23:49:03.837Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/1b6b5ddd94005a2dac97a4490c9838f3154977850d633abcb65b30089437/wrapt-2.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:8ffbeaea6771a6eba6e6eeb09767864995726bc8240bb54baf88a9bb1db34d5c", size = 80029, upload-time = "2026-06-20T23:49:05.237Z" }, + { url = "https://files.pythonhosted.org/packages/b0/33/9ebcf8aafe91c601127cbd93708c16aa8f688f34a10bf004046803ecdc4f/wrapt-2.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:09f811d43f6f33ec7515f0be76b159569f4057ab54d3e079c3204dddb90afa2a", size = 83357, upload-time = "2026-06-20T23:49:06.632Z" }, + { url = "https://files.pythonhosted.org/packages/39/38/ec45b635153327b52e52732a0ea980e5f00b7efba65f9e018828f1e69daa/wrapt-2.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a795d3c06e5fbf9ea2f13196180b77aeab1b4685917256ee0d014cc163d90063", size = 83794, upload-time = "2026-06-20T23:49:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ea/1a89e6d3b7a83c3affe5c09cde77792c947e63e4bc85ad84cd5bb9abb0d8/wrapt-2.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:45c2f2768e790c9f8db90f239ef23a2af8e7570f25a35619ef902df4a738447f", size = 203362, upload-time = "2026-06-20T23:49:09.811Z" }, + { url = "https://files.pythonhosted.org/packages/19/d8/3b58763d9863b5a73771c0d97110f9595d248db454009e07e1535ee905a4/wrapt-2.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbf00ee0cb55ec24e2b0995a71942b85b21a066db8f3f46e1dbfdb9433ffba81", size = 210449, upload-time = "2026-06-20T23:49:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/2d/6f/17fd9e053103d8be148d20d5d7505facc72d5fe1f9127973904ceaed79cf/wrapt-2.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2252f77663651b89255895f58cc6ac08fcb206d4371813e5af61bb62d4f7689c", size = 195349, upload-time = "2026-06-20T23:49:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/d0d1ccaaa12cb7dccf28a23f0279a608ba498f71e81d949d5ed54bcfd5c1/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cd7181ab1c31192ff5219269830744b5a62020b3a6d433588c4f1c95b8f8bff", size = 206099, upload-time = "2026-06-20T23:49:15.051Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/e8aa07b619890a2aa6cde1931b1887abb08820721b564a5f80b7ca3f3aa0/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6fe35fd51b74867d8b80174c277bd6bbf6a73e443f908129dc531c4b688a20d5", size = 192728, upload-time = "2026-06-20T23:49:16.854Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f0/1819fb50f0d3c9bd758d8a83b56f1b470dee8b5b8eac8702b7c137cea9d4/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11d95fc2fbad3163596c39d440e6f21ca9fccece74b56e30a37ac2fca786a07c", size = 198842, upload-time = "2026-06-20T23:49:18.504Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/e88313f16a99930b899ef970d91c281544a470749a359decad994483bbda/wrapt-2.2.2-cp314-cp314t-win32.whl", hash = "sha256:d8a15813215f33fa83667bfc978b300e35669ea8bb424e970a1426bcb7bc6cca", size = 79059, upload-time = "2026-06-20T23:49:20.107Z" }, + { url = "https://files.pythonhosted.org/packages/a0/4f/ac12fda57a55068a094ec42851fb0a40e8489d8941863d517452de62e507/wrapt-2.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d09db0f7e8357060d3c38fc22a018aba683a796bf184360fd1a58f6fc180dc77", size = 82462, upload-time = "2026-06-20T23:49:21.631Z" }, + { url = "https://files.pythonhosted.org/packages/48/a7/df732dac86d9b2027c56bd163dbc883e037b16c3469614752e148d219c61/wrapt-2.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f32fe639c39561ccc187bcae17e9271be0eb45f1c2952510d2f29b33ab577347", size = 81182, upload-time = "2026-06-20T23:49:23.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d2/6317eb6d4554855bbf12d61857774af34747bf88a42c19bf306de67e2fa3/wrapt-2.2.2-py3-none-any.whl", hash = "sha256:5bad217350f19ce99ca5b5e71d406765ea86fe541628426772b657375ee1c048", size = 61460, upload-time = "2026-06-20T23:49:42.966Z" }, +] + +[[package]] +name = "wsproto" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/df/f1c7a3de0831cd83194f1a85c5bb431b13f81e6b45079314c86d1c4ef3f2/yarl-1.24.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5249a113065c2b7a958bc699759e359cd61cfc81e3069662208f48f191b7ed12", size = 129057, upload-time = "2026-05-19T21:27:47.564Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/7daafb32dd7562bf45b1ce56562e7e1a9146f6479b6456873eb8a3413c40/yarl-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4425fa244fbf530b006d0c5f79ce920114cfff5b4f5f6056e669f8e160fdc0", size = 91545, upload-time = "2026-05-19T21:27:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/a8/8f/7b3ec212f1ea0683f55f978e3246bc313c38818664edfc97a9f349a4901e/yarl-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15c0b5e49d3c44e2a0b93e6a49476c5edad0a7686b92c395765a7ea775572a75", size = 91380, upload-time = "2026-05-19T21:27:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1b/8bafab7db23b0567ae9db749099b329d91e3b82bc6028b2050ba583e116c/yarl-1.24.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:246d32a53a947c8f0189f5d699cbd4c7036de45d9359e13ba238d1239678c727", size = 105957, upload-time = "2026-05-19T21:27:53.98Z" }, + { url = "https://files.pythonhosted.org/packages/7f/77/21030c2f8d21d21559719beafc772ada2014be933418ed1eaed9cc800e42/yarl-1.24.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:64480fb3e4d4ed9ed71c48a91a477384fc342a50ca30071d2f8a88d51d9c9413", size = 97242, upload-time = "2026-05-19T21:27:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/f9ea63d1b6aa910a866e089d871fff6cbd49caab29b86b35221a62dfa0d5/yarl-1.24.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:349de4701dc3760b6e876628423a8f147ef4f5599d10aba1e10702075d424ed9", size = 114719, upload-time = "2026-05-19T21:27:58.037Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/04e0ee98ac58a249ea7ed75223f5f901ba81a834f0b4921b58e5cec11757/yarl-1.24.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d162677af8d5d3d6ebab8394b021f4d041ac107a4b705873148a77a49dc9e1b2", size = 112140, upload-time = "2026-05-19T21:27:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/ad/0b9cc9f38a7324a7eb1d80f834eaa5283d17e9271bbda3186e598dddaeac/yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5f5c6ec23a9043f2d139cc072f53dd23168d202a334b9b2fda8de4c3e890d90", size = 106721, upload-time = "2026-05-19T21:28:02.586Z" }, + { url = "https://files.pythonhosted.org/packages/65/e7/a52478ebfc66ec989e085c6ae038b9f1bfa4190baa193b133b669c709e2f/yarl-1.24.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:60de6742447fbbf697f16f070b8a443f1b5fe6ca3826fbef9fe70ecd5328e643", size = 106478, upload-time = "2026-05-19T21:28:04.523Z" }, + { url = "https://files.pythonhosted.org/packages/04/d8/5508530fea8472542de00013ae280765fc938ee196fc4030c43a498afb36/yarl-1.24.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acf93187c3710e422368eb768aee98db551ec7c85adc250207a95c16548ab7ac", size = 105423, upload-time = "2026-05-19T21:28:06.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/f1/ece28505e9628e8b756e11bb4f28864a17cc33b6b44db4d2aaf0622bf630/yarl-1.24.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f4b0352fd41fd34b6651934606268816afd6914d09626f9bcbbf018edb0afb3f", size = 99878, upload-time = "2026-05-19T21:28:08.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/fb5d34529b46dd84013afcfb30b8d2bc2832ed03d412736f577d604fa393/yarl-1.24.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6b208bb939099b4b297438da4e9b25357f0b1c791888669b963e45b203ea9f36", size = 114025, upload-time = "2026-05-19T21:28:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/ff9d31aaab024f7a251c0ed308a98ae29bf9f7dc344e78f28b1322431ca2/yarl-1.24.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4b85b8825e631295ff4bc8943f7471d54c533a9360bbe15ebb38e018b555bb8a", size = 105613, upload-time = "2026-05-19T21:28:12.784Z" }, + { url = "https://files.pythonhosted.org/packages/31/7d/3296fb3f3ecd52bf9ae6c16b0895c1cda7e9170a2083861552b683f70264/yarl-1.24.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e26acf20c26cb4fefc631fdb75aca2a6b8fa8b7b5d7f204fb6a8f1e63c706f53", size = 111665, upload-time = "2026-05-19T21:28:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/77aa6ddaca4fbf42e45e675a465c43956dd40702281049975a2aa04eae59/yarl-1.24.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:819ca24f8eafcfb683c1bd5f44f2f488cea1274eb8944731ffd2e1f10f619342", size = 106914, upload-time = "2026-05-19T21:28:15.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/02/7611f22cd1d4ed7373eb7f9ee21fde1046edba2e7c0e514880d760352f48/yarl-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:5cb0f995a901c36be096ccbf4c673591c2faabbe96279598ffaec8c030f85bf4", size = 92658, upload-time = "2026-05-19T21:28:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/91/00/671d0add79938127292839ae44506ce2f7fe8909c72d5a931864f128fd0b/yarl-1.24.2-cp310-cp310-win_arm64.whl", hash = "sha256:f408eace7e22a68b467a0562e0d27d322f91fe3eaaa6f466b962c6cfaea9fa39", size = 87887, upload-time = "2026-05-19T21:28:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" }, + { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" }, + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/28efd1d371f1acd037ac64ed1c5e2b41514a6cc937dd6ab6a13ab9f0702f/zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd", size = 795256, upload-time = "2025-09-14T22:15:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/96/34/ef34ef77f1ee38fc8e4f9775217a613b452916e633c4f1d98f31db52c4a5/zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7", size = 640565, upload-time = "2025-09-14T22:15:58.177Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1b/4fdb2c12eb58f31f28c4d28e8dc36611dd7205df8452e63f52fb6261d13e/zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550", size = 5345306, upload-time = "2025-09-14T22:16:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/73/28/a44bdece01bca027b079f0e00be3b6bd89a4df180071da59a3dd7381665b/zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d", size = 5055561, upload-time = "2025-09-14T22:16:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/e9/74/68341185a4f32b274e0fc3410d5ad0750497e1acc20bd0f5b5f64ce17785/zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b", size = 5402214, upload-time = "2025-09-14T22:16:04.109Z" }, + { url = "https://files.pythonhosted.org/packages/8b/67/f92e64e748fd6aaffe01e2b75a083c0c4fd27abe1c8747fee4555fcee7dd/zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0", size = 5449703, upload-time = "2025-09-14T22:16:06.312Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e5/6d36f92a197c3c17729a2125e29c169f460538a7d939a27eaaa6dcfcba8e/zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0", size = 5556583, upload-time = "2025-09-14T22:16:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/41939e60d8d7ebfe2b747be022d0806953799140a702b90ffe214d557638/zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd", size = 5045332, upload-time = "2025-09-14T22:16:10.444Z" }, + { url = "https://files.pythonhosted.org/packages/b3/87/d3ee185e3d1aa0133399893697ae91f221fda79deb61adbe998a7235c43f/zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701", size = 5572283, upload-time = "2025-09-14T22:16:12.128Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1d/58635ae6104df96671076ac7d4ae7816838ce7debd94aecf83e30b7121b0/zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1", size = 4959754, upload-time = "2025-09-14T22:16:14.225Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/57e9cb0a9983e9a229dd8fd2e6e96593ef2aa82a3907188436f22b111ccd/zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150", size = 5266477, upload-time = "2025-09-14T22:16:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a9/ee891e5edf33a6ebce0a028726f0bbd8567effe20fe3d5808c42323e8542/zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab", size = 5440914, upload-time = "2025-09-14T22:16:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/58/08/a8522c28c08031a9521f27abc6f78dbdee7312a7463dd2cfc658b813323b/zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e", size = 5819847, upload-time = "2025-09-14T22:16:20.559Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/4c91411805c3f7b6f31c60e78ce347ca48f6f16d552fc659af6ec3b73202/zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74", size = 5363131, upload-time = "2025-09-14T22:16:22.206Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d6/8c4bd38a3b24c4c7676a7a3d8de85d6ee7a983602a734b9f9cdefb04a5d6/zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa", size = 436469, upload-time = "2025-09-14T22:16:25.002Z" }, + { url = "https://files.pythonhosted.org/packages/93/90/96d50ad417a8ace5f841b3228e93d1bb13e6ad356737f42e2dde30d8bd68/zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e", size = 506100, upload-time = "2025-09-14T22:16:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, + { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, + { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, + { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, + { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +]