diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..2ffd3de8ce --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,88 @@ +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. +name: Tests + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + schedule: + - cron: '53 0 * * *' # Daily at 00:53 UTC + # Triggered on push to branch "main" by .github/workflows/release.yaml + workflow_call: + +jobs: + lint: + name: Lint + uses: canonical/data-platform-workflows/.github/workflows/lint.yaml@v11.1.2 + + unit-test: + name: Unit test charm + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install tox & poetry + run: | + pipx install tox + pipx install poetry + - name: Run tests + run: tox run -e unit + + + lib-check: + name: Check libraries + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - run: | + # Workaround for https://github.com/canonical/charmcraft/issues/1389#issuecomment-1880921728 + touch requirements.txt + - name: Check libs + uses: canonical/charming-actions/check-libraries@2.4.0 + with: + credentials: ${{ secrets.CHARMHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + use-labels: false + fail-build: ${{ github.event_name == 'pull_request' }} + + build: + name: Build charm + strategy: + fail-fast: false + matrix: + path: + - . + - ./tests/integration/relations/opensearch_provider/application-charm/ + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v11.1.2 + with: + path-to-charm-directory: ${{ matrix.path }} + cache: true + + integration-test: + name: Integration test charm + needs: + - lint + - unit-test + - build + uses: canonical/data-platform-workflows/.github/workflows/integration_test_charm.yaml@v11.1.2 + with: + artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} + cloud: lxd + juju-snap-channel: 3.3/stable + secrets: + integration-test: | + { + "AWS_ACCESS_KEY": "${{ secrets.AWS_ACCESS_KEY }}", + "AWS_SECRET_KEY": "${{ secrets.AWS_SECRET_KEY }}", + "GCP_ACCESS_KEY": "${{ secrets.GCP_ACCESS_KEY }}", + "GCP_SECRET_KEY": "${{ secrets.GCP_SECRET_KEY }}", + "GCP_SERVICE_ACCOUNT": "${{ secrets.GCP_SERVICE_ACCOUNT }}", + } diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml deleted file mode 100644 index c29be9e4d7..0000000000 --- a/.github/workflows/integration.yaml +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2023 Canonical Ltd. -# See LICENSE file for licensing details. -on: - workflow_call: - inputs: - juju-snap-channel: - description: Snap channel for Juju CLI - default: 3.1/stable - type: string - libjuju-version-specifier: - description: | - python-libjuju version specifier (e.g. ">=1.3") - https://packaging.python.org/en/latest/glossary/#term-Version-Specifier - required: false - type: string -# secrets: -# integration-test: -# description: | -# Secrets needed in integration tests -# -# Passed to tests with `SECRETS_FROM_GITHUB` environment variable -# -# Use a string representation of a Python dict[str, str] built from multiple GitHub secrets -# Do NOT put the string into a single GitHub secret—build the string from multiple GitHub secrets so that GitHub is more likely to redact the secrets in GitHub Actions logs. -# -# Python code to verify the string format: -# ``` -# import ast -# secrets = ast.literal_eval("") -# assert isinstance(secrets, dict) -# for key, value in secrets.items(): -# assert isinstance(key, str) and isinstance(value, str) -# ``` -# required: false - -jobs: - build: - name: Build charms - uses: canonical/data-platform-workflows/.github/workflows/build_charms_with_cache.yaml@v2 - - integration-test: - needs: - - build - strategy: - fail-fast: false - matrix: - tox-environments: - - charm-integration - - tls-integration - - client-integration - - ha-base-integration - - ha-networking-integration - - ha-multi-clusters-integration - - large-deployments-integration - - plugins-integration -# - ha-backup-integration - runner: ["ubuntu-22.04"] - include: - - tox-environments: h-scaling-integration - runner: "Ubuntu_4C_16G" - name: ${{ matrix.tox-environments }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 360 - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Upgrade LXD - run: | - sudo snap refresh lxd --channel=latest/stable - - - name: Setup Juju environment - # Runs on juju 3 by default - # TODO: Replace with custom image on self-hosted runner - uses: charmed-kubernetes/actions-operator@main - with: - provider: lxd - juju-channel: ${{ inputs.juju-snap-channel }} - - - name: Download packed charm(s) - uses: actions/download-artifact@v3 - with: - name: ${{ needs.build.outputs.artifact-name }} - -# - name: Install CLI -# run: | -# sudo apt update -# sudo apt install -y pipx -# pipx install git+https://github.com/canonical/data-platform-workflows#subdirectory=python/cli -# - name: Redact secrets from log -# run: redact-secrets -# env: -# SECRETS: ${{ secrets.integration-test }} - - - name: Select tests - id: select-tests - run: | - if [ "${{ github.event_name }}" == "schedule" ] - then - echo Running unstable and stable tests - echo "mark_expression=" >> $GITHUB_OUTPUT - else - echo Skipping unstable tests - echo "mark_expression=not unstable" >> $GITHUB_OUTPUT - fi - - - name: Run integration tests - run: | - # free space in the runner - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/share/boost - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - # Set kernel params for OpenSearch - sudo sysctl -w vm.max_map_count=262144 vm.swappiness=0 net.ipv4.tcp_retries2=5 - # Set kernel params for testing - sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.default.disable_ipv6=1 net.ipv6.conf.all.autoconf=0 - tox run -e ${{ matrix.tox-environments }} -- -m '${{ steps.select-tests.outputs.mark_expression }}' - env: - CI_PACKED_CHARMS: ${{ needs.build.outputs.charms }} - LIBJUJU_VERSION_SPECIFIER: ${{ inputs.libjuju-version-specifier }} - SECRETS_FROM_GITHUB: "" -# SECRETS_FROM_GITHUB: ${{ secrets.integration-test }} - - - - backup-microceph-integration-test: - needs: - - build - name: backup-microceph-integration-test - runs-on: "ubuntu-22.04" - timeout-minutes: 360 - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Upgrade LXD - run: | - sudo snap refresh lxd --channel=latest/stable - - - name: Setup Juju environment - # Runs on juju 3 by default - # TODO: Replace with custom image on self-hosted runner - uses: charmed-kubernetes/actions-operator@main - with: - provider: lxd - juju-channel: ${{ inputs.juju-snap-channel }} - - - name: Download packed charm(s) - uses: actions/download-artifact@v3 - with: - name: ${{ needs.build.outputs.artifact-name }} - - - name: Free space in the runner - id: free-space-runner - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/share/boost - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - - name: Select tests - id: select-tests - run: | - if [ "${{ github.event_name }}" == "schedule" ] - then - echo Running unstable and stable tests - echo "mark_expression=" >> $GITHUB_OUTPUT - else - echo Skipping unstable tests - echo "mark_expression=not unstable" >> $GITHUB_OUTPUT - fi - - - name: Run backup integration - run: | - sudo sysctl -w vm.max_map_count=262144 vm.swappiness=0 net.ipv4.tcp_retries2=5 - tox run -e ha-backup-integration -- -m '${{ steps.select-tests.outputs.mark_expression }}' - env: - CI_PACKED_CHARMS: ${{ needs.build.outputs.charms }} - LIBJUJU_VERSION_SPECIFIER: ${{ inputs.libjuju-version-specifier }} - -# - name: Run backup test with AWS -# run: tox run -e ha-backup-integration -- -m '${{ steps.select-tests.outputs.mark_expression }}' -# env: -# CI_PACKED_CHARMS: ${{ needs.build.outputs.charms }} -# LIBJUJU_VERSION_SPECIFIER: ${{ matrix.libjuju-version }} -# SECRETS_FROM_GITHUB: | -# { -# "AWS_ACCESS_KEY": "${{ secrets.AWS_ACCESS_KEY }}", -# "AWS_SECRET_KEY": "${{ secrets.AWS_SECRET_KEY }}", -# } -# - name: Run backup test with GCP -# run: tox run -e ha-backup-integration -- -m '${{ steps.select-tests.outputs.mark_expression }}' -# env: -# CI_PACKED_CHARMS: ${{ needs.build.outputs.charms }} -# LIBJUJU_VERSION_SPECIFIER: ${{ matrix.libjuju-version }} -# SECRETS_FROM_GITHUB: | -# { -# "GCP_ACCESS_KEY": "${{ secrets.GCP_ACCESS_KEY }}", -# "GCP_SECRET_KEY": "${{ secrets.GCP_SECRET_KEY }}", -# } diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml deleted file mode 100644 index 012b8db0d5..0000000000 --- a/.github/workflows/pr.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright 2023 Canonical Ltd. -# See LICENSE file for licensing details. -name: PR CI - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - pull_request: - -jobs: - pre-integration-checks: - secrets: inherit - uses: ./.github/workflows/pre_integration_checks.yaml - with: - libjuju-version-specifier: ==3.3.0.0 - - integration: - needs: - - pre-integration-checks - uses: ./.github/workflows/integration.yaml - with: - libjuju-version-specifier: ==3.3.0.0 diff --git a/.github/workflows/pre_integration_checks.yaml b/.github/workflows/pre_integration_checks.yaml deleted file mode 100644 index 373e2c3704..0000000000 --- a/.github/workflows/pre_integration_checks.yaml +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2023 Canonical Ltd. -# See LICENSE file for licensing details. -name: Pre-integration checks - -on: - workflow_call: - inputs: - libjuju-version-specifier: - description: | - python-libjuju version specifier (e.g. ">=1.3") - https://packaging.python.org/en/latest/glossary/#term-Version-Specifier - required: false - type: string - -jobs: - lint: - name: Lint - runs-on: ubuntu-22.04 - timeout-minutes: 5 - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install tox - # TODO: Consider replacing with custom image on self-hosted runner OR pinning version - run: python3 -m pip install tox - - name: Run linters - run: tox run -e lint - - unit-test: - name: Unit tests - runs-on: ubuntu-22.04 - timeout-minutes: 5 - needs: - - lint - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install tox - # TODO: Consider replacing with custom image on self-hosted runner OR pinning version - run: python3 -m pip install tox - - name: Run tests - run: tox run -e unit - env: - LIBJUJU_VERSION_SPECIFIER: ${{ inputs.libjuju-version-specifier }} - - lib-check: - name: Check libraries - runs-on: ubuntu-22.04 - timeout-minutes: 5 - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Check libs - uses: canonical/charming-actions/check-libraries@2.1.1 - with: - credentials: "${{ secrets.CHARMHUB_TOKEN }}" # FIXME: current token will expire in 2023-07-04 - github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9ca3bc83d6..c2dfc55fe0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,48 +8,41 @@ on: - main jobs: - pre-integration-checks: + ci-tests: + name: Tests + uses: ./.github/workflows/ci.yaml secrets: inherit - strategy: - fail-fast: false - matrix: - include: - - libjuju: ==3.3.0.0 - uses: ./.github/workflows/pre_integration_checks.yaml - with: - libjuju-version-specifier: ${{ matrix.libjuju }} - integration: - needs: - - pre-integration-checks - strategy: - fail-fast: false - matrix: - include: - - snap: 3.1/stable - libjuju: ==3.3.0.0 - uses: ./.github/workflows/integration.yaml - with: - juju-snap-channel: ${{ matrix.snap }} - libjuju-version-specifier: ${{ matrix.libjuju }} + # release-libraries: + # name: Release libraries + # needs: + # - ci-tests + # runs-on: ubuntu-latest + # timeout-minutes: 60 + # steps: + # - name: Checkout + # uses: actions/checkout@v3 + # with: + # fetch-depth: 0 + # - name: Release charm libraries + # uses: canonical/charming-actions/release-libraries@2.3.0 + # with: + # credentials: ${{ secrets.CHARMHUB_TOKEN }} + # github-token: ${{ secrets.GITHUB_TOKEN }} + build: + name: Build charm + uses: canonical/data-platform-workflows/.github/workflows/build_charm.yaml@v11.1.2 - release-to-charmhub: - name: Release to CharmHub + release: + name: Release charm needs: - - pre-integration-checks - - integration - runs-on: ubuntu-22.04 - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Select charmhub channel - uses: canonical/charming-actions/channel@2.1.1 - id: channel - - name: Upload charm to charmhub - uses: canonical/charming-actions/upload-charm@2.1.1 - with: - credentials: "${{ secrets.CHARMHUB_TOKEN }}" - github-token: "${{ secrets.GITHUB_TOKEN }}" - channel: "2/edge" + - build + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v11.1.2 + with: + channel: latest/edge + artifact-prefix: ${{ needs.build.outputs.artifact-prefix }} + secrets: + charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} + permissions: + contents: write # Needed to create GitHub release diff --git a/.github/workflows/scheduled_tests.yaml b/.github/workflows/scheduled_tests.yaml deleted file mode 100644 index 10507a6b31..0000000000 --- a/.github/workflows/scheduled_tests.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright 2023 Canonical Ltd. -# See LICENSE file for licensing details. -name: Scheduled CI - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - schedule: - - cron: '53 0 * * *' # Daily at 00:53 UTC - -jobs: - pre-integration-checks: - secrets: inherit - uses: ./.github/workflows/pre_integration_checks.yaml - - integration: - needs: - - pre-integration-checks - strategy: - fail-fast: false - matrix: - include: - - snap: 3.1/stable - libjuju: ==3.3.0.0 - - snap: 2.9/stable - libjuju: ==2.9.44.0 - uses: ./.github/workflows/integration.yaml - with: - juju-snap-channel: ${{ matrix.snap }} - libjuju-version-specifier: ${{ matrix.libjuju }} diff --git a/.gitignore b/.gitignore index f1b819d2b1..51ccad860b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ __pycache__/ *.tar.xz cloudinit-userdata.yaml /.pytest_cache/ + +# Moving to Poetry, we do not need this file to be pushed any longer +/requirements.txt +/requirements-last-build.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 72e5cde063..85a7b39ebb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,6 +21,33 @@ this operator. - Please help us out in ensuring easy to review branches by rebasing your pull request branch onto the `main` branch. This also avoids merge commits and creates a linear Git commit history. + +## Build charm + +Build the charm in this git repository using tox. + +There are two alternatives to build the charm: using the charm cache or not. +Cache will speed the build by downloading all dependencies from charmcraftcache-hub. + +First, ensure you have the right dependencies: +* charmcraft v2.5.4+ +* charmcraftcache + +By running the following commands: + +```shell +pipx install charmcraftcache +tox -e build-dev +``` + +### Build Without Cache + +To run the traditional build only using `charmcraft`, run the following command: + +```shell +tox -e build-production +``` + ## Developing You can create an environment for development with `tox`: @@ -32,41 +59,66 @@ source venv/bin/activate ### Testing -To run tests, run the following +To run tests, first build the charm as described above, then run the following ```shell tox -e format # update your code according to linting rules tox -e lint # code style tox -e unit # unit tests -tox -m integration # integration tests, running on juju 2. +tox -e integration # integration tests, running on juju 3. tox # runs 'format', 'lint', and 'unit' environments ``` -Integration tests can be run for separate areas of functionality: +Integration tests can be run for separate files: ```shell -tox -e charm-integration # basic charm integration tests -tox -e tls-integration # TLS-specific integration tests -tox -e client-integration # Validates the `opensearch-client` integration -tox -e ha-integration # HA tests -tox -e h-scaling-integration # HA tests specific to horizontal scaling +tox -e integration -- tests/integration/tls/test_tls.py +tox -e integration -- tests/integration/relations/test_charm.py +tox -e integration -- tests/integration/plugins/test_plugins.py +tox -e integration -- tests/integration/ha/test_storage.py +tox -e integration -- tests/integration/ha/test_large_deployments.py +tox -e integration -- tests/integration/ha/test_horizontal_scaling.py +tox -e integration -- tests/integration/ha/test_ha_networking.py +tox -e integration -- tests/integration/ha/test_ha_multi_clusters.py +tox -e integration -- tests/integration/relations/test_opensearch_provider.py +tox -e integration -- tests/integration/ha/test_ha.py +tox -e integration -- tests/integration/ha/test_backups.py ``` -If you're running tests on juju 3, run the following command to change libjuju to the correct version: +#### Running different major versions of Juju + +For integration tests, libjuju must be in-sync with the target juju version. +Make sure that the version of libjuju installed is compatible with the bootstrapped +controller version. If not, update it with: ```shell -export LIBJUJU_VERSION_SPECIFIER="==3.3.0.0" +poetry add --lock --group integration juju@ ``` -## Build charm +#### FOR DEVELOPMENT ONLY: Testing Backups In Your Local Machine + +Backup testing installs microceph and can run on S3 (aws) object stores. +To setup your environment, you should set the: access / secret / service account information as environment variables. + +To run the test only against microceph: -Build the charm in this git repository using: +```shell +tox -e integration -- tests/integration/ha/test_backups.py --group='microceph' # test backup service for microceph +``` + +And against public clouds + microceph: + +```shell +SECRETS_FROM_GITHUB=$(cat /credentials.json) tox -e integration -- tests/integration/ha/test_backups.py +``` +Where, for AWS only, `credentials.json` should look like: ```shell -charmcraft pack +$ cat credentials.json +{ "AWS_ACCESS_KEY": ..., "AWS_SECRET_KEY": ...} ``` -### Deploy +## Deploy OpenSearch has a set of system requirements to correctly function, you can find the list [here](https://opensearch.org/docs/latest/install-and-configure/install-opensearch/index/). Some of those settings must be set using cloudinit-userdata on the model, while others must be set on the host machine: diff --git a/charmcraft.yaml b/charmcraft.yaml index bd045e1edd..22fd6bbf55 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -9,9 +9,17 @@ bases: run-on: - name: "ubuntu" channel: "22.04" - parts: charm: + override-pull: | + craftctl default + if [[ ! -f requirements.txt ]] + then + echo 'ERROR: Use "tox run -e build-dev" instead of calling "charmcraft pack" directly' >&2 + exit 1 + fi + charm-strict-dependencies: true + charm-entrypoint: src/charm.py build-packages: - cargo - libffi-dev diff --git a/lib/charms/opensearch/v0/helper_cluster.py b/lib/charms/opensearch/v0/helper_cluster.py index 1f59ad92e8..82c5fc116e 100644 --- a/lib/charms/opensearch/v0/helper_cluster.py +++ b/lib/charms/opensearch/v0/helper_cluster.py @@ -352,6 +352,14 @@ def health( """Fetch the cluster health.""" endpoint = "/_cluster/health" + # Extra logging: list shards and index status + logger.debug( + "indices status:\n" + f"{opensearch.request('GET', '/_cat/indices?v')}\n" + "indices shards:\n" + f"{opensearch.request('GET', '/_cat/shards?v')}\n" + ) + timeout = 5 if wait_for_green: endpoint = f"{endpoint}?wait_for_status=green&timeout=1m" diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000000..56e6a8a288 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2281 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "attrs" +version = "23.2.0" +description = "Classes Without Boilerplate" +optional = false +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 = "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 = "black" +version = "23.12.1" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"}, + {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"}, + {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"}, + {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"}, + {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"}, + {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"}, + {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"}, + {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"}, + {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"}, + {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"}, + {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"}, + {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"}, + {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"}, + {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"}, + {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"}, + {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"}, + {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"}, + {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"}, + {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"}, + {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"}, + {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"}, + {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "boto3" +version = "1.34.49" +description = "The AWS SDK for Python" +optional = false +python-versions = ">= 3.8" +files = [ + {file = "boto3-1.34.49-py3-none-any.whl", hash = "sha256:ce8d1de03024f52a1810e8d71ad4dba3a5b9bb48b35567191500e3432a9130b4"}, + {file = "boto3-1.34.49.tar.gz", hash = "sha256:96b9dc85ce8d52619b56ca7b1ac1423eaf0af5ce132904bcc8aa81396eec2abf"}, +] + +[package.dependencies] +botocore = ">=1.34.49,<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.49" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">= 3.8" +files = [ + {file = "botocore-1.34.49-py3-none-any.whl", hash = "sha256:4ed9d7603a04b5bb5bd5de63b513bc2c8a7e8b1cd0088229c5ceb461161f43b6"}, + {file = "botocore-1.34.49.tar.gz", hash = "sha256:d89410bc60673eaff1699f3f1fdcb0e3a5e1f7a6a048c0d88c3ce5c3549433ec"}, +] + +[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\""} + +[package.extras] +crt = ["awscrt (==0.19.19)"] + +[[package]] +name = "cachetools" +version = "5.3.2" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.2-py3-none-any.whl", hash = "sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"}, + {file = "cachetools-5.3.2.tar.gz", hash = "sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2"}, +] + +[[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 = "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 = "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 = "codespell" +version = "2.2.6" +description = "Codespell" +optional = false +python-versions = ">=3.8" +files = [ + {file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"}, + {file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"}, +] + +[package.extras] +dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"] +hard-encoding-detection = ["chardet"] +toml = ["tomli"] +types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"] + +[[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 = "cosl" +version = "0.0.8" +description = "Utils for COS Lite charms" +optional = false +python-versions = ">=3.8" +files = [ + {file = "cosl-0.0.8-py3-none-any.whl", hash = "sha256:71e4e73abba9029553d11a5c55dd2bae0251fb094556e0757fe1c49f74ad18ac"}, + {file = "cosl-0.0.8.tar.gz", hash = "sha256:b41f795a507d55d12f4c0cc68565543badcbbef96afe4ae8553999efc423d834"}, +] + +[package.dependencies] +ops = "*" +pyyaml = "*" +typing-extensions = "*" + +[[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 = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[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 = "executing" +version = "2.0.1" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.5" +files = [ + {file = "executing-2.0.1-py2.py3-none-any.whl", hash = "sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc"}, + {file = "executing-2.0.1.tar.gz", hash = "sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "flake8-builtins" +version = "2.2.0" +description = "Check for python builtins being used as variables or parameters" +optional = false +python-versions = ">=3.8" +files = [ + {file = "flake8_builtins-2.2.0-py3-none-any.whl", hash = "sha256:7ee5766d9c60e5d579dfda84e65c6d0e6c26005f6f59cb9bf722462d7987a807"}, + {file = "flake8_builtins-2.2.0.tar.gz", hash = "sha256:392d5af3a0720c5a863aa93dc47f48c879081345a143fe9f20d995fe9ff5686a"}, +] + +[package.dependencies] +flake8 = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "flake8-copyright" +version = "0.2.4" +description = "Adds copyright checks to flake8" +optional = false +python-versions = "*" +files = [ + {file = "flake8-copyright-0.2.4.tar.gz", hash = "sha256:b78491fcf575266d7e78dcfa899c876edd1c29929d247de3408bf4e3f971bf1c"}, + {file = "flake8_copyright-0.2.4-py3-none-any.whl", hash = "sha256:5d33d900c4183bb6748692407867229d1e5b84016a100e8899a7f58dcf52223f"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "flake8-docstrings" +version = "1.7.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, + {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, +] + +[package.dependencies] +flake8 = ">=3" +pydocstyle = ">=2.1" + +[[package]] +name = "google-auth" +version = "2.28.1" +description = "Google Authentication Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-auth-2.28.1.tar.gz", hash = "sha256:34fc3046c257cedcf1622fc4b31fc2be7923d9b4d44973d481125ecc50d83885"}, + {file = "google_auth-2.28.1-py2.py3-none-any.whl", hash = "sha256:25141e2d7a14bfcba945f5e9827f98092716e99482562f15306e5b026e21aa72"}, +] + +[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 = "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 = "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 = "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 = "ipdb" +version = "0.13.13" +description = "IPython-enabled pdb" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"}, + {file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"}, +] + +[package.dependencies] +decorator = {version = "*", markers = "python_version > \"3.6\""} +ipython = {version = ">=7.31.1", markers = "python_version > \"3.6\""} +tomli = {version = "*", markers = "python_version > \"3.6\" and python_version < \"3.11\""} + +[[package]] +name = "ipython" +version = "8.22.1" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.22.1-py3-none-any.whl", hash = "sha256:869335e8cded62ffb6fac8928e5287a05433d6462e3ebaac25f4216474dd6bc4"}, + {file = "ipython-8.22.1.tar.gz", hash = "sha256:39c6f9efc079fb19bfb0f17eee903978fe9a290b1b82d68196c641cecb76ea22"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" + +[package.extras] +all = ["ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,terminal]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "stack-data", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pickleshare", "pytest (<8)", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[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 = false +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 = "jproperties" +version = "2.1.1" +description = "Java Property file parser and writer for Python" +optional = false +python-versions = "*" +files = [ + {file = "jproperties-2.1.1-py2.py3-none-any.whl", hash = "sha256:4dfcd7cab56d9c79bce4453f7ca9ffbe0ff0574ddcf1c2a99a8646df60634664"}, + {file = "jproperties-2.1.1.tar.gz", hash = "sha256:40b71124e8d257e8954899a91cd2d5c0f72e0f67f1b72048a5ba264567604f29"}, +] + +[package.dependencies] +six = ">=1.13,<2.0" + +[[package]] +name = "jsonschema" +version = "4.21.1" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.21.1-py3-none-any.whl", hash = "sha256:7996507afae316306f9e2290407761157c6f78002dcf7419acb99822143d1c6f"}, + {file = "jsonschema-4.21.1.tar.gz", hash = "sha256:85727c00279f5fa6bedbe6238d2aa6403bedd8b4864ab11207d07df3cc1b2ee5"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "juju" +version = "3.3.1.1" +description = "Python library for Juju" +optional = false +python-versions = "*" +files = [ + {file = "juju-3.3.1.1.tar.gz", hash = "sha256:b30b19051a1c394fa1b1a8c6d38c31d8acdceec2f95b33ffb46fab2d791a29fd"}, +] + +[package.dependencies] +hvac = "*" +kubernetes = ">=12.0.1" +macaroonbakery = ">=1.1,<2.0" +packaging = "*" +paramiko = ">=2.4.0" +pyasn1 = ">=0.4.4" +pyRFC3339 = ">=1.0,<2.0" +pyyaml = ">=5.1.2" +toposort = ">=1.5,<2" +typing_inspect = ">=0.6.0" +websockets = ">=8.1" + +[[package]] +name = "kubernetes" +version = "29.0.0" +description = "Kubernetes python client" +optional = false +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 = "macaroonbakery" +version = "1.3.4" +description = "A Python library port for bakery, higher level operation to work with macaroons" +optional = false +python-versions = "*" +files = [ + {file = "macaroonbakery-1.3.4-py2.py3-none-any.whl", hash = "sha256:1e952a189f5c1e96ef82b081b2852c770d7daa20987e2088e762dd5689fb253b"}, + {file = "macaroonbakery-1.3.4.tar.gz", hash = "sha256:41ca993a23e4f8ef2fe7723b5cd4a30c759735f1d5021e990770c8a0e0f33970"}, +] + +[package.dependencies] +protobuf = ">=3.20.0" +pymacaroons = ">=0.12.0,<1.0" +PyNaCl = ">=1.1.2,<2.0" +pyRFC3339 = ">=1.0,<2.0" +requests = ">=2.18.1,<3.0" +six = ">=1.11.0,<2.0" + +[[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 = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "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 = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +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 = false +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 = "ops" +version = "2.10.0" +description = "The Python library behind great charms" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ops-2.10.0-py3-none-any.whl", hash = "sha256:25ecac7054e53b500d1ea6cccf47de4ed69d39de4eaf9a2bc05d49a12f8651bf"}, + {file = "ops-2.10.0.tar.gz", hash = "sha256:7b0bf23c7ae20891d3fd7943830d75f1b38e7cafb216d5b727771d9c2f69d654"}, +] + +[package.dependencies] +PyYAML = "==6.*" +websocket-client = "==1.*" + +[package.extras] +docs = ["furo", "lxd-sphinx-extensions", "sphinx (==6.2.1)", "sphinx-copybutton", "sphinx-design", "sphinx-tabs"] + +[[package]] +name = "overrides" +version = "7.4.0" +description = "A decorator to automatically detect mismatch when overriding a method." +optional = false +python-versions = ">=3.6" +files = [ + {file = "overrides-7.4.0-py3-none-any.whl", hash = "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d"}, + {file = "overrides-7.4.0.tar.gz", hash = "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "parameterized" +version = "0.9.0" +description = "Parameterized testing with any Python test framework" +optional = false +python-versions = ">=3.7" +files = [ + {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, + {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, +] + +[package.extras] +dev = ["jinja2"] + +[[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 = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pep8-naming" +version = "0.13.3" +description = "Check PEP-8 naming conventions, plugin for flake8" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pep8-naming-0.13.3.tar.gz", hash = "sha256:1705f046dfcd851378aac3be1cd1551c7c1e5ff363bacad707d43007877fa971"}, + {file = "pep8_naming-0.13.3-py3-none-any.whl", hash = "sha256:1a86b8c71a03337c97181917e2b472f0f5e4ccb06844a0d6f0a33522549e7a80"}, +] + +[package.dependencies] +flake8 = ">=5.0.0" + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[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 = "prompt-toolkit" +version = "3.0.43" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "protobuf" +version = "3.20.0" +description = "Protocol Buffers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "protobuf-3.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9d0f3aca8ca51c8b5e204ab92bd8afdb2a8e3df46bd0ce0bd39065d79aabcaa4"}, + {file = "protobuf-3.20.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:001c2160c03b6349c04de39cf1a58e342750da3632f6978a1634a3dcca1ec10e"}, + {file = "protobuf-3.20.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5860b790498f233cdc8d635a17fc08de62e59d4dcd8cdb6c6c0d38a31edf2b"}, + {file = "protobuf-3.20.0-cp310-cp310-win32.whl", hash = "sha256:0b250c60256c8824219352dc2a228a6b49987e5bf94d3ffcf4c46585efcbd499"}, + {file = "protobuf-3.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:a1eebb6eb0653e594cb86cd8e536b9b083373fca9aba761ade6cd412d46fb2ab"}, + {file = "protobuf-3.20.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:bc14037281db66aa60856cd4ce4541a942040686d290e3f3224dd3978f88f554"}, + {file = "protobuf-3.20.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:47257d932de14a7b6c4ae1b7dbf592388153ee35ec7cae216b87ae6490ed39a3"}, + {file = "protobuf-3.20.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fbcbb068ebe67c4ff6483d2e2aa87079c325f8470b24b098d6bf7d4d21d57a69"}, + {file = "protobuf-3.20.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:542f25a4adf3691a306dcc00bf9a73176554938ec9b98f20f929a044f80acf1b"}, + {file = "protobuf-3.20.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fd7133b885e356fa4920ead8289bb45dc6f185a164e99e10279f33732ed5ce15"}, + {file = "protobuf-3.20.0-cp37-cp37m-win32.whl", hash = "sha256:8d84453422312f8275455d1cb52d850d6a4d7d714b784e41b573c6f5bfc2a029"}, + {file = "protobuf-3.20.0-cp37-cp37m-win_amd64.whl", hash = "sha256:52bae32a147c375522ce09bd6af4d2949aca32a0415bc62df1456b3ad17c6001"}, + {file = "protobuf-3.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25d2fcd6eef340082718ec9ad2c58d734429f2b1f7335d989523852f2bba220b"}, + {file = "protobuf-3.20.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:88c8be0558bdfc35e68c42ae5bf785eb9390d25915d4863bbc7583d23da77074"}, + {file = "protobuf-3.20.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:38fd9eb74b852e4ee14b16e9670cd401d147ee3f3ec0d4f7652e0c921d6227f8"}, + {file = "protobuf-3.20.0-cp38-cp38-win32.whl", hash = "sha256:7dcd84dc31ebb35ade755e06d1561d1bd3b85e85dbdbf6278011fc97b22810db"}, + {file = "protobuf-3.20.0-cp38-cp38-win_amd64.whl", hash = "sha256:1eb13f5a5a59ca4973bcfa2fc8fff644bd39f2109c3f7a60bd5860cb6a49b679"}, + {file = "protobuf-3.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d24c81c2310f0063b8fc1c20c8ed01f3331be9374b4b5c2de846f69e11e21fb"}, + {file = "protobuf-3.20.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8be43a91ab66fe995e85ccdbdd1046d9f0443d59e060c0840319290de25b7d33"}, + {file = "protobuf-3.20.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7a53d4035427b9dbfbb397f46642754d294f131e93c661d056366f2a31438263"}, + {file = "protobuf-3.20.0-cp39-cp39-win32.whl", hash = "sha256:32bf4a90c207a0b4e70ca6dd09d43de3cb9898f7d5b69c2e9e3b966a7f342820"}, + {file = "protobuf-3.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:6efe066a7135233f97ce51a1aa007d4fb0be28ef093b4f88dac4ad1b3a2b7b6f"}, + {file = "protobuf-3.20.0-py2.py3-none-any.whl", hash = "sha256:4eda68bd9e2a4879385e6b1ea528c976f59cd9728382005cc54c28bcce8db983"}, + {file = "protobuf-3.20.0.tar.gz", hash = "sha256:71b2c3d1cd26ed1ec7c8196834143258b2ad7f444efff26fdc366c6f5e752702"}, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pyasn1" +version = "0.5.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +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 = false +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 = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[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 = "pydantic" +version = "1.10.14" +description = "Data validation and settings management using python type hints" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, + {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, + {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, + {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, + {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, + {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, + {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, + {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, + {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, +] + +[package.dependencies] +typing-extensions = ">=4.2.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pydocstyle" +version = "6.3.0" +description = "Python docstring style checker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[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 = "pymacaroons" +version = "0.13.0" +description = "Macaroon library for Python" +optional = false +python-versions = "*" +files = [ + {file = "pymacaroons-0.13.0-py2.py3-none-any.whl", hash = "sha256:3e14dff6a262fdbf1a15e769ce635a8aea72e6f8f91e408f9a97166c53b91907"}, + {file = "pymacaroons-0.13.0.tar.gz", hash = "sha256:1e6bba42a5f66c245adf38a5a4006a99dcc06a0703786ea636098667d42903b8"}, +] + +[package.dependencies] +PyNaCl = ">=1.1.2,<2.0" +six = ">=1.8.0" + +[[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 = "pyproject-flake8" +version = "6.1.0" +description = "pyproject-flake8 (`pflake8`), a monkey patching wrapper to connect flake8 with pyproject.toml configuration" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "pyproject_flake8-6.1.0-py3-none-any.whl", hash = "sha256:86ea5559263c098e1aa4f866776aa2cf45362fd91a576b9fd8fbbbb55db12c4e"}, + {file = "pyproject_flake8-6.1.0.tar.gz", hash = "sha256:6da8e5a264395e0148bc11844c6fb50546f1fac83ac9210f7328664135f9e70f"}, +] + +[package.dependencies] +flake8 = "6.1.0" +tomli = {version = "*", markers = "python_version < \"3.11\""} + +[[package]] +name = "pyrfc3339" +version = "1.1" +description = "Generate and parse RFC 3339 timestamps" +optional = false +python-versions = "*" +files = [ + {file = "pyRFC3339-1.1-py2.py3-none-any.whl", hash = "sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4"}, + {file = "pyRFC3339-1.1.tar.gz", hash = "sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a"}, +] + +[package.dependencies] +pytz = "*" + +[[package]] +name = "pytest" +version = "7.4.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, +] + +[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.21.1" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"}, + {file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + +[[package]] +name = "pytest-github-secrets" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.source] +type = "git" +url = "https://github.com/canonical/data-platform-workflows" +reference = "v11.1.2" +resolved_reference = "7bb62c9aa15950d9f0c8c50f1a2b3b610716da54" +subdirectory = "python/pytest_plugins/github_secrets" + +[[package]] +name = "pytest-microceph" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.dependencies] +boto3 = "^1.34.16" +pytest = "^7.4.4" + +[package.source] +type = "git" +url = "https://github.com/canonical/data-platform-workflows" +reference = "v11.1.2" +resolved_reference = "7bb62c9aa15950d9f0c8c50f1a2b3b610716da54" +subdirectory = "python/pytest_plugins/microceph" + +[[package]] +name = "pytest-operator" +version = "0.32.0" +description = "Fixtures for Operators" +optional = false +python-versions = "*" +files = [ + {file = "pytest-operator-0.32.0.tar.gz", hash = "sha256:9e7b3b1384118110654f86bb6aaf772d29c6f38aec05492707ad09beff7b645b"}, + {file = "pytest_operator-0.32.0-py3-none-any.whl", hash = "sha256:a03efd6e3aea5f5c7395ef64c45d6d1719fde61f8593804dc5c8ffff561ecfd4"}, +] + +[package.dependencies] +ipdb = "*" +jinja2 = "*" +juju = "*" +pytest = "*" +pytest-asyncio = "<0.23" +pyyaml = "*" + +[[package]] +name = "pytest-operator-cache" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.dependencies] +pyyaml = "*" + +[package.source] +type = "git" +url = "https://github.com/canonical/data-platform-workflows" +reference = "v11.1.2" +resolved_reference = "7bb62c9aa15950d9f0c8c50f1a2b3b610716da54" +subdirectory = "python/pytest_plugins/pytest_operator_cache" + +[[package]] +name = "pytest-operator-groups" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.dependencies] +pytest = "*" + +[package.source] +type = "git" +url = "https://github.com/canonical/data-platform-workflows" +reference = "v11.1.2" +resolved_reference = "7bb62c9aa15950d9f0c8c50f1a2b3b610716da54" +subdirectory = "python/pytest_plugins/pytest_operator_groups" + +[[package]] +name = "python-dateutil" +version = "2.8.2" +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.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[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 = "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 = "referencing" +version = "0.33.0" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.33.0-py3-none-any.whl", hash = "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5"}, + {file = "referencing-0.33.0.tar.gz", hash = "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.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 = "1.3.1" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "rpds-py" +version = "0.18.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.18.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5b4e7d8d6c9b2e8ee2d55c90b59c707ca59bc30058269b3db7b1f8df5763557e"}, + {file = "rpds_py-0.18.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c463ed05f9dfb9baebef68048aed8dcdc94411e4bf3d33a39ba97e271624f8f7"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01e36a39af54a30f28b73096dd39b6802eddd04c90dbe161c1b8dbe22353189f"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d62dec4976954a23d7f91f2f4530852b0c7608116c257833922a896101336c51"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd18772815d5f008fa03d2b9a681ae38d5ae9f0e599f7dda233c439fcaa00d40"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:923d39efa3cfb7279a0327e337a7958bff00cc447fd07a25cddb0a1cc9a6d2da"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39514da80f971362f9267c600b6d459bfbbc549cffc2cef8e47474fddc9b45b1"}, + {file = "rpds_py-0.18.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a34d557a42aa28bd5c48a023c570219ba2593bcbbb8dc1b98d8cf5d529ab1434"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93df1de2f7f7239dc9cc5a4a12408ee1598725036bd2dedadc14d94525192fc3"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34b18ba135c687f4dac449aa5157d36e2cbb7c03cbea4ddbd88604e076aa836e"}, + {file = "rpds_py-0.18.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0b5dcf9193625afd8ecc92312d6ed78781c46ecbf39af9ad4681fc9f464af88"}, + {file = "rpds_py-0.18.0-cp310-none-win32.whl", hash = "sha256:c4325ff0442a12113a6379af66978c3fe562f846763287ef66bdc1d57925d337"}, + {file = "rpds_py-0.18.0-cp310-none-win_amd64.whl", hash = "sha256:7223a2a5fe0d217e60a60cdae28d6949140dde9c3bcc714063c5b463065e3d66"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3a96e0c6a41dcdba3a0a581bbf6c44bb863f27c541547fb4b9711fd8cf0ffad4"}, + {file = "rpds_py-0.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30f43887bbae0d49113cbaab729a112251a940e9b274536613097ab8b4899cf6"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcb25daa9219b4cf3a0ab24b0eb9a5cc8949ed4dc72acb8fa16b7e1681aa3c58"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d68c93e381010662ab873fea609bf6c0f428b6d0bb00f2c6939782e0818d37bf"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b34b7aa8b261c1dbf7720b5d6f01f38243e9b9daf7e6b8bc1fd4657000062f2c"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2e6d75ab12b0bbab7215e5d40f1e5b738aa539598db27ef83b2ec46747df90e1"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8612cd233543a3781bc659c731b9d607de65890085098986dfd573fc2befe5"}, + {file = "rpds_py-0.18.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aec493917dd45e3c69d00a8874e7cbed844efd935595ef78a0f25f14312e33c6"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:661d25cbffaf8cc42e971dd570d87cb29a665f49f4abe1f9e76be9a5182c4688"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1df3659d26f539ac74fb3b0c481cdf9d725386e3552c6fa2974f4d33d78e544b"}, + {file = "rpds_py-0.18.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1ce3ba137ed54f83e56fb983a5859a27d43a40188ba798993812fed73c70836"}, + {file = "rpds_py-0.18.0-cp311-none-win32.whl", hash = "sha256:69e64831e22a6b377772e7fb337533c365085b31619005802a79242fee620bc1"}, + {file = "rpds_py-0.18.0-cp311-none-win_amd64.whl", hash = "sha256:998e33ad22dc7ec7e030b3df701c43630b5bc0d8fbc2267653577e3fec279afa"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7f2facbd386dd60cbbf1a794181e6aa0bd429bd78bfdf775436020172e2a23f0"}, + {file = "rpds_py-0.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d9a5be316c15ffb2b3c405c4ff14448c36b4435be062a7f578ccd8b01f0c4d8"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd5bf1af8efe569654bbef5a3e0a56eca45f87cfcffab31dd8dde70da5982475"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5417558f6887e9b6b65b4527232553c139b57ec42c64570569b155262ac0754f"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56a737287efecafc16f6d067c2ea0117abadcd078d58721f967952db329a3e5c"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8f03bccbd8586e9dd37219bce4d4e0d3ab492e6b3b533e973fa08a112cb2ffc9"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4457a94da0d5c53dc4b3e4de1158bdab077db23c53232f37a3cb7afdb053a4e3"}, + {file = "rpds_py-0.18.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab39c1ba9023914297dd88ec3b3b3c3f33671baeb6acf82ad7ce883f6e8e157"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9d54553c1136b50fd12cc17e5b11ad07374c316df307e4cfd6441bea5fb68496"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0af039631b6de0397ab2ba16eaf2872e9f8fca391b44d3d8cac317860a700a3f"}, + {file = "rpds_py-0.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84ffab12db93b5f6bad84c712c92060a2d321b35c3c9960b43d08d0f639d60d7"}, + {file = "rpds_py-0.18.0-cp312-none-win32.whl", hash = "sha256:685537e07897f173abcf67258bee3c05c374fa6fff89d4c7e42fb391b0605e98"}, + {file = "rpds_py-0.18.0-cp312-none-win_amd64.whl", hash = "sha256:e003b002ec72c8d5a3e3da2989c7d6065b47d9eaa70cd8808b5384fbb970f4ec"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:08f9ad53c3f31dfb4baa00da22f1e862900f45908383c062c27628754af2e88e"}, + {file = "rpds_py-0.18.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0013fe6b46aa496a6749c77e00a3eb07952832ad6166bd481c74bda0dcb6d58"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32a92116d4f2a80b629778280103d2a510a5b3f6314ceccd6e38006b5e92dcb"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e541ec6f2ec456934fd279a3120f856cd0aedd209fc3852eca563f81738f6861"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bed88b9a458e354014d662d47e7a5baafd7ff81c780fd91584a10d6ec842cb73"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2644e47de560eb7bd55c20fc59f6daa04682655c58d08185a9b95c1970fa1e07"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e8916ae4c720529e18afa0b879473049e95949bf97042e938530e072fde061d"}, + {file = "rpds_py-0.18.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:465a3eb5659338cf2a9243e50ad9b2296fa15061736d6e26240e713522b6235c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea7d4a99f3b38c37eac212dbd6ec42b7a5ec51e2c74b5d3223e43c811609e65f"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:67071a6171e92b6da534b8ae326505f7c18022c6f19072a81dcf40db2638767c"}, + {file = "rpds_py-0.18.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:41ef53e7c58aa4ef281da975f62c258950f54b76ec8e45941e93a3d1d8580594"}, + {file = "rpds_py-0.18.0-cp38-none-win32.whl", hash = "sha256:fdea4952db2793c4ad0bdccd27c1d8fdd1423a92f04598bc39425bcc2b8ee46e"}, + {file = "rpds_py-0.18.0-cp38-none-win_amd64.whl", hash = "sha256:7cd863afe7336c62ec78d7d1349a2f34c007a3cc6c2369d667c65aeec412a5b1"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5307def11a35f5ae4581a0b658b0af8178c65c530e94893345bebf41cc139d33"}, + {file = "rpds_py-0.18.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77f195baa60a54ef9d2de16fbbfd3ff8b04edc0c0140a761b56c267ac11aa467"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39f5441553f1c2aed4de4377178ad8ff8f9d733723d6c66d983d75341de265ab"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a00312dea9310d4cb7dbd7787e722d2e86a95c2db92fbd7d0155f97127bcb40"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f2fc11e8fe034ee3c34d316d0ad8808f45bc3b9ce5857ff29d513f3ff2923a1"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:586f8204935b9ec884500498ccc91aa869fc652c40c093bd9e1471fbcc25c022"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddc2f4dfd396c7bfa18e6ce371cba60e4cf9d2e5cdb71376aa2da264605b60b9"}, + {file = "rpds_py-0.18.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ddcba87675b6d509139d1b521e0c8250e967e63b5909a7e8f8944d0f90ff36f"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7bd339195d84439cbe5771546fe8a4e8a7a045417d8f9de9a368c434e42a721e"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:d7c36232a90d4755b720fbd76739d8891732b18cf240a9c645d75f00639a9024"}, + {file = "rpds_py-0.18.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6b0817e34942b2ca527b0e9298373e7cc75f429e8da2055607f4931fded23e20"}, + {file = "rpds_py-0.18.0-cp39-none-win32.whl", hash = "sha256:99f70b740dc04d09e6b2699b675874367885217a2e9f782bdf5395632ac663b7"}, + {file = "rpds_py-0.18.0-cp39-none-win_amd64.whl", hash = "sha256:6ef687afab047554a2d366e112dd187b62d261d49eb79b77e386f94644363294"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ad36cfb355e24f1bd37cac88c112cd7730873f20fb0bdaf8ba59eedf8216079f"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:36b3ee798c58ace201289024b52788161e1ea133e4ac93fba7d49da5fec0ef9e"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8a2f084546cc59ea99fda8e070be2fd140c3092dc11524a71aa8f0f3d5a55ca"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e4461d0f003a0aa9be2bdd1b798a041f177189c1a0f7619fe8c95ad08d9a45d7"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8db715ebe3bb7d86d77ac1826f7d67ec11a70dbd2376b7cc214199360517b641"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793968759cd0d96cac1e367afd70c235867831983f876a53389ad869b043c948"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66e6a3af5a75363d2c9a48b07cb27c4ea542938b1a2e93b15a503cdfa8490795"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ef0befbb5d79cf32d0266f5cff01545602344eda89480e1dd88aca964260b18"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:1d4acf42190d449d5e89654d5c1ed3a4f17925eec71f05e2a41414689cda02d1"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:a5f446dd5055667aabaee78487f2b5ab72e244f9bc0b2ffebfeec79051679984"}, + {file = "rpds_py-0.18.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9dbbeb27f4e70bfd9eec1be5477517365afe05a9b2c441a0b21929ee61048124"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:22806714311a69fd0af9b35b7be97c18a0fc2826e6827dbb3a8c94eac6cf7eeb"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b34ae4636dfc4e76a438ab826a0d1eed2589ca7d9a1b2d5bb546978ac6485461"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c8370641f1a7f0e0669ddccca22f1da893cef7628396431eb445d46d893e5cd"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c8362467a0fdeccd47935f22c256bec5e6abe543bf0d66e3d3d57a8fb5731863"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11a8c85ef4a07a7638180bf04fe189d12757c696eb41f310d2426895356dcf05"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b316144e85316da2723f9d8dc75bada12fa58489a527091fa1d5a612643d1a0e"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf1ea2e34868f6fbf070e1af291c8180480310173de0b0c43fc38a02929fc0e3"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e546e768d08ad55b20b11dbb78a745151acbd938f8f00d0cfbabe8b0199b9880"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4901165d170a5fde6f589acb90a6b33629ad1ec976d4529e769c6f3d885e3e80"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:618a3d6cae6ef8ec88bb76dd80b83cfe415ad4f1d942ca2a903bf6b6ff97a2da"}, + {file = "rpds_py-0.18.0-pp38-pypy38_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ed4eb745efbff0a8e9587d22a84be94a5eb7d2d99c02dacf7bd0911713ed14dd"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6c81e5f372cd0dc5dc4809553d34f832f60a46034a5f187756d9b90586c2c307"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:43fbac5f22e25bee1d482c97474f930a353542855f05c1161fd804c9dc74a09d"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d7faa6f14017c0b1e69f5e2c357b998731ea75a442ab3841c0dbbbfe902d2c4"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:08231ac30a842bd04daabc4d71fddd7e6d26189406d5a69535638e4dcb88fe76"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:044a3e61a7c2dafacae99d1e722cc2d4c05280790ec5a05031b3876809d89a5c"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f26b5bd1079acdb0c7a5645e350fe54d16b17bfc5e71f371c449383d3342e17"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:482103aed1dfe2f3b71a58eff35ba105289b8d862551ea576bd15479aba01f66"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1374f4129f9bcca53a1bba0bb86bf78325a0374577cf7e9e4cd046b1e6f20e24"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:635dc434ff724b178cb192c70016cc0ad25a275228f749ee0daf0eddbc8183b1"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:bc362ee4e314870a70f4ae88772d72d877246537d9f8cb8f7eacf10884862432"}, + {file = "rpds_py-0.18.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:4832d7d380477521a8c1644bbab6588dfedea5e30a7d967b5fb75977c45fd77f"}, + {file = "rpds_py-0.18.0.tar.gz", hash = "sha256:42821446ee7a76f5d9f71f9e33a4fb2ffd724bb3e7f93386150b61a43115788d"}, +] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +optional = false +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 = "ruamel-yaml" +version = "0.17.35" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3" +files = [ + {file = "ruamel.yaml-0.17.35-py3-none-any.whl", hash = "sha256:b105e3e6fc15b41fdb201ba1b95162ae566a4ef792b9f884c46b4ccc5513a87a"}, + {file = "ruamel.yaml-0.17.35.tar.gz", hash = "sha256:801046a9caacb1b43acc118969b49b96b65e8847f29029563b29ac61d02db61b"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.6" +files = [ + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, + {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, + {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, +] + +[[package]] +name = "s3transfer" +version = "0.10.0" +description = "An Amazon S3 Transfer Manager" +optional = false +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 = "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 = "shellcheck-py" +version = "0.9.0.6" +description = "Python wrapper around invoking shellcheck (https://www.shellcheck.net/)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "shellcheck_py-0.9.0.6-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:38d48a4e2279f5deac374574e7625cd53b7f615301f36b1b1fffd22105dc066d"}, + {file = "shellcheck_py-0.9.0.6-py2.py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:730235c4f92657884f8b343d5426e4dc28e9a6ba9ad54d469cd038e340ea5be0"}, + {file = "shellcheck_py-0.9.0.6-py2.py3-none-win_amd64.whl", hash = "sha256:d1d0c285e2c094813659e0920559a2892da598c1176da59cb4eb9e2f505e5ee8"}, + {file = "shellcheck_py-0.9.0.6.tar.gz", hash = "sha256:f83a0ee1e9762f787ab52e8a906e553b9583586c44e3f9730b6e635f296a69e8"}, +] + +[[package]] +name = "shortuuid" +version = "1.0.11" +description = "A generator library for concise, unambiguous and URL-safe UUIDs." +optional = false +python-versions = ">=3.5" +files = [ + {file = "shortuuid-1.0.11-py3-none-any.whl", hash = "sha256:27ea8f28b1bd0bf8f15057a3ece57275d2059d2b0bb02854f02189962c13b6aa"}, + {file = "shortuuid-1.0.11.tar.gz", hash = "sha256:fc75f2615914815a8e4cb1501b3a513745cb66ef0fd5fc6fb9f8c3fa3481f789"}, +] + +[[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 = "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 = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +optional = false +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 = "toposort" +version = "1.10" +description = "Implements a topological sort algorithm." +optional = false +python-versions = "*" +files = [ + {file = "toposort-1.10-py3-none-any.whl", hash = "sha256:cbdbc0d0bee4d2695ab2ceec97fe0679e9c10eab4b2a87a9372b929e70563a87"}, + {file = "toposort-1.10.tar.gz", hash = "sha256:bfbb479c53d0a696ea7402601f4e693c97b0367837c8898bc6471adfca37a6bd"}, +] + +[[package]] +name = "traitlets" +version = "5.14.1" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.1-py3-none-any.whl", hash = "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74"}, + {file = "traitlets-5.14.1.tar.gz", hash = "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + +[[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.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 = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "websocket-client" +version = "1.7.0" +description = "WebSocket client for Python with low level API options" +optional = false +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 = "websockets" +version = "12.0" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-12.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d554236b2a2006e0ce16315c16eaa0d628dab009c33b63ea03f41c6107958374"}, + {file = "websockets-12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d225bb6886591b1746b17c0573e29804619c8f755b5598d875bb4235ea639be"}, + {file = "websockets-12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb809e816916a3b210bed3c82fb88eaf16e8afcf9c115ebb2bacede1797d2547"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c588f6abc13f78a67044c6b1273a99e1cf31038ad51815b3b016ce699f0d75c2"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5aa9348186d79a5f232115ed3fa9020eab66d6c3437d72f9d2c8ac0c6858c558"}, + {file = "websockets-12.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6350b14a40c95ddd53e775dbdbbbc59b124a5c8ecd6fbb09c2e52029f7a9f480"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:70ec754cc2a769bcd218ed8d7209055667b30860ffecb8633a834dde27d6307c"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e96f5ed1b83a8ddb07909b45bd94833b0710f738115751cdaa9da1fb0cb66e8"}, + {file = "websockets-12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d87be612cbef86f994178d5186add3d94e9f31cc3cb499a0482b866ec477603"}, + {file = "websockets-12.0-cp310-cp310-win32.whl", hash = "sha256:befe90632d66caaf72e8b2ed4d7f02b348913813c8b0a32fae1cc5fe3730902f"}, + {file = "websockets-12.0-cp310-cp310-win_amd64.whl", hash = "sha256:363f57ca8bc8576195d0540c648aa58ac18cf85b76ad5202b9f976918f4219cf"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d873c7de42dea355d73f170be0f23788cf3fa9f7bed718fd2830eefedce01b4"}, + {file = "websockets-12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f61726cae9f65b872502ff3c1496abc93ffbe31b278455c418492016e2afc8f"}, + {file = "websockets-12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed2fcf7a07334c77fc8a230755c2209223a7cc44fc27597729b8ef5425aa61a3"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e332c210b14b57904869ca9f9bf4ca32f5427a03eeb625da9b616c85a3a506c"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5693ef74233122f8ebab026817b1b37fe25c411ecfca084b29bc7d6efc548f45"}, + {file = "websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e9e7db18b4539a29cc5ad8c8b252738a30e2b13f033c2d6e9d0549b45841c04"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6e2df67b8014767d0f785baa98393725739287684b9f8d8a1001eb2839031447"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bea88d71630c5900690fcb03161ab18f8f244805c59e2e0dc4ffadae0a7ee0ca"}, + {file = "websockets-12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dff6cdf35e31d1315790149fee351f9e52978130cef6c87c4b6c9b3baf78bc53"}, + {file = "websockets-12.0-cp311-cp311-win32.whl", hash = "sha256:3e3aa8c468af01d70332a382350ee95f6986db479ce7af14d5e81ec52aa2b402"}, + {file = "websockets-12.0-cp311-cp311-win_amd64.whl", hash = "sha256:25eb766c8ad27da0f79420b2af4b85d29914ba0edf69f547cc4f06ca6f1d403b"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df"}, + {file = "websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc"}, + {file = "websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92"}, + {file = "websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2"}, + {file = "websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113"}, + {file = "websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d"}, + {file = "websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5f6ffe2c6598f7f7207eef9a1228b6f5c818f9f4d53ee920aacd35cec8110438"}, + {file = "websockets-12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9edf3fc590cc2ec20dc9d7a45108b5bbaf21c0d89f9fd3fd1685e223771dc0b2"}, + {file = "websockets-12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8572132c7be52632201a35f5e08348137f658e5ffd21f51f94572ca6c05ea81d"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604428d1b87edbf02b233e2c207d7d528460fa978f9e391bd8aaf9c8311de137"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a9d160fd080c6285e202327aba140fc9a0d910b09e423afff4ae5cbbf1c7205"}, + {file = "websockets-12.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b4aafed34653e465eb77b7c93ef058516cb5acf3eb21e42f33928616172def"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b2ee7288b85959797970114deae81ab41b731f19ebcd3bd499ae9ca0e3f1d2c8"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7fa3d25e81bfe6a89718e9791128398a50dec6d57faf23770787ff441d851967"}, + {file = "websockets-12.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a571f035a47212288e3b3519944f6bf4ac7bc7553243e41eac50dd48552b6df7"}, + {file = "websockets-12.0-cp38-cp38-win32.whl", hash = "sha256:3c6cc1360c10c17463aadd29dd3af332d4a1adaa8796f6b0e9f9df1fdb0bad62"}, + {file = "websockets-12.0-cp38-cp38-win_amd64.whl", hash = "sha256:1bf386089178ea69d720f8db6199a0504a406209a0fc23e603b27b300fdd6892"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ab3d732ad50a4fbd04a4490ef08acd0517b6ae6b77eb967251f4c263011a990d"}, + {file = "websockets-12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1d9697f3337a89691e3bd8dc56dea45a6f6d975f92e7d5f773bc715c15dde28"}, + {file = "websockets-12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1df2fbd2c8a98d38a66f5238484405b8d1d16f929bb7a33ed73e4801222a6f53"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23509452b3bc38e3a057382c2e941d5ac2e01e251acce7adc74011d7d8de434c"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e5fc14ec6ea568200ea4ef46545073da81900a2b67b3e666f04adf53ad452ec"}, + {file = "websockets-12.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46e71dbbd12850224243f5d2aeec90f0aaa0f2dde5aeeb8fc8df21e04d99eff9"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b81f90dcc6c85a9b7f29873beb56c94c85d6f0dac2ea8b60d995bd18bf3e2aae"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a02413bc474feda2849c59ed2dfb2cddb4cd3d2f03a2fedec51d6e959d9b608b"}, + {file = "websockets-12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bbe6013f9f791944ed31ca08b077e26249309639313fff132bfbf3ba105673b9"}, + {file = "websockets-12.0-cp39-cp39-win32.whl", hash = "sha256:cbe83a6bbdf207ff0541de01e11904827540aa069293696dd528a6640bd6a5f6"}, + {file = "websockets-12.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc4e7fa5414512b481a2483775a8e8be7803a35b30ca805afa4998a84f9fd9e8"}, + {file = "websockets-12.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:248d8e2446e13c1d4326e0a6a4e9629cb13a11195051a73acf414812700badbd"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e37d36f0d19f0a4413d3e18c0d03d0c268ada2061868c1e6f5ab1a6d575077"}, + {file = "websockets-12.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d829f975fc2e527a3ef2f9c8f25e553eb7bc779c6665e8e1d52aa22800bb38b"}, + {file = "websockets-12.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2c71bd45a777433dd9113847af751aae36e448bc6b8c361a566cb043eda6ec30"}, + {file = "websockets-12.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:423fc1ed29f7512fceb727e2d2aecb952c46aa34895e9ed96071821309951123"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27a5e9964ef509016759f2ef3f2c1e13f403725a5e6a1775555994966a66e931"}, + {file = "websockets-12.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3181df4583c4d3994d31fb235dc681d2aaad744fbdbf94c4802485ececdecf2"}, + {file = "websockets-12.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b067cb952ce8bf40115f6c19f478dc71c5e719b7fbaa511359795dfd9d1a6468"}, + {file = "websockets-12.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e469d01137942849cff40517c97a30a93ae79917752b34029f0ec72df6b46399"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7"}, + {file = "websockets-12.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba0cab91b3956dfa9f512147860783a1829a8d905ee218a9837c18f683239611"}, + {file = "websockets-12.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2cb388a5bfb56df4d9a406783b7f9dbefb888c09b71629351cc6b036e9259370"}, + {file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"}, + {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "0e9fde1a9c21658f652634e800681f9309cdde8fc6d5a856d431fe822564b68c" diff --git a/pyproject.toml b/pyproject.toml index 44aa33b16d..5bed7e9dd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,88 @@ # Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. -# Testing tools configuration +[tool.poetry] +# Charm is not packed as a standard Python package; this information is not used +name = "charm" +version = "0.1.0" +description = "" +authors = [] + +[tool.poetry.dependencies] +python = "^3.10" +ops = "^2.5.0" +tenacity = "^8.2.2" +boto3 = "^1.28.22" +jinja2 = "^3.1.2" +overrides = "7.4.0" +requests = "2.31.0" +# Official name: ruamel.yaml, but due to Poetry GH#109 - replace dots with dashs +ruamel-yaml = "0.17.35" +shortuuid = "1.0.11" +jproperties = "2.1.1" +pydantic = "^1.10, <2" +cryptography = "^42.0.2" +jsonschema = "^4.21.1" + + +[tool.poetry.group.charm-libs.dependencies] +# data_platform_libs/v0/data_interfaces.py +ops = ">=2.0.0" +# data_platform_libs/v0/upgrade.py +# grafana_agent/v0/cos_agent.py requires pydantic <2 +pydantic = "^1.10, <2" +# tls_certificates_interface/v1/tls_certificates.py +cryptography = "^42.0.2" +jsonschema = "^4.21.1" +# grafana_agent/v0/cos_agent.py +cosl = ">=0.0.7" +bcrypt = ">=4.0.1" + +[tool.poetry.group.format] +optional = true + +[tool.poetry.group.format.dependencies] +black = "^23.7.0" +isort = "^5.12.0" + +[tool.poetry.group.lint] +optional = true + +[tool.poetry.group.lint.dependencies] +black = "^23.7.0" +isort = "^5.12.0" +flake8 = "^6.0.0" +flake8-docstrings = "^1.7.0" +flake8-copyright = "^0.2.4" +flake8-builtins = "^2.1.0" +pyproject-flake8 = "^6.0.0.post1" +pep8-naming = "^0.13.3" +codespell = "^2.2.5" +shellcheck-py = "^0.9.0.5" + +[tool.poetry.group.unit.dependencies] +pytest = "^7.4.0" +pytest-asyncio = "<0.23" +coverage = {extras = ["toml"], version = "^7.4.1"} +parameterized = "^0.9.0" + +[tool.poetry.group.integration.dependencies] +boto3 = "^1.28.23" +pytest = "^7.4.0" +pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v11.1.2", subdirectory = "python/pytest_plugins/github_secrets"} +pytest-asyncio = "<0.23" +pytest-operator = "^0.32.0" +pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", tag = "v11.1.2", subdirectory = "python/pytest_plugins/pytest_operator_cache"} +pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v11.1.2", subdirectory = "python/pytest_plugins/pytest_operator_groups"} +pytest-microceph = {git = "https://github.com/canonical/data-platform-workflows", tag = "v11.1.2", subdirectory = "python/pytest_plugins/microceph"} +juju = "^3.2.2" +ops = "^2.5.0" +tenacity = "^8.2.2" +pyyaml = "^6.0.1" +urllib3 = "^1.26.16" +protobuf = "3.20.0" +opensearch-py = "^2.4.2" + [tool.coverage.run] branch = true @@ -11,8 +92,8 @@ show_missing = true [tool.pytest.ini_options] minversion = "6.0" log_cli_level = "INFO" -asyncio_mode = "auto" markers = ["unstable"] +asyncio_mode = "auto" # Formatting tools configuration [tool.black] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c1befdf4ba..0000000000 --- a/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -bcrypt==4.1.2 -cosl==0.0.7 -cryptography==42.0.1 -jproperties==2.1.1 -jsonschema==4.21.1 -ops==2.9.0 -overrides==7.6.0 -parameterized==0.9.0 -pydantic==1.10.14 -pytest-mock==3.12.0 -requests==2.31.0 -ruamel.yaml==0.18.5 -shortuuid==1.0.11 -tenacity==8.2.3 -urllib3==2.1.0 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 60fdaf9472..0000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2023 Canonical Ltd. -# See LICENSE file for licensing details. -import os -from unittest.mock import PropertyMock - -import pytest -from ops import JujuVersion -from pytest_mock import MockerFixture - - -@pytest.fixture(autouse=True) -def juju_has_secrets(mocker: MockerFixture): - """This fixture will force the usage of secrets whenever run on Juju 3.x. - - NOTE: This is needed, as normally JujuVersion is set to 0.0.0 in tests - (i.e. not the real juju version) - """ - juju_version = os.environ["LIBJUJU_VERSION_SPECIFIER"] - if juju_version.startswith("=="): - juju_version = juju_version[2:] # Removing == symbol - - if juju_version < "3": - mocker.patch.object(JujuVersion, "has_secrets", new_callable=PropertyMock).return_value = ( - False - ) - return False - - mocker.patch.object(JujuVersion, "has_secrets", new_callable=PropertyMock).return_value = True - return True - - -@pytest.fixture -def only_with_juju_secrets(juju_has_secrets): - """Pretty way to skip Juju 3 tests.""" - if not juju_has_secrets: - pytest.skip("Secrets test only applies on Juju 3.x") - - -@pytest.fixture -def only_without_juju_secrets(juju_has_secrets): - """Pretty way to skip Juju 2-specific tests. - - Typically: to save CI time, when the same check were executed in a Juju 3-specific way already - """ - if juju_has_secrets: - pytest.skip("Skipping legacy secrets tests") diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000000..db3bfe1a65 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py deleted file mode 100644 index da132958d9..0000000000 --- a/tests/integration/conftest.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2022 Canonical Ltd. -# See LICENSE file for licensing details. - -import json -import os -from pathlib import Path - -import pytest -from pytest_operator.plugin import OpsTest - - -@pytest.fixture(scope="module") -def ops_test(ops_test: OpsTest) -> OpsTest: - if os.environ.get("CI") == "true": - # Running in GitHub Actions; skip build step - # (GitHub Actions uses a separate, cached build step. See .github/workflows/ci.yaml) - packed_charms = json.loads(os.environ["CI_PACKED_CHARMS"]) - - async def build_charm(charm_path, bases_index: int = None) -> Path: - for charm in packed_charms: - if Path(charm_path) == Path(charm["directory_path"]): - if bases_index is None or bases_index == charm["bases_index"]: - return charm["file_path"] - raise ValueError(f"Unable to find .charm file for {bases_index=} at {charm_path=}") - - ops_test.build_charm = build_charm - return ops_test diff --git a/tests/integration/ha/__init__.py b/tests/integration/ha/__init__.py new file mode 100644 index 0000000000..db3bfe1a65 --- /dev/null +++ b/tests/integration/ha/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/ha/conftest.py b/tests/integration/ha/conftest.py new file mode 100644 index 0000000000..93a2511f62 --- /dev/null +++ b/tests/integration/ha/conftest.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +import logging + +import pytest +from pytest_operator.plugin import OpsTest + +from ..helpers import APP_NAME, get_application_unit_ids +from .continuous_writes import ContinuousWrites +from .helpers import ORIGINAL_RESTART_DELAY, app_name, update_restart_delay + +logger = logging.getLogger(__name__) + + +@pytest.fixture(scope="function") +async def reset_restart_delay(ops_test: OpsTest): + """Resets service file delay on all units.""" + yield + app = (await app_name(ops_test)) or APP_NAME + for unit_id in get_application_unit_ids(ops_test, app): + await update_restart_delay(ops_test, app, unit_id, ORIGINAL_RESTART_DELAY) + + +@pytest.fixture(scope="function") +async def c_writes(ops_test: OpsTest): + """Creates instance of the ContinuousWrites.""" + app = (await app_name(ops_test)) or APP_NAME + return ContinuousWrites(ops_test, app) + + +@pytest.fixture(scope="function") +async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): + """Starts continuous write operations and clears writes at the end of the test.""" + await c_writes.start() + yield + await c_writes.clear() + logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") + + +@pytest.fixture(scope="function") +async def c_balanced_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): + """Same as previous runner, but starts continuous writes on cluster wide replicated index.""" + await c_writes.start(repl_on_all_nodes=True) + yield + await c_writes.clear() + logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") diff --git a/tests/integration/ha/continuous_writes.py b/tests/integration/ha/continuous_writes.py index 7481a2135d..610e300ebb 100644 --- a/tests/integration/ha/continuous_writes.py +++ b/tests/integration/ha/continuous_writes.py @@ -22,11 +22,7 @@ wait_random, ) -from tests.integration.helpers import ( - get_admin_secrets, - get_application_unit_ips, - opensearch_client, -) +from ..helpers import get_admin_secrets, get_application_unit_ips, opensearch_client logging.getLogger("opensearch").setLevel(logging.ERROR) logging.getLogger("opensearchpy.helpers").setLevel(logging.ERROR) diff --git a/tests/integration/ha/helpers.py b/tests/integration/ha/helpers.py index 4c73a8ea75..23629d1331 100644 --- a/tests/integration/ha/helpers.py +++ b/tests/integration/ha/helpers.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. + import json import logging import subprocess @@ -18,9 +19,7 @@ wait_random, ) -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers_data import index_docs_count -from tests.integration.helpers import ( +from ..helpers import ( get_application_unit_ids, get_application_unit_ids_hostnames, get_application_unit_ids_ips, @@ -28,8 +27,13 @@ juju_version_major, run_action, ) +from .continuous_writes import ContinuousWrites +from .helpers_data import index_docs_count OPENSEARCH_SERVICE_PATH = "/etc/systemd/system/snap.opensearch.daemon.service" +ORIGINAL_RESTART_DELAY = 20 +SECOND_APP_NAME = "second-opensearch" +RESTART_DELAY = 360 logger = logging.getLogger(__name__) @@ -207,6 +211,16 @@ async def all_nodes(ops_test: OpsTest, unit_ip: str) -> List[Node]: return result +async def assert_continuous_writes_increasing( + c_writes: ContinuousWrites, +) -> None: + """Asserts that the continuous writes are increasing.""" + writes_count = await c_writes.count() + time.sleep(5) + more_writes = await c_writes.count() + assert more_writes > writes_count, "Writes not continuing to DB" + + async def assert_continuous_writes_consistency( ops_test: OpsTest, c_writes: ContinuousWrites, app: str ) -> None: @@ -442,7 +456,7 @@ async def print_logs(ops_test: OpsTest, app: str, unit_id: int, msg: str) -> str return msg -async def wait_backup_finish(ops_test, leader_id): +async def wait_for_backup(ops_test, leader_id): """Waits the backup to finish and move to the finished state or throws a RetryException.""" for attempt in Retrying(stop=stop_after_attempt(8), wait=wait_fixed(15)): with attempt: @@ -476,8 +490,8 @@ async def wait_restore_finish(ops_test, unit_ip): raise Exception() -async def continuous_writes_increases(ops_test: OpsTest, unit_ip: str, app: str) -> bool: - """Asserts that TEST_BACKUP_INDEX is writable while under continuous writes. +async def start_and_check_continuous_writes(ops_test: OpsTest, unit_ip: str, app: str) -> bool: + """Start continuous writes and check that documents are increasing after some time. Given we are restoring an index, we need to make sure ContinuousWrites restart at the tip of that index instead of doc_id = 0. @@ -490,18 +504,19 @@ async def continuous_writes_increases(ops_test: OpsTest, unit_ip: str, app: str) ) writer = ContinuousWrites(ops_test, app, initial_count=initial_count) await writer.start() - time.sleep(5) + time.sleep(10) result = await writer.stop() return result.count > initial_count -async def backup_cluster(ops_test: OpsTest, leader_id: int) -> bool: +async def backup_cluster(ops_test: OpsTest, leader_id: int) -> int: """Runs the backup of the cluster.""" action = await run_action(ops_test, leader_id, "create-backup") + assert action.status == "completed" logger.debug(f"create-backup output: {action}") - await wait_backup_finish(ops_test, leader_id) - return action.status == "completed" + await wait_for_backup(ops_test, leader_id) + return int(action.response["backup-id"]) async def restore_cluster(ops_test: OpsTest, backup_id: int, unit_ip: str, leader_id: int) -> bool: diff --git a/tests/integration/ha/helpers_data.py b/tests/integration/ha/helpers_data.py index 5b194e4a9f..3e98fcc170 100644 --- a/tests/integration/ha/helpers_data.py +++ b/tests/integration/ha/helpers_data.py @@ -10,7 +10,7 @@ from pytest_operator.plugin import OpsTest from tenacity import Retrying, retry, stop_after_attempt, wait_fixed, wait_random -from tests.integration.helpers import http_request +from ..helpers import http_request logger = logging.getLogger(__name__) diff --git a/tests/integration/ha/test_backups.py b/tests/integration/ha/test_backups.py deleted file mode 100644 index ac9367c32a..0000000000 --- a/tests/integration/ha/test_backups.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2023 Canonical Ltd. -# See LICENSE file for licensing details. - -import asyncio -import logging -import os -import random -import subprocess - -# from pathlib import Path -# -# import boto3 -import pytest -import requests -from pytest_operator.plugin import OpsTest - -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import ( - app_name, - assert_continuous_writes_consistency, - backup_cluster, - continuous_writes_increases, - restore_cluster, -) -from tests.integration.ha.helpers_data import index_docs_count -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( - APP_NAME, - MODEL_CONFIG, - SERIES, - get_leader_unit_id, - get_leader_unit_ip, - get_reachable_unit_ips, - http_request, - run_action, -) -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME - -logger = logging.getLogger(__name__) - - -S3_INTEGRATOR_NAME = "s3-integrator" -TEST_BACKUP_DOC_ID = 10 -CLOUD_CONFIGS = { - "aws": { - "endpoint": "https://s3.amazonaws.com", - "bucket": "data-charms-testing", - "path": "opensearch", - "region": "us-east-1", - }, - "gcp": { - "endpoint": "https://storage.googleapis.com", - "bucket": "data-charms-testing", - "path": "opensearch", - "region": "", - }, -} - -backups_by_cloud = {} -value_before_backup, value_after_backup = None, None - - -# @pytest.fixture(scope="session") -# def cloud_credentials(github_secrets) -> dict[str, dict[str, str]]: -# """Read cloud credentials.""" -# return { -# "aws": { -# "access-key": github_secrets["AWS_ACCESS_KEY"], -# "secret-key": github_secrets["AWS_SECRET_KEY"], -# }, -# "gcp": { -# "access-key": github_secrets["GCP_ACCESS_KEY"], -# "secret-key": github_secrets["GCP_SECRET_KEY"], -# }, -# } - - -# @pytest.fixture(scope="session", autouse=True) -# def clean_backups_from_buckets(cloud_credentials) -> None: -# """Teardown to clean up created backups from clouds.""" -# yield -# -# logger.info("Cleaning backups from cloud buckets") -# for cloud_name, config in CLOUD_CONFIGS.items(): -# backup = backups_by_cloud.get(cloud_name) -# -# if not backup: -# continue -# -# session = boto3.session.Session( -# aws_access_key_id=cloud_credentials[cloud_name]["access-key"], -# aws_secret_access_key=cloud_credentials[cloud_name]["secret-key"], -# region_name=config["region"], -# ) -# s3 = session.resource("s3", endpoint_url=config["endpoint"]) -# bucket = s3.Bucket(config["bucket"]) -# -# # GCS doesn't support batch delete operation, so delete the objects one by one -# backup_path = str(Path(config["path"]) / backups_by_cloud[cloud_name]) -# for bucket_object in bucket.objects.filter(Prefix=backup_path): -# bucket_object.delete() - - -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Starts continuous write operations and clears writes at the end of the test.""" - await c_writes.start() - yield - - reachable_ip = random.choice(await get_reachable_unit_ips(ops_test)) - await http_request(ops_test, "GET", f"https://{reachable_ip}:9200/_cat/nodes", json_resp=False) - await http_request( - ops_test, "GET", f"https://{reachable_ip}:9200/_cat/shards", json_resp=False - ) - - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - -# TODO: Remove this method as soon as poetry gets merged. -@pytest.fixture(scope="session") -def microceph(): - """Starts microceph radosgw.""" - if "microceph" not in subprocess.check_output(["sudo", "snap", "list"]).decode(): - uceph = "/tmp/microceph.sh" - - with open(uceph, "w") as f: - # TODO: if this code stays, then the script below should be added as a file - # in the charm. - resp = requests.get( - "https://raw.githubusercontent.com/canonical/microceph-action/main/microceph.sh" - ) - f.write(resp.content.decode()) - - os.chmod(uceph, 0o755) - subprocess.check_output( - [ - "sudo", - uceph, - "-c", - "latest/edge", - "-a", - "accesskey", - "-s", - "secretkey", - "-b", - "data-charms-testing", - "-z", - "5G", - ] - ) - ip = subprocess.check_output(["hostname", "-I"]).decode().split()[0] - # TODO: if this code stays, then we should generate random keys for the test. - return {"url": f"http://{ip}", "access-key": "accesskey", "secret-key": "secretkey"} - - -@pytest.mark.abort_on_fail -@pytest.mark.skip_if_deployed -async def test_build_and_deploy( - ops_test: OpsTest, microceph -) -> None: # , cloud_credentials) -> None: - """Build and deploy an HA cluster of OpenSearch and corresponding S3 integration.""" - # it is possible for users to provide their own cluster for HA testing. - # Hence, check if there is a pre-existing cluster. - - if await app_name(ops_test): - return - - s3_config = { - "bucket": "data-charms-testing", - "path": "/", - "endpoint": microceph["url"], - "region": "default", - } - - my_charm = await ops_test.build_charm(".") - await ops_test.model.set_config(MODEL_CONFIG) - - # Deploy TLS Certificates operator. - tls_config = {"ca-common-name": "CN_CA"} - - # Convert to integer as environ always returns string - app_num_units = 3 - - await asyncio.gather( - ops_test.model.deploy(TLS_CERTIFICATES_APP_NAME, channel="stable", config=tls_config), - ops_test.model.deploy(S3_INTEGRATOR_NAME, channel="stable", config=s3_config), - ops_test.model.deploy(my_charm, num_units=app_num_units, series=SERIES), - ) - s3_creds = { - "access-key": microceph["access-key"], - "secret-key": microceph["secret-key"], - } - - await run_action( - ops_test, - 0, - "sync-s3-credentials", - params=s3_creds, - app=S3_INTEGRATOR_NAME, - ) - - # Relate it to OpenSearch to set up TLS. - await ops_test.model.integrate(APP_NAME, TLS_CERTIFICATES_APP_NAME) - await ops_test.model.integrate(APP_NAME, S3_INTEGRATOR_NAME) - await ops_test.model.wait_for_idle( - apps=[TLS_CERTIFICATES_APP_NAME, APP_NAME], - status="active", - timeout=1400, - idle_period=IDLE_PERIOD, - ) - - -@pytest.mark.abort_on_fail -async def test_backup_cluster( - ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner -) -> None: - """Runs the backup process whilst writing to the cluster into 'noisy-index'.""" - app = (await app_name(ops_test)) or APP_NAME - leader_id = await get_leader_unit_id(ops_test) - - assert await backup_cluster( - ops_test, - leader_id, - ) - # continuous writes checks - await assert_continuous_writes_consistency(ops_test, c_writes, app) - - -@pytest.mark.abort_on_fail -async def test_restore_cluster(ops_test: OpsTest) -> None: - """Deletes the TEST_BACKUP_INDEX, restores the cluster and tries to search for index.""" - unit_ip = await get_leader_unit_ip(ops_test) - app = (await app_name(ops_test)) or APP_NAME - leader_id = await get_leader_unit_id(ops_test) - - assert await restore_cluster( - ops_test, - 1, # backup_id - unit_ip, - leader_id, - ) - count = await index_docs_count(ops_test, app, unit_ip, ContinuousWrites.INDEX_NAME) - assert count > 0 - await continuous_writes_increases(ops_test, unit_ip, app) - - -@pytest.mark.abort_on_fail -async def test_restore_cluster_after_app_destroyed(ops_test: OpsTest) -> None: - """Deletes the entire OpenSearch cluster and redeploys from scratch. - - Restores the backup and then checks if the same TEST_BACKUP_INDEX is there. - """ - app = (await app_name(ops_test)) or APP_NAME - - logging.info("Destroying the application") - await ops_test.model.remove_application(app, block_until_done=True) - app_num_units = 3 - my_charm = await ops_test.build_charm(".") - # Redeploy - await asyncio.gather( - ops_test.model.deploy(my_charm, num_units=app_num_units, series=SERIES), - ) - # Relate it to OpenSearch to set up TLS. - await ops_test.model.integrate(APP_NAME, TLS_CERTIFICATES_APP_NAME) - await ops_test.model.integrate(APP_NAME, S3_INTEGRATOR_NAME) - await ops_test.model.wait_for_idle( - apps=[APP_NAME], - status="active", - timeout=1400, - idle_period=IDLE_PERIOD, - ) - - leader_id = await get_leader_unit_id(ops_test) - leader_unit_ip = await get_leader_unit_ip(ops_test, app=app) - assert await restore_cluster( - ops_test, - 1, # backup_id - leader_unit_ip, - leader_id, - ) - # Count the number of docs in the index - count = await index_docs_count(ops_test, app, leader_unit_ip, ContinuousWrites.INDEX_NAME) - assert count > 0 - await continuous_writes_increases(ops_test, leader_unit_ip, app) - - -@pytest.mark.abort_on_fail -async def test_remove_and_readd_s3_relation(ops_test: OpsTest) -> None: - """Removes and re-adds the s3-credentials relation to test backup and restore.""" - app = (await app_name(ops_test)) or APP_NAME - leader_id = await get_leader_unit_id(ops_test) - unit_ip = await get_leader_unit_ip(ops_test) - - logger.info("Remove s3-credentials relation") - # Remove relation - await ops_test.model.applications[app].destroy_relation( - "s3-credentials", f"{S3_INTEGRATOR_NAME}:s3-credentials" - ) - await ops_test.model.wait_for_idle( - apps=[app], - status="active", - timeout=1400, - idle_period=IDLE_PERIOD, - ) - - logger.info("Re-add s3-credentials relation") - await ops_test.model.integrate(APP_NAME, S3_INTEGRATOR_NAME) - await ops_test.model.wait_for_idle( - apps=[app], - status="active", - timeout=1400, - idle_period=IDLE_PERIOD, - ) - - assert await backup_cluster( - ops_test, - leader_id, - ) - assert await restore_cluster( - ops_test, - 1, # backup_id - unit_ip, - leader_id, - ) - count = await index_docs_count(ops_test, app, unit_ip, ContinuousWrites.INDEX_NAME) - assert count > 0 - await continuous_writes_increases(ops_test, unit_ip, app) diff --git a/tests/integration/ha/test_ha.py b/tests/integration/ha/test_ha.py index ab5beb1b18..2fcb9277e5 100644 --- a/tests/integration/ha/test_ha.py +++ b/tests/integration/ha/test_ha.py @@ -9,25 +9,7 @@ import pytest from pytest_operator.plugin import OpsTest -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import ( - all_processes_down, - app_name, - assert_continuous_writes_consistency, - get_elected_cm_unit_id, - get_shards_by_index, - send_kill_signal_to_process, - update_restart_delay, -) -from tests.integration.ha.helpers_data import ( - create_index, - default_doc, - delete_index, - index_doc, - search, -) -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( +from ..helpers import ( APP_NAME, MODEL_CONFIG, SERIES, @@ -40,51 +22,28 @@ get_reachable_unit_ips, is_up, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..helpers_deployments import wait_until +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .continuous_writes import ContinuousWrites +from .helpers import ( + ORIGINAL_RESTART_DELAY, + RESTART_DELAY, + all_processes_down, + app_name, + assert_continuous_writes_consistency, + assert_continuous_writes_increasing, + get_elected_cm_unit_id, + get_shards_by_index, + send_kill_signal_to_process, + update_restart_delay, +) +from .helpers_data import create_index, default_doc, delete_index, index_doc, search +from .test_horizontal_scaling import IDLE_PERIOD logger = logging.getLogger(__name__) -SECOND_APP_NAME = "second-opensearch" -ORIGINAL_RESTART_DELAY = 20 -RESTART_DELAY = 360 - - -@pytest.fixture() -async def reset_restart_delay(ops_test: OpsTest): - """Resets service file delay on all units.""" - yield - app = (await app_name(ops_test)) or APP_NAME - for unit_id in get_application_unit_ids(ops_test, app): - await update_restart_delay(ops_test, app, unit_id, ORIGINAL_RESTART_DELAY) - - -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Starts continuous write operations and clears writes at the end of the test.""" - await c_writes.start() - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - -@pytest.fixture() -async def c_balanced_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Same as previous runner, but starts continuous writes on cluster wide replicated index.""" - await c_writes.start(repl_on_all_nodes=True) - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -96,7 +55,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: my_charm = await ops_test.build_charm(".") await ops_test.model.set_config(MODEL_CONFIG) - # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( @@ -115,6 +73,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == 3 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_replication_across_members( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner @@ -139,7 +98,7 @@ async def test_replication_across_members( await index_doc(ops_test, app, leader_unit_ip, index_name, doc_id) # check that the doc can be retrieved from any node - for u_id, u_ip in units.items(): + for u_ip in units.values(): docs = await search( ops_test, app, @@ -157,6 +116,7 @@ async def test_replication_across_members( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_kill_db_process_node_with_primary_shard( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -189,12 +149,7 @@ async def test_kill_db_process_node_with_primary_shard( ops_test, app, first_unit_with_primary_shard, signal="SIGKILL" ) - # verify new writes are continuing by counting the number of writes before and after 5 seconds - # should also be plenty for the shard primary reelection to happen - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # verify that the opensearch service is back running on the old primary unit assert await is_up( @@ -223,6 +178,7 @@ async def test_kill_db_process_node_with_primary_shard( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_kill_db_process_node_with_elected_cm( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -252,12 +208,7 @@ async def test_kill_db_process_node_with_elected_cm( # Kill the opensearch process await send_kill_signal_to_process(ops_test, app, first_elected_cm_unit_id, signal="SIGKILL") - # verify new writes are continuing by counting the number of writes before and after 5 seconds - # should also be plenty for the cluster manager reelection to happen - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # verify that the opensearch service is back running on the old elected cm unit assert await is_up( @@ -279,6 +230,7 @@ async def test_kill_db_process_node_with_elected_cm( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_freeze_db_process_node_with_primary_shard( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -318,12 +270,7 @@ async def test_freeze_db_process_node_with_primary_shard( is_node_up = await is_up(ops_test, units_ips[first_unit_with_primary_shard], retries=3) assert not is_node_up - # verify new writes are continuing by counting the number of writes before and after 5 seconds - # should also be plenty for the shard primary reelection to happen - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # get reachable unit to perform requests against, in case the previously stopped unit # is leader unit, so its address is not reachable @@ -368,6 +315,7 @@ async def test_freeze_db_process_node_with_primary_shard( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_freeze_db_process_node_with_elected_cm( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -406,12 +354,7 @@ async def test_freeze_db_process_node_with_elected_cm( is_node_up = await is_up(ops_test, units_ips[first_elected_cm_unit_id], retries=3) assert not is_node_up - # verify new writes are continuing by counting the number of writes before and after 5 seconds - # should also be plenty for the cluster manager reelection to happen - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # get reachable unit to perform requests against, in case the previously stopped unit # is leader unit, so its address is not reachable @@ -446,6 +389,7 @@ async def test_freeze_db_process_node_with_elected_cm( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_restart_db_process_node_with_elected_cm( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -475,12 +419,7 @@ async def test_restart_db_process_node_with_elected_cm( # restart the opensearch process await send_kill_signal_to_process(ops_test, app, first_elected_cm_unit_id, signal="SIGTERM") - # verify new writes are continuing by counting the number of writes before and after 5 seconds - # should also be plenty for the cluster manager reelection to happen - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # verify that the opensearch service is back running on the unit previously elected CM unit assert await is_up( @@ -501,6 +440,7 @@ async def test_restart_db_process_node_with_elected_cm( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_restart_db_process_node_with_primary_shard( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -533,12 +473,7 @@ async def test_restart_db_process_node_with_primary_shard( ops_test, app, first_unit_with_primary_shard, signal="SIGTERM" ) - # verify new writes are continuing by counting the number of writes before and after 5 seconds - # should also be plenty for the cluster manager reelection to happen - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # verify that the opensearch service is back running on the previous primary shard unit assert await is_up( @@ -566,6 +501,7 @@ async def test_restart_db_process_node_with_primary_shard( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) async def test_full_cluster_crash( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner, reset_restart_delay ) -> None: @@ -607,11 +543,7 @@ async def test_full_cluster_crash( ops_test, leader_ip, get_application_unit_names(ops_test, app=app) ) - # verify new writes are continuing by counting the number of writes before and after 5 seconds - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # check that cluster health is green (all primary and replica shards allocated) health_resp = await cluster_health(ops_test, leader_ip) @@ -621,6 +553,7 @@ async def test_full_cluster_crash( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_full_cluster_restart( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner, reset_restart_delay @@ -663,11 +596,7 @@ async def test_full_cluster_restart( ops_test, leader_ip, get_application_unit_names(ops_test, app=app) ) - # verify new writes are continuing by counting the number of writes before and after 5 seconds - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # check that cluster health is green (all primary and replica shards allocated) health_resp = await cluster_health(ops_test, leader_ip) diff --git a/tests/integration/ha/test_ha_multi_clusters.py b/tests/integration/ha/test_ha_multi_clusters.py index fd1524bd2f..67c3616d7f 100644 --- a/tests/integration/ha/test_ha_multi_clusters.py +++ b/tests/integration/ha/test_ha_multi_clusters.py @@ -8,66 +8,25 @@ import pytest from pytest_operator.plugin import OpsTest -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import ( - app_name, - assert_continuous_writes_consistency, - update_restart_delay, -) -from tests.integration.ha.helpers_data import delete_index, index_doc, search -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( +from ..helpers import ( APP_NAME, MODEL_CONFIG, SERIES, + app_name, get_application_unit_ids, get_leader_unit_ip, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..helpers_deployments import wait_until +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .continuous_writes import ContinuousWrites +from .helpers import SECOND_APP_NAME, assert_continuous_writes_consistency +from .helpers_data import delete_index, index_doc, search +from .test_horizontal_scaling import IDLE_PERIOD logger = logging.getLogger(__name__) -SECOND_APP_NAME = "second-opensearch" -ORIGINAL_RESTART_DELAY = 20 -RESTART_DELAY = 360 - - -@pytest.fixture() -async def reset_restart_delay(ops_test: OpsTest): - """Resets service file delay on all units.""" - yield - app = (await app_name(ops_test)) or APP_NAME - for unit_id in get_application_unit_ids(ops_test, app): - await update_restart_delay(ops_test, app, unit_id, ORIGINAL_RESTART_DELAY) - - -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Starts continuous write operations and clears writes at the end of the test.""" - await c_writes.start() - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - -@pytest.fixture() -async def c_balanced_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Same as previous runner, but starts continuous writes on cluster wide replicated index.""" - await c_writes.start(repl_on_all_nodes=True) - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -79,7 +38,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: my_charm = await ops_test.build_charm(".") await ops_test.model.set_config(MODEL_CONFIG) - # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( @@ -101,6 +59,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: # put this test at the end of the list of tests, as we delete an app during cleanup # and the safeguards we have on the charm prevent us from doing so, so we'll keep # using a unit without need - when other tests may need the unit on the CI +@pytest.mark.group(1) async def test_multi_clusters_db_isolation( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner ) -> None: diff --git a/tests/integration/ha/test_ha_networking.py b/tests/integration/ha/test_ha_networking.py index eabe086829..4b43b9d8b4 100644 --- a/tests/integration/ha/test_ha_networking.py +++ b/tests/integration/ha/test_ha_networking.py @@ -4,15 +4,13 @@ import asyncio import logging -import time import pytest from pytest_operator.plugin import OpsTest -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import ( - app_name, +from ..ha.helpers import ( assert_continuous_writes_consistency, + assert_continuous_writes_increasing, cut_network_from_unit_with_ip_change, cut_network_from_unit_without_ip_change, get_elected_cm_unit_id, @@ -22,11 +20,11 @@ restore_network_for_unit_with_ip_change, restore_network_for_unit_without_ip_change, ) -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( +from ..helpers import ( APP_NAME, MODEL_CONFIG, SERIES, + app_name, check_cluster_formation_successful, get_application_unit_ids_hostnames, get_application_unit_ids_ips, @@ -35,28 +33,15 @@ get_leader_unit_ip, is_up, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..helpers_deployments import wait_until +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .continuous_writes import ContinuousWrites +from .test_horizontal_scaling import IDLE_PERIOD logger = logging.getLogger(__name__) -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_balanced_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Same as previous runner, but starts continuous writes on cluster wide replicated index.""" - await c_writes.start(repl_on_all_nodes=True) - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -68,7 +53,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: my_charm = await ops_test.build_charm(".") await ops_test.model.set_config(MODEL_CONFIG) - # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( @@ -87,6 +71,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == 3 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_full_network_cut_with_ip_change_node_with_elected_cm( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -146,11 +131,7 @@ async def test_full_network_cut_with_ip_change_node_with_elected_cm( ops_test, first_elected_cm_unit_ip, retries=3 ), "Connection still possible to the first CM node where the network was cut." - # verify new writes are continuing by counting the number of writes before and after 5 seconds - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # check new CM got elected leader_unit_ip = await get_leader_unit_ip(ops_test, app=app) @@ -192,6 +173,7 @@ async def test_full_network_cut_with_ip_change_node_with_elected_cm( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_full_network_cut_with_ip_change_node_with_primary_shard( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -252,11 +234,7 @@ async def test_full_network_cut_with_ip_change_node_with_primary_shard( ops_test, first_unit_with_primary_shard_ip, retries=3 ), "Connection still possible to the first unit with primary shard where the network was cut." - # verify new writes are continuing by counting the number of writes before and after 5 seconds - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # check new primary shard got elected leader_unit_ip = await get_leader_unit_ip(ops_test, app=app) @@ -313,6 +291,7 @@ async def test_full_network_cut_with_ip_change_node_with_primary_shard( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_full_network_cut_without_ip_change_node_with_elected_cm( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -367,11 +346,7 @@ async def test_full_network_cut_without_ip_change_node_with_elected_cm( ops_test, first_elected_cm_unit_ip, retries=3 ), "Connection still possible to the first CM node where the network was cut." - # verify new writes are continuing by counting the number of writes before and after 5 seconds - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # check new CM got elected leader_unit_ip = await get_leader_unit_ip(ops_test, app=app) @@ -404,6 +379,7 @@ async def test_full_network_cut_without_ip_change_node_with_elected_cm( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_full_network_cut_without_ip_change_node_with_primary_shard( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner @@ -461,11 +437,7 @@ async def test_full_network_cut_without_ip_change_node_with_primary_shard( ops_test, first_unit_with_primary_shard_ip, retries=3 ), "Connection still possible to the first unit with primary shard where the network was cut." - # verify new writes are continuing by counting the number of writes before and after 5 seconds - writes = await c_writes.count() - time.sleep(5) - more_writes = await c_writes.count() - assert more_writes > writes, "Writes not continuing to DB" + await assert_continuous_writes_increasing(c_writes) # check new primary shard got elected leader_unit_ip = await get_leader_unit_ip(ops_test, app=app) diff --git a/tests/integration/ha/test_horizontal_scaling.py b/tests/integration/ha/test_horizontal_scaling.py index 7df21c784a..973db96af5 100644 --- a/tests/integration/ha/test_horizontal_scaling.py +++ b/tests/integration/ha/test_horizontal_scaling.py @@ -11,8 +11,7 @@ from charms.opensearch.v0.helper_cluster import ClusterTopology from pytest_operator.plugin import OpsTest -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import ( +from ..ha.helpers import ( all_nodes, assert_continuous_writes_consistency, get_elected_cm_unit_id, @@ -20,12 +19,7 @@ get_shards_by_index, get_shards_by_state, ) -from tests.integration.ha.helpers_data import ( - create_dummy_docs, - create_dummy_indexes, - delete_dummy_indexes, -) -from tests.integration.helpers import ( +from ..helpers import ( APP_NAME, IDLE_PERIOD, MODEL_CONFIG, @@ -38,28 +32,15 @@ get_leader_unit_id, get_leader_unit_ip, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..helpers_deployments import wait_until +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .continuous_writes import ContinuousWrites +from .helpers_data import create_dummy_docs, create_dummy_indexes, delete_dummy_indexes logger = logging.getLogger(__name__) -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Starts continuous write operations and clears writes at the end of the test.""" - await c_writes.start() - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -71,7 +52,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: my_charm = await ops_test.build_charm(".") await ops_test.model.set_config(MODEL_CONFIG) - # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( @@ -87,6 +67,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == 1 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_horizontal_scale_up( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner @@ -134,6 +115,7 @@ async def test_horizontal_scale_up( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_safe_scale_down_shards_realloc( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner @@ -243,6 +225,7 @@ async def test_safe_scale_down_shards_realloc( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_safe_scale_down_roles_reassigning( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner @@ -337,6 +320,7 @@ async def test_safe_scale_down_roles_reassigning( await assert_continuous_writes_consistency(ops_test, c_writes, app) +@pytest.mark.group(1) async def test_safe_scale_down_remove_leaders( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner ) -> None: diff --git a/tests/integration/ha/test_large_deployments.py b/tests/integration/ha/test_large_deployments.py index 7326fb29ea..1ad9af0a74 100644 --- a/tests/integration/ha/test_large_deployments.py +++ b/tests/integration/ha/test_large_deployments.py @@ -9,63 +9,26 @@ from charms.opensearch.v0.constants_charm import PClusterWrongNodesCountForQuorum from pytest_operator.plugin import OpsTest -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import all_nodes, app_name, update_restart_delay -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( +from ..helpers import ( APP_NAME, MODEL_CONFIG, SERIES, check_cluster_formation_successful, cluster_health, - get_application_unit_ids, get_application_unit_names, get_application_unit_status, get_leader_unit_ip, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..helpers_deployments import wait_until +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .continuous_writes import ContinuousWrites +from .helpers import all_nodes, app_name +from .test_horizontal_scaling import IDLE_PERIOD logger = logging.getLogger(__name__) -ORIGINAL_RESTART_DELAY = 20 - - -@pytest.fixture() -async def reset_restart_delay(ops_test: OpsTest): - """Resets service file delay on all units.""" - yield - app = (await app_name(ops_test)) or APP_NAME - for unit_id in get_application_unit_ids(ops_test, app): - await update_restart_delay(ops_test, app, unit_id, ORIGINAL_RESTART_DELAY) - - -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Starts continuous write operations and clears writes at the end of the test.""" - await c_writes.start() - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - -@pytest.fixture() -async def c_balanced_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Same as previous runner, but starts continuous writes on cluster wide replicated index.""" - await c_writes.start(repl_on_all_nodes=True) - yield - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -77,7 +40,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: my_charm = await ops_test.build_charm(".") await ops_test.model.set_config(MODEL_CONFIG) - # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( @@ -98,6 +60,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == 3 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_set_roles_manually( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner diff --git a/tests/integration/ha/test_storage.py b/tests/integration/ha/test_storage.py index 32f7e51c5d..6af195f663 100644 --- a/tests/integration/ha/test_storage.py +++ b/tests/integration/ha/test_storage.py @@ -4,86 +4,21 @@ import asyncio import logging -import random import time import pytest from pytest_operator.plugin import OpsTest -from tests.integration.ha.continuous_writes import ContinuousWrites -from tests.integration.ha.helpers import ( - app_name, - storage_id, - storage_type, - update_restart_delay, -) -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( - APP_NAME, - MODEL_CONFIG, - SERIES, - get_application_unit_ids, - get_reachable_unit_ips, - http_request, -) -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..ha.helpers import app_name, storage_id, storage_type +from ..ha.test_horizontal_scaling import IDLE_PERIOD +from ..helpers import APP_NAME, MODEL_CONFIG, SERIES, get_application_unit_ids +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .continuous_writes import ContinuousWrites logger = logging.getLogger(__name__) -SECOND_APP_NAME = "second-opensearch" -ORIGINAL_RESTART_DELAY = 20 -RESTART_DELAY = 360 - - -@pytest.fixture() -async def reset_restart_delay(ops_test: OpsTest): - """Resets service file delay on all units.""" - yield - app = (await app_name(ops_test)) or APP_NAME - for unit_id in get_application_unit_ids(ops_test, app): - await update_restart_delay(ops_test, app, unit_id, ORIGINAL_RESTART_DELAY) - - -@pytest.fixture() -async def c_writes(ops_test: OpsTest): - """Creates instance of the ContinuousWrites.""" - app = (await app_name(ops_test)) or APP_NAME - return ContinuousWrites(ops_test, app) - - -@pytest.fixture() -async def c_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Starts continuous write operations and clears writes at the end of the test.""" - await c_writes.start() - yield - - reachable_ip = random.choice(await get_reachable_unit_ips(ops_test)) - await http_request(ops_test, "GET", f"https://{reachable_ip}:9200/_cat/nodes", json_resp=False) - await http_request( - ops_test, "GET", f"https://{reachable_ip}:9200/_cat/shards", json_resp=False - ) - - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - -@pytest.fixture() -async def c_balanced_writes_runner(ops_test: OpsTest, c_writes: ContinuousWrites): - """Same as previous runner, but starts continuous writes on cluster wide replicated index.""" - await c_writes.start(repl_on_all_nodes=True) - yield - - reachable_ip = random.choice(await get_reachable_unit_ips(ops_test)) - await http_request(ops_test, "GET", f"https://{reachable_ip}:9200/_cat/nodes", json_resp=False) - await http_request( - ops_test, "GET", f"https://{reachable_ip}:9200/_cat/shards", json_resp=False - ) - - await c_writes.clear() - logger.info("\n\n\n\nThe writes have been cleared.\n\n\n\n") - - +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -95,7 +30,6 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: my_charm = await ops_test.build_charm(".") await ops_test.model.set_config(MODEL_CONFIG) - # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( @@ -114,6 +48,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == 1 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_storage_reuse_after_scale_down( ops_test: OpsTest, c_writes: ContinuousWrites, c_writes_runner @@ -176,6 +111,7 @@ async def test_storage_reuse_after_scale_down( assert writes_result.max_stored_id == (await c_writes.max_stored_id()) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_storage_reuse_in_new_cluster_after_app_removal( ops_test: OpsTest, c_writes: ContinuousWrites, c_balanced_writes_runner diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index 339647f00f..aa6aeee725 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -13,11 +13,6 @@ import requests import yaml from charms.opensearch.v0.helper_networking import is_reachable -from integration.helpers_deployments import ( - Status, - get_application_units, - get_unit_hostname, -) from opensearchpy import OpenSearch from pytest_operator.plugin import OpsTest from tenacity import ( @@ -29,6 +24,8 @@ wait_random, ) +from .helpers_deployments import Status, get_application_units, get_unit_hostname + METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) APP_NAME = METADATA["name"] diff --git a/tests/integration/plugins/__init__.py b/tests/integration/plugins/__init__.py new file mode 100644 index 0000000000..db3bfe1a65 --- /dev/null +++ b/tests/integration/plugins/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/plugins/helpers.py b/tests/integration/plugins/helpers.py index fc033079ff..d6ab3bb155 100644 --- a/tests/integration/plugins/helpers.py +++ b/tests/integration/plugins/helpers.py @@ -18,8 +18,8 @@ wait_random, ) -from tests.integration.ha.helpers_data import bulk_insert, create_index -from tests.integration.helpers import get_application_unit_ids, http_request +from ..ha.helpers_data import bulk_insert, create_index +from ..helpers import get_application_unit_ids, http_request logger = logging.getLogger(__name__) diff --git a/tests/integration/plugins/test_plugins.py b/tests/integration/plugins/test_plugins.py index da209abf6a..e77a1969a9 100644 --- a/tests/integration/plugins/test_plugins.py +++ b/tests/integration/plugins/test_plugins.py @@ -9,10 +9,10 @@ from pytest_operator.plugin import OpsTest from tenacity import RetryError -from tests.integration.ha.helpers import app_name -from tests.integration.ha.helpers_data import bulk_insert, create_index, search -from tests.integration.ha.test_horizontal_scaling import IDLE_PERIOD -from tests.integration.helpers import ( +from ..ha.helpers import app_name +from ..ha.helpers_data import bulk_insert, create_index, search +from ..ha.test_horizontal_scaling import IDLE_PERIOD +from ..helpers import ( APP_NAME, MODEL_CONFIG, SERIES, @@ -25,7 +25,7 @@ http_request, run_action, ) -from tests.integration.plugins.helpers import ( +from ..plugins.helpers import ( create_index_and_bulk_insert, generate_bulk_training_data, get_application_unit_ids_start_time, @@ -33,15 +33,14 @@ is_knn_training_complete, run_knn_training, ) -from tests.integration.relations.opensearch_provider.helpers import ( - get_unit_relation_data, -) -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from ..relations.helpers import get_unit_relation_data +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME COS_APP_NAME = "grafana-agent" COS_RELATION_NAME = "cos-agent" +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -62,10 +61,10 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await asyncio.gather( - ops_test.model.deploy(TLS_CERTIFICATES_APP_NAME, channel="stable", config=config), ops_test.model.deploy( my_charm, num_units=3, series=SERIES, config={"plugin_opensearch_knn": True} ), + ops_test.model.deploy(TLS_CERTIFICATES_APP_NAME, channel="stable", config=config), ) # Relate it to OpenSearch to set up TLS. @@ -80,6 +79,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: @pytest.mark.abort_on_fail +@pytest.mark.group(1) async def test_prometheus_exporter_enabled_by_default(ops_test): """Test that Prometheus Exporter is running before the relation is there.""" leader_unit_ip = await get_leader_unit_ip(ops_test, app=APP_NAME) @@ -92,6 +92,7 @@ async def test_prometheus_exporter_enabled_by_default(ops_test): @pytest.mark.abort_on_fail +@pytest.mark.group(1) async def test_prometheus_exporter_cos_relation(ops_test): await ops_test.model.deploy(COS_APP_NAME, channel="edge"), await ops_test.model.integrate(APP_NAME, COS_APP_NAME) @@ -122,6 +123,7 @@ async def test_prometheus_exporter_cos_relation(ops_test): @pytest.mark.abort_on_fail +@pytest.mark.group(1) async def test_monitoring_user_fetch_prometheus_data(ops_test): leader_unit_ip = await get_leader_unit_ip(ops_test, app=APP_NAME) endpoint = f"https://{leader_unit_ip}:9200/_prometheus/metrics" @@ -143,6 +145,7 @@ async def test_monitoring_user_fetch_prometheus_data(ops_test): @pytest.mark.abort_on_fail +@pytest.mark.group(1) async def test_prometheus_monitor_user_password_change(ops_test): # Password change applied as expected leader_id = await get_leader_unit_id(ops_test, APP_NAME) @@ -166,6 +169,7 @@ async def test_prometheus_monitor_user_password_change(ops_test): @pytest.mark.abort_on_fail +@pytest.mark.group(1) async def test_knn_enabled_disabled(ops_test): config = await ops_test.model.applications[APP_NAME].get_config() assert config["plugin_opensearch_knn"]["default"] is True @@ -185,6 +189,7 @@ async def test_knn_enabled_disabled(ops_test): await ops_test.model.wait_for_idle(apps=[APP_NAME], status="active", idle_period=45) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_knn_search_with_hnsw_faiss(ops_test: OpsTest) -> None: """Uploads data and runs a query search against the FAISS KNNEngine.""" @@ -228,6 +233,7 @@ async def test_knn_search_with_hnsw_faiss(ops_test: OpsTest) -> None: assert len(docs) == 2 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_knn_search_with_hnsw_nmslib(ops_test: OpsTest) -> None: """Uploads data and runs a query search against the NMSLIB KNNEngine.""" @@ -271,6 +277,7 @@ async def test_knn_search_with_hnsw_nmslib(ops_test: OpsTest) -> None: assert len(docs) == 2 +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_knn_training_search(ops_test: OpsTest) -> None: """Tests the entire cycle of KNN plugin. diff --git a/tests/integration/relations/__init__.py b/tests/integration/relations/__init__.py new file mode 100644 index 0000000000..db3bfe1a65 --- /dev/null +++ b/tests/integration/relations/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/relations/opensearch_provider/conftest.py b/tests/integration/relations/conftest.py similarity index 100% rename from tests/integration/relations/opensearch_provider/conftest.py rename to tests/integration/relations/conftest.py diff --git a/tests/integration/relations/opensearch_provider/helpers.py b/tests/integration/relations/helpers.py similarity index 100% rename from tests/integration/relations/opensearch_provider/helpers.py rename to tests/integration/relations/helpers.py diff --git a/tests/integration/relations/opensearch_provider/test_opensearch_provider.py b/tests/integration/relations/test_opensearch_provider.py similarity index 70% rename from tests/integration/relations/opensearch_provider/test_opensearch_provider.py rename to tests/integration/relations/test_opensearch_provider.py index 5f7b63b70d..722fab073a 100644 --- a/tests/integration/relations/opensearch_provider/test_opensearch_provider.py +++ b/tests/integration/relations/test_opensearch_provider.py @@ -11,17 +11,17 @@ from charms.opensearch.v0.constants_charm import ClientRelationName from pytest_operator.plugin import OpsTest -from tests.integration.helpers import APP_NAME as OPENSEARCH_APP_NAME -from tests.integration.helpers import ( +from ..helpers import APP_NAME as OPENSEARCH_APP_NAME +from ..helpers import ( MODEL_CONFIG, SERIES, get_application_unit_ids, get_leader_unit_ip, http_request, - scale_application, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.relations.opensearch_provider.helpers import ( +from ..helpers_deployments import wait_until +from ..tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .helpers import ( get_application_relation_data, ip_to_url, run_request, @@ -32,7 +32,6 @@ CLIENT_APP_NAME = "application" SECONDARY_CLIENT_APP_NAME = "secondary-application" -TLS_CERTIFICATES_APP_NAME = "self-signed-certificates" ALL_APPS = [OPENSEARCH_APP_NAME, TLS_CERTIFICATES_APP_NAME, CLIENT_APP_NAME] NUM_UNITS = 3 @@ -51,6 +50,7 @@ ] +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_create_relation(ops_test: OpsTest, application_charm, opensearch_charm): """Test basic functionality of relation interface.""" @@ -59,8 +59,10 @@ async def test_create_relation(ops_test: OpsTest, application_charm, opensearch_ new_model_conf = MODEL_CONFIG.copy() new_model_conf["update-status-hook-interval"] = "1m" + config = {"ca-common-name": "CN_CA"} + await ops_test.model.deploy(TLS_CERTIFICATES_APP_NAME, channel="stable", config=config) + await ops_test.model.set_config(new_model_conf) - tls_config = {"ca-common-name": "CN_CA"} await asyncio.gather( ops_test.model.deploy( application_charm, @@ -72,7 +74,6 @@ async def test_create_relation(ops_test: OpsTest, application_charm, opensearch_ num_units=NUM_UNITS, series=SERIES, ), - ops_test.model.deploy(TLS_CERTIFICATES_APP_NAME, channel="stable", config=tls_config), ) await ops_test.model.integrate(OPENSEARCH_APP_NAME, TLS_CERTIFICATES_APP_NAME) wait_for_relation_joined_between(ops_test, OPENSEARCH_APP_NAME, TLS_CERTIFICATES_APP_NAME) @@ -91,6 +92,7 @@ async def test_create_relation(ops_test: OpsTest, application_charm, opensearch_ ) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_index_usage(ops_test: OpsTest): """Check we can update and delete things. @@ -128,6 +130,7 @@ async def test_index_usage(ops_test: OpsTest): ) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_bulk_index_usage(ops_test: OpsTest): """Check we can update and delete things using bulk api.""" @@ -168,6 +171,7 @@ async def test_bulk_index_usage(ops_test: OpsTest): assert set(artists) == {"Herbie Hancock", "Lydian Collective", "Vulfpeck"} +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_version(ops_test: OpsTest): """Check version reported in the databag is consistent with the version on the charm.""" @@ -188,64 +192,6 @@ async def test_version(ops_test: OpsTest): assert version == results.get("version", {}).get("number"), results -@pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_without_juju_secrets") -async def test_scaling(ops_test: OpsTest): - """Test that scaling correctly updates endpoints in databag. - - scale_application also contains a wait_for_idle check, including checking for active status. - Idle_period checks must be greater than 1 minute to guarantee update_status fires correctly. - """ - - async def rel_endpoints(app_name: str, rel_name: str) -> str: - return await get_application_relation_data( - ops_test, f"{app_name}/0", rel_name, "endpoints" - ) - - async def get_num_of_endpoints(app_name: str, rel_name: str) -> int: - return len((await rel_endpoints(app_name, rel_name)).split(",")) - - def get_num_of_opensearch_units() -> int: - return len(ops_test.model.applications[OPENSEARCH_APP_NAME].units) - - # Test things are already working fine - assert ( - await get_num_of_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) - == get_num_of_opensearch_units() - ), await rel_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) - await ops_test.model.wait_for_idle( - status="active", apps=ALL_APPS, timeout=1600, idle_period=70 - ) - - # Test scale down - await scale_application( - ops_test, - OPENSEARCH_APP_NAME, - get_num_of_opensearch_units() - 1, - timeout=1600, - idle_period=70, - ) - await ops_test.model.wait_for_idle(status="active", apps=ALL_APPS) - assert ( - await get_num_of_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) - == get_num_of_opensearch_units() - ), await rel_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) - - # test scale back up again - await scale_application( - ops_test, - OPENSEARCH_APP_NAME, - get_num_of_opensearch_units() + 1, - timeout=1600, - idle_period=70, - ) - await ops_test.model.wait_for_idle(status="active", apps=ALL_APPS, timeout=1600) - assert ( - await get_num_of_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) - == get_num_of_opensearch_units() - ), await rel_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) - - async def get_secret_data(ops_test, secret_uri): secret_unique_id = secret_uri.split("/")[-1] complete_command = f"show-secret {secret_uri} --reveal --format=json" @@ -253,9 +199,9 @@ async def get_secret_data(ops_test, secret_uri): return json.loads(stdout)[secret_unique_id]["content"]["Data"] +@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_with_juju_secrets") -async def test_scaling_secrets(ops_test: OpsTest): +async def test_scaling(ops_test: OpsTest): """Test that scaling correctly updates endpoints in databag. scale_application also contains a wait_for_idle check, including checking for active status. @@ -317,6 +263,7 @@ async def _is_number_of_endpoints_valid(client_app: str, rel: str) -> bool: ), await rel_endpoints(CLIENT_APP_NAME, FIRST_RELATION_NAME) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_multiple_relations(ops_test: OpsTest, application_charm): """Test that two different applications can connect to the database.""" @@ -381,6 +328,7 @@ async def test_multiple_relations(ops_test: OpsTest, application_charm): assert "403 Client Error: Forbidden for url:" in results[0], results +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_multiple_relations_accessing_same_index(ops_test: OpsTest): """Test that two different applications can connect to the database.""" @@ -417,6 +365,7 @@ async def test_multiple_relations_accessing_same_index(ops_test: OpsTest): assert set(artists) == {"Herbie Hancock", "Lydian Collective", "Vulfpeck"} +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_admin_relation(ops_test: OpsTest): """Test we can create relations with admin permissions.""" @@ -452,8 +401,8 @@ async def test_admin_relation(ops_test: OpsTest): assert set(artists) == {"Herbie Hancock", "Lydian Collective", "Vulfpeck"} +@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_without_juju_secrets") async def test_admin_permissions(ops_test: OpsTest): """Test admin permissions behave the way we want. @@ -483,70 +432,6 @@ async def test_admin_permissions(ops_test: OpsTest): logging.info(results) assert "403 Client Error: Forbidden for url:" in results[0], results - # verify admin can't delete users - first_relation_user = await get_application_relation_data( - ops_test, f"{CLIENT_APP_NAME}/0", FIRST_RELATION_NAME, "username" - ) - first_relation_user_endpoint = f"/_plugins/_security/api/internalusers/{first_relation_user}" - run_delete_users = await run_request( - ops_test, - unit_name=test_unit.name, - endpoint=first_relation_user_endpoint, - method="DELETE", - relation_id=admin_relation.id, - relation_name=ADMIN_RELATION_NAME, - ) - results = json.loads(run_delete_users["results"]) - logging.info(results) - assert "403 Client Error: Forbidden for url:" in results[0], results - - # verify admin can't modify protected indices - for protected_index in PROTECTED_INDICES: - protected_index_endpoint = f"/{protected_index}" - run_remove_distro = await run_request( - ops_test, - unit_name=test_unit.name, - endpoint=protected_index_endpoint, - method="DELETE", - relation_id=admin_relation.id, - relation_name=ADMIN_RELATION_NAME, - ) - results = json.loads(run_remove_distro["results"]) - logging.info(results) - assert "Error:" in results[0], results - - -@pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_with_juju_secrets") -async def test_admin_permissions_secrets(ops_test: OpsTest): - """Test admin permissions behave the way we want. - - admin-only actions include: - - creating multiple indices - - removing indices they've created - - set cluster roles. - - verify that: - - we can't remove .opendistro_security index - - otherwise create client-admin-role - - verify neither admin nor default users can access user api - - otherwise create client-default-role - """ - test_unit = ops_test.model.applications[CLIENT_APP_NAME].units[0] - # Verify admin can't access security API - security_api_endpoint = "/_plugins/_security/api/internalusers" - run_dump_users = await run_request( - ops_test, - unit_name=test_unit.name, - endpoint=security_api_endpoint, - method="GET", - relation_id=admin_relation.id, - relation_name=ADMIN_RELATION_NAME, - ) - results = json.loads(run_dump_users["results"]) - logging.info(results) - assert "403 Client Error: Forbidden for url:" in results[0], results - # verify admin can't delete users secret_uri = await get_application_relation_data( ops_test, f"{CLIENT_APP_NAME}/0", FIRST_RELATION_NAME, "secret-user" @@ -584,8 +469,8 @@ async def test_admin_permissions_secrets(ops_test: OpsTest): assert "Error:" in results[0], results +@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_without_juju_secrets") async def test_normal_user_permissions(ops_test: OpsTest): """Test normal user permissions behave the way we want. @@ -609,64 +494,6 @@ async def test_normal_user_permissions(ops_test: OpsTest): logging.info(results) assert "403 Client Error: Forbidden for url:" in results[0], results - # verify normal users can't delete users - first_relation_user = await get_application_relation_data( - ops_test, f"{CLIENT_APP_NAME}/0", FIRST_RELATION_NAME, "username" - ) - first_relation_user_endpoint = f"/_plugins/_security/api/internalusers/{first_relation_user}" - run_delete_users = await run_request( - ops_test, - unit_name=test_unit.name, - endpoint=first_relation_user_endpoint, - method="DELETE", - relation_id=client_relation.id, - relation_name=FIRST_RELATION_NAME, - ) - results = json.loads(run_delete_users["results"]) - logging.info(results) - assert "403 Client Error: Forbidden for url:" in results[0], results - - # verify user can't modify protected indices - for protected_index in PROTECTED_INDICES: - protected_index_endpoint = f"/{protected_index}" - run_remove_index = await run_request( - ops_test, - unit_name=test_unit.name, - endpoint=protected_index_endpoint, - method="DELETE", - relation_id=client_relation.id, - relation_name=FIRST_RELATION_NAME, - ) - results = json.loads(run_remove_index["results"]) - logging.info(results) - assert "Error:" in results[0], results - - -@pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_with_juju_secrets") -async def test_normal_user_permissions_secrets(ops_test: OpsTest): - """Test normal user permissions behave the way we want. - - verify that: - - we can't remove .opendistro_security index - - verify neither admin nor default users can access user api - """ - test_unit = ops_test.model.applications[CLIENT_APP_NAME].units[0] - - # Verify normal users can't access security API - security_api_endpoint = "/_plugins/_security/api/internalusers" - run_dump_users = await run_request( - ops_test, - unit_name=test_unit.name, - endpoint=security_api_endpoint, - method="GET", - relation_id=client_relation.id, - relation_name=FIRST_RELATION_NAME, - ) - results = json.loads(run_dump_users["results"]) - logging.info(results) - assert "403 Client Error: Forbidden for url:" in results[0], results - # verify normal users can't delete users secret_uri = await get_application_relation_data( ops_test, f"{CLIENT_APP_NAME}/0", FIRST_RELATION_NAME, "secret-user" @@ -703,58 +530,9 @@ async def test_normal_user_permissions_secrets(ops_test: OpsTest): assert "Error:" in results[0], results +@pytest.mark.group(1) @pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_without_juju_secrets") async def test_relation_broken(ops_test: OpsTest): - """Test that the user is removed when the relation is broken.""" - # Retrieve the relation user. - relation_user = await get_application_relation_data( - ops_test, f"{CLIENT_APP_NAME}/0", FIRST_RELATION_NAME, "username" - ) - await ops_test.model.wait_for_idle( - status="active", - apps=[SECONDARY_CLIENT_APP_NAME] + ALL_APPS, - idle_period=70, - timeout=1600, - ) - - # Break the relation. - await asyncio.gather( - ops_test.model.applications[OPENSEARCH_APP_NAME].remove_relation( - f"{OPENSEARCH_APP_NAME}:{ClientRelationName}", - f"{CLIENT_APP_NAME}:{FIRST_RELATION_NAME}", - ), - ops_test.model.applications[OPENSEARCH_APP_NAME].remove_relation( - f"{OPENSEARCH_APP_NAME}:{ClientRelationName}", - f"{CLIENT_APP_NAME}:{ADMIN_RELATION_NAME}", - ), - ) - - await asyncio.gather( - ops_test.model.wait_for_idle(apps=[CLIENT_APP_NAME], status="blocked", idle_period=70), - ops_test.model.wait_for_idle( - apps=[OPENSEARCH_APP_NAME, TLS_CERTIFICATES_APP_NAME, SECONDARY_CLIENT_APP_NAME], - status="active", - idle_period=70, - timeout=1600, - ), - ) - - leader_ip = await get_leader_unit_ip(ops_test) - users = await http_request( - ops_test, - "GET", - f"https://{ip_to_url(leader_ip)}:9200/_plugins/_security/api/internalusers/", - verify=False, - ) - logger.info(relation_user) - logger.info(users) - assert relation_user not in users.keys() - - -@pytest.mark.abort_on_fail -@pytest.mark.usefixtures("only_with_juju_secrets") -async def test_relation_broken_secrets(ops_test: OpsTest): """Test that the user is removed when the relation is broken.""" # Retrieve the relation user. secret_uri = await get_application_relation_data( @@ -807,6 +585,7 @@ async def test_relation_broken_secrets(ops_test: OpsTest): assert relation_user not in users.keys() +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_data_persists_on_relation_rejoin(ops_test: OpsTest): """Verify that if we recreate a relation, we can access the same index.""" diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 401eb13d4b..ee28eacc72 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -7,7 +7,7 @@ import pytest from pytest_operator.plugin import OpsTest -from tests.integration.helpers import ( +from .helpers import ( APP_NAME, MODEL_CONFIG, SERIES, @@ -18,8 +18,8 @@ http_request, run_action, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.test_tls import TLS_CERTIFICATES_APP_NAME +from .helpers_deployments import wait_until +from .tls.test_tls import TLS_CERTIFICATES_APP_NAME logger = logging.getLogger(__name__) @@ -27,6 +27,7 @@ DEFAULT_NUM_UNITS = 2 +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy(ops_test: OpsTest) -> None: @@ -42,6 +43,7 @@ async def test_build_and_deploy(ops_test: OpsTest) -> None: await ops_test.model.wait_for_idle(wait_for_exact_units=DEFAULT_NUM_UNITS, timeout=1800) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_status(ops_test: OpsTest) -> None: """Verifies that the application and unit are active.""" @@ -54,6 +56,7 @@ async def test_status(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == DEFAULT_NUM_UNITS +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_actions_get_admin_password(ops_test: OpsTest) -> None: """Test the retrieval of admin secrets.""" @@ -64,14 +67,13 @@ async def test_actions_get_admin_password(ops_test: OpsTest) -> None: # Deploy TLS Certificates operator. config = {"ca-common-name": "CN_CA"} await ops_test.model.deploy(TLS_CERTIFICATES_APP_NAME, channel="stable", config=config) - await ops_test.model.wait_for_idle( - apps=[TLS_CERTIFICATES_APP_NAME], status="active", timeout=1000 - ) - # Relate it to OpenSearch to set up TLS. await ops_test.model.integrate(APP_NAME, TLS_CERTIFICATES_APP_NAME) await ops_test.model.wait_for_idle( - apps=[APP_NAME], status="active", timeout=1200, wait_for_exact_units=DEFAULT_NUM_UNITS + apps=[APP_NAME], + status="active", + timeout=1200, + wait_for_exact_units=DEFAULT_NUM_UNITS, ) leader_ip = await get_leader_unit_ip(ops_test) @@ -92,6 +94,7 @@ async def test_actions_get_admin_password(ops_test: OpsTest) -> None: assert result.status == "failed" +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_actions_rotate_admin_password(ops_test: OpsTest) -> None: """Test the rotation and change of admin password.""" diff --git a/tests/integration/tls/__init__.py b/tests/integration/tls/__init__.py new file mode 100644 index 0000000000..db3bfe1a65 --- /dev/null +++ b/tests/integration/tls/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/tls/helpers.py b/tests/integration/tls/helpers.py index 3c3542988c..ce8c184782 100644 --- a/tests/integration/tls/helpers.py +++ b/tests/integration/tls/helpers.py @@ -5,7 +5,7 @@ from pytest_operator.plugin import OpsTest from tenacity import retry, stop_after_attempt, wait_fixed, wait_random -from tests.integration.helpers import http_request +from ..helpers import http_request @retry( diff --git a/tests/integration/tls/test_tls.py b/tests/integration/tls/test_tls.py index fb12c4c125..234241b09e 100644 --- a/tests/integration/tls/test_tls.py +++ b/tests/integration/tls/test_tls.py @@ -7,7 +7,7 @@ import pytest from pytest_operator.plugin import OpsTest -from tests.integration.helpers import ( +from ..helpers import ( APP_NAME, MODEL_CONFIG, SERIES, @@ -17,17 +17,16 @@ get_application_unit_names, get_leader_unit_ip, ) -from tests.integration.helpers_deployments import wait_until -from tests.integration.tls.helpers import ( - check_security_index_initialised, - check_unit_tls_configured, -) +from ..helpers_deployments import wait_until +from ..tls.helpers import check_security_index_initialised, check_unit_tls_configured logger = logging.getLogger(__name__) + TLS_CERTIFICATES_APP_NAME = "self-signed-certificates" +@pytest.mark.group(1) @pytest.mark.abort_on_fail @pytest.mark.skip_if_deployed async def test_build_and_deploy_active(ops_test: OpsTest) -> None: @@ -58,6 +57,7 @@ async def test_build_and_deploy_active(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[APP_NAME].units) == len(UNIT_IDS) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_security_index_initialised(ops_test: OpsTest) -> None: """Test that the security index is well initialised.""" @@ -66,6 +66,7 @@ async def test_security_index_initialised(ops_test: OpsTest) -> None: assert await check_security_index_initialised(ops_test, leader_unit_ip) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_tls_configured(ops_test: OpsTest) -> None: """Test that TLS is enabled when relating to the TLS Certificates Operator.""" @@ -73,6 +74,7 @@ async def test_tls_configured(ops_test: OpsTest) -> None: assert await check_unit_tls_configured(ops_test, unit_ip, unit_name) +@pytest.mark.group(1) @pytest.mark.abort_on_fail async def test_cluster_formation_after_tls(ops_test: OpsTest) -> None: """Test that the cluster formation is successful after TLS setup.""" diff --git a/tests/unit/lib/test_opensearch_secrets.py b/tests/unit/lib/test_opensearch_secrets.py index 60fa011039..77f6cfda8e 100644 --- a/tests/unit/lib/test_opensearch_secrets.py +++ b/tests/unit/lib/test_opensearch_secrets.py @@ -3,7 +3,6 @@ from unittest.mock import MagicMock, patch -import pytest from charms.opensearch.v0.constants_charm import ClientRelationName, PeerRelationName from charms.opensearch.v0.constants_tls import CertType from charms.opensearch.v0.opensearch_base_charm import SERVICE_MANAGER @@ -17,7 +16,11 @@ from charm import OpenSearchOperatorCharm -@pytest.mark.usefixtures("only_with_juju_secrets") +class JujuVersionMock: + def has_secrets(self): + return True + + class TestOpenSearchSecrets(TestOpenSearchInternalData): """Ensuring that secrets interfaces and expected behavior are preserved. @@ -37,6 +40,8 @@ def setUp(self): self.secrets = self.charm.secrets self.store = self.charm.secrets + JujuVersion.from_environ = MagicMock(return_value=JujuVersionMock()) + self.peers_rel_id = self.harness.add_relation(PeerRelationName, self.charm.app.name) self.service_rel_id = self.harness.add_relation(SERVICE_MANAGER, self.charm.app.name) self.client_rel_id = self.harness.add_relation(ClientRelationName, "application") diff --git a/tox.ini b/tox.ini index 9b18274619..b1656961ba 100644 --- a/tox.ini +++ b/tox.ini @@ -1,147 +1,105 @@ + # Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. [tox] no_package = True -skip_missing_interpreters = True -env_list = format, lint, unit -labels = - # Don't run this group in parallel, or with --keep-models because it creates a lot of local VMs. - integration = {charm, tls, ha, ha-networking, ha-multi-clusters, large-deployments, client, h-scaling, ha-storage}-integration +env_list = lint, unit [vars] src_path = {tox_root}/src tests_path = {tox_root}/tests -lib_path = {tox_root}/lib/charms/opensearch/v0 -all_path = {[vars]src_path} {[vars]lib_path} {[vars]tests_path} +lib_path = {tox_root}/lib/charms/opensearch +all_path = {[vars]src_path} {[vars]tests_path} {[vars]lib_path} [testenv] set_env = - PYTHONPATH = {tox_root}:{tox_root}/lib:{[vars]src_path} - PYTHONBREAKPOINT=ipdb.set_trace + PYTHONPATH = {[vars]src_path}:{tox_root}/lib PY_COLORS=1 - LIBJUJU_VERSION_SPECIFIER={env:LIBJUJU_VERSION_SPECIFIER:==3.3.0.0} +allowlist_externals = + poetry +[testenv:build-{production,dev,wrapper}] +# Wrap `charmcraft pack` pass_env = - PYTHONPATH - CHARM_BUILD_DIR - MODEL_SETTINGS - LIBJUJU_VERSION_SPECIFIER + CI +allowlist_externals = + {[testenv]allowlist_externals} + charmcraft + charmcraftcache + mv +commands_pre = + poetry export --only main,charm-libs --output requirements.txt +commands = + build-production: charmcraft pack {posargs} + build-dev: charmcraftcache pack {posargs} +commands_post = + mv requirements.txt requirements-last-build.txt [testenv:format] description = Apply coding style standards to code -deps = - black - isort +commands_pre = + poetry install --only format commands = - isort {[vars]all_path} - black {[vars]all_path} + poetry lock --no-update + poetry run isort {[vars]all_path} + poetry run black {[vars]all_path} [testenv:lint] description = Check code against coding style standards -pass_env = - {[testenv]pass_env} -deps = - black - flake8 - flake8-docstrings - flake8-copyright - flake8-builtins - pyproject-flake8 - pep8-naming - isort - codespell +allowlist_externals = + {[testenv]allowlist_externals} + find +commands_pre = + poetry install --only lint commands = - codespell {[vars]lib_path} - codespell {tox_root} --skip {tox_root}/.git --skip {tox_root}/.tox \ - --skip {tox_root}/build --skip {tox_root}/lib --skip {tox_root}/venv \ - --skip {tox_root}/.mypy_cache --skip {tox_root}/icon.svg + poetry check --lock + poetry run codespell {[vars]all_path} # pflake8 wrapper supports config from pyproject.toml - pflake8 {[vars]all_path} - isort --check-only --diff {[vars]all_path} - black --check --diff {[vars]all_path} + # exclude the lib folder, that is copied from lib/charms/data_platform_libs/ + poetry run pflake8 --exclude '.git,__pycache__,.tox,build,dist,*.egg_info,venv,tests/integration/relations/opensearch_provider/application-charm/lib/charms/data_platform_libs/' {[vars]all_path} + poetry run isort --check-only --diff {[vars]all_path} + poetry run black --check --diff {[vars]all_path} + find {[vars]all_path} -type f \( -name "*.sh" -o -name "*.bash" \) -exec poetry run shellcheck --color=always \{\} + [testenv:unit] description = Run unit tests -deps = - pytest - pytest-asyncio - coverage[toml] - -r {tox_root}/requirements.txt +set_env = + {[testenv]set_env} +commands_pre = + poetry install --only main,charm-libs,unit commands = - coverage run --source={[vars]src_path} --source={[vars]lib_path} \ + poetry run coverage run --source={[vars]src_path},{[vars]lib_path} \ -m pytest -v --tb native -s {posargs} {[vars]tests_path}/unit - coverage report + poetry run coverage report + poetry run coverage xml -[testenv:{charm, tls, client, ha-base, h-scaling, ha-storage, ha-networking, ha-multi-clusters, large-deployments, plugins}-integration] +[testenv:integration] description = Run integration tests pass_env = - {[testenv]pass_env} CI - CI_PACKED_CHARMS - S3_ACCESS_KEY - S3_SECRET_KEY - S3_BUCKET - S3_REGION - S3_SERVER_URL - S3_CA_BUNDLE_PATH - # For AWS testing - AWS_ACCESS_KEY - AWS_SECRET_KEY - # For GCP testing - GCP_ACCESS_KEY - GCP_SECRET_KEY - # Generic secrets from CI: + GITHUB_OUTPUT + S3_INTEGRATOR_CHARMPATH SECRETS_FROM_GITHUB - TEST_NUM_APP_UNITS -deps = - # This applies to libjuju, not Juju. - juju{env:LIBJUJU_VERSION_SPECIFIER} - opensearch-py - pytest - pytest-asyncio - pytest-operator - -r {tox_root}/requirements.txt -commands = - charm: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/test_charm.py - tls: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/tls/test_tls.py - client: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/relations/opensearch_provider/test_opensearch_provider.py - ha-base: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_ha.py - # h-scaling must run on a machine with more than 2 cores - h-scaling: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_horizontal_scaling.py - ha-storage: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_storage.py - ha-networking: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_ha_networking.py - ha-multi-clusters: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_ha_multi_clusters.py - large-deployments: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_large_deployments.py - plugins: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/plugins/test_plugins.py +set_env = + {[testenv]set_env} + # Workaround for https://github.com/python-poetry/poetry/issues/6958 + POETRY_INSTALLER_PARALLEL = false +allowlist_externals = + {[testenv:build-wrapper]allowlist_externals} -[testenv:ha-backup-integration] -description = Run integration tests -pass_env = - {[testenv]pass_env} - CI - CI_PACKED_CHARMS - # For microceph testing - S3_ACCESS_KEY - S3_SECRET_KEY - S3_BUCKET - S3_REGION - S3_SERVER_URL - S3_CA_BUNDLE_PATH - # For AWS testing - AWS_ACCESS_KEY - AWS_SECRET_KEY - # For GCP testing - GCP_ACCESS_KEY - GCP_SECRET_KEY - TEST_NUM_APP_UNITS -deps = - # This applies to libjuju, not Juju. - juju{env:LIBJUJU_VERSION_SPECIFIER} - opensearch-py - pytest - pytest-asyncio - pytest-operator - -r {tox_root}/requirements.txt + # Set the testing host before starting the lxd cloud + sudo + sysctl + +commands_pre = + poetry install --only main,charm-libs,integration + + # Set the testing host before starting the lxd cloud + sudo sysctl -w vm.max_map_count=262144 vm.swappiness=0 net.ipv4.tcp_retries2=5 + + {[testenv:build-wrapper]commands_pre} commands = - ha-backup: pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tests_path}/integration/ha/test_backups.py + poetry run pytest -v --tb native --log-cli-level=INFO -s --ignore={[vars]tests_path}/unit/ {posargs} +commands_post = + {[testenv:build-wrapper]commands_post}