From fad3e0c45fab4946af2c59e8b05e45051cfb2259 Mon Sep 17 00:00:00 2001 From: AlexStroke <111361420+AlexStroke@users.noreply.github.com> Date: Thu, 23 May 2024 12:56:30 +0200 Subject: [PATCH] test: Add torii api tests (#4564) * test: add torii api tests Signed-off-by: alexstroke <111361420+astrokov7@users.noreply.github.com> * test: corrections for bug reports in github Signed-off-by: alexstroke <111361420+astrokov7@users.noreply.github.com> * test: optimize json for /scheme endpoint Signed-off-by: alexstroke <111361420+astrokov7@users.noreply.github.com> * test: add linters Signed-off-by: alexstroke <111361420+astrokov7@users.noreply.github.com> --------- Signed-off-by: alexstroke <111361420+astrokov7@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug.yml | 108 +++ .github/workflows/iroha2-dev-pr-static.yml | 28 +- .github/workflows/iroha2-dev-pr.yml | 13 +- Dockerfile.build | 4 - torii/pytests/README.md | 30 + .../get_configuration_response_schema.json | 17 + .../common/schemas/get_schema_response.json | 162 ++++ .../common/schemas/get_status_response.json | 37 + torii/pytests/common/settings.py | 15 + torii/pytests/poetry.lock | 807 ++++++++++++++++++ torii/pytests/pyproject.toml | 27 + torii/pytests/test/__init__.py | 5 + torii/pytests/test/api_version/conftest.py | 20 + .../test/api_version/test_api_version.py | 99 +++ torii/pytests/test/configuration/conftest.py | 26 + .../configuration/test_get_configuration.py | 119 +++ .../configuration/test_post_configuration.py | 49 ++ torii/pytests/test/conftest.py | 8 + torii/pytests/test/general/conftest.py | 1 + .../test/general/test_200_status_codes.py | 175 ++++ .../test/general/test_400_status_codes.py | 36 + torii/pytests/test/health/conftest.py | 20 + torii/pytests/test/health/test_health.py | 69 ++ torii/pytests/test/schema/conftest.py | 18 + torii/pytests/test/schema/test_schema.py | 76 ++ torii/pytests/test/status/conftest.py | 18 + torii/pytests/test/status/test_status.py | 74 ++ 27 files changed, 2050 insertions(+), 11 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 torii/pytests/README.md create mode 100644 torii/pytests/common/schemas/get_configuration_response_schema.json create mode 100644 torii/pytests/common/schemas/get_schema_response.json create mode 100644 torii/pytests/common/schemas/get_status_response.json create mode 100644 torii/pytests/common/settings.py create mode 100644 torii/pytests/poetry.lock create mode 100644 torii/pytests/pyproject.toml create mode 100644 torii/pytests/test/__init__.py create mode 100644 torii/pytests/test/api_version/conftest.py create mode 100644 torii/pytests/test/api_version/test_api_version.py create mode 100644 torii/pytests/test/configuration/conftest.py create mode 100644 torii/pytests/test/configuration/test_get_configuration.py create mode 100644 torii/pytests/test/configuration/test_post_configuration.py create mode 100644 torii/pytests/test/conftest.py create mode 100644 torii/pytests/test/general/conftest.py create mode 100644 torii/pytests/test/general/test_200_status_codes.py create mode 100644 torii/pytests/test/general/test_400_status_codes.py create mode 100644 torii/pytests/test/health/conftest.py create mode 100644 torii/pytests/test/health/test_health.py create mode 100644 torii/pytests/test/schema/conftest.py create mode 100644 torii/pytests/test/schema/test_schema.py create mode 100644 torii/pytests/test/status/conftest.py create mode 100644 torii/pytests/test/status/test_status.py diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 00000000000..62829b94c1e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,108 @@ +name: "\U0001F41E Bug report:" +description: Submit a bug you found in Iroha +title: "[BUG] " +labels: [ "Bug" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this form! You may refer to the [contributing guide](https://github.com/hyperledger/iroha/blob/main/CONTRIBUTING.md#reporting-bugs) for further details on filling bug reports. + + Please be aware that SDK issues belong to other repositories: + - JavaScript: [`iroha-javascript`](https://github.com/hyperledger/iroha-javascript) + - Java/Kotlin: [`iroha-java`](https://github.com/hyperledger/iroha-java) + - Python: [`iroha-python`](https://github.com/hyperledger/iroha-python) + - type: input + id: env + attributes: + label: OS and Environment + description: | + Which operating system did you use when you encountered the issue? + Did you build Iroha from the source code or pulled it from [Docker Hub](https://hub.docker.com/) or [Nix](https://search.nixos.org/packages) packages? + placeholder: Ubuntu, Docker Hub + validations: + required: true + - type: input + id: commit-hash + attributes: + label: GIT commit hash + description: | + What is the commit hash of your Iroha version? + You can use the `git rev-parse --short HEAD` command to retrieve it. + Note that older versions may have more bugs. + placeholder: 4936869d + validations: + required: true + - type: textarea + id: mwe + attributes: + label: Minimum working example / Steps to reproduce + description: | + Please share a minimal working code that allows us to reproduce the issue. + Make sure you enable [syntax highlighting](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks#syntax-highlighting). + value: | + ```rust + fn main() { + } + ``` + validations: + required: true + - type: textarea + id: actual-behaviour + attributes: + label: Actual result + description: What is the result or behaviour you got? + placeholder: | + I get an error message when running Iroha: + + Example error #123 + validations: + required: true + - type: textarea + id: expected-behaviour + attributes: + label: Expected result + description: What is the result or behaviour you expected to get? + placeholder: I expected Iroha to run normally on bare-metal after building Iroha. + validations: + required: true + - type: textarea + id: json-logs + attributes: + label: Logs + description: | + Provide an output log in JSON format, so we could determine what caused the issue faster. + To configure a file path and level for logs, check the [reference documentation](https://github.com/hyperledger/iroha/blob/iroha2-dev/docs/source/references/config.md#logger) or [peer configuration](https://hyperledger.github.io/iroha-2-docs/guide/configure/peer-configuration.html#logger). + **Please** leave JSON [syntax highlighting](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks#syntax-highlighting) and [collapsed sections](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections) (`
` tag) enabled. + If the log indentation is broken, use the [js-beautify](https://beautifier.io/) service to format it. + value: | +
+ Log contents + + ```json + Replace this text with a JSON log, + so it doesn't grow too large and has highlighting. + ``` +
+ validations: + required: true + - type: input + id: who-can-help + attributes: + label: Who can help to reproduce? + description: | + If you figure out the right person to tag, your issue might be resolved faster. + You can use `git blame` or check the following list of people **you can tag** based on what the issue is connected to: + + - Documentation issues: Ekaterina Mekhnetsova (`@outoftardis`) + - Quality Assurance (`@AlexStroke`) + - WASM: Marin Veršić (`@mversic`) + - Triggers: Daniil Polyakov (`@Arjentix`) + placeholder: "@Username ..." + - type: textarea + id: notes + attributes: + label: Notes + description: | + For example specify the type of LibC you're using (`GNU libc` or `musl libc`) and its version etc. Use the `ldd --version ldd` command to determine it. + placeholder: Any useful information diff --git a/.github/workflows/iroha2-dev-pr-static.yml b/.github/workflows/iroha2-dev-pr-static.yml index cc5910f8651..c987af863e1 100644 --- a/.github/workflows/iroha2-dev-pr-static.yml +++ b/.github/workflows/iroha2-dev-pr-static.yml @@ -40,19 +40,37 @@ jobs: image: hyperledger/iroha2-ci:nightly-2024-04-18 steps: - uses: actions/checkout@v4 - - name: Install dependencies using Poetry + - name: Install dependencies using Poetry for client_cli/pytests working-directory: client_cli/pytests run: | + poetry lock --no-update poetry install - - name: Check code formatting with Black + - name: Install dependencies using Poetry for torii/pytests + working-directory: torii/pytests + run: | + poetry lock --no-update + poetry install + - name: Check code formatting with Black in client_cli/pytests working-directory: client_cli/pytests run: | poetry run black --check . - - name: Run mypy (Type Checker) + - name: Check code formatting with Black in torii/pytests + working-directory: torii/pytests + run: | + poetry run black --check . + - name: Run mypy (Type Checker) in client_cli/pytests working-directory: client_cli/pytests run: | - poetry run mypy --explicit-package-bases . - - name: Run flake8 (Linter) + poetry run mypy --explicit-package-bases --ignore-missing-imports . + - name: Run mypy (Type Checker) in torii/pytests + working-directory: torii/pytests + run: | + poetry run mypy --explicit-package-bases --ignore-missing-imports . + - name: Run flake8 (Linter) in client_cli/pytests working-directory: client_cli/pytests run: | poetry run flake8 . --max-line-length=110 --ignore=F401,W503,E203 + - name: Run flake8 (Linter) in torii/pytests + working-directory: torii/pytests + run: | + poetry run flake8 . --max-line-length=110 --ignore=F401,W503,E203 diff --git a/.github/workflows/iroha2-dev-pr.yml b/.github/workflows/iroha2-dev-pr.yml index 22a9fc33032..ad69a5e1601 100644 --- a/.github/workflows/iroha2-dev-pr.yml +++ b/.github/workflows/iroha2-dev-pr.yml @@ -8,6 +8,7 @@ on: - '**.json' - '**.toml' - '**.lock' + - '**.py' - '.github/workflows/iroha2-dev-pr.yml' concurrency: @@ -94,7 +95,7 @@ jobs: # This context specification is required context: . - client-cli-tests: + torii-api-and-client-cli-tests: runs-on: [self-hosted, Linux, iroha2] container: image: hyperledger/iroha2-ci:nightly-2024-04-18 @@ -112,7 +113,15 @@ jobs: - name: Mark binaries as executable run: | chmod +x ${{ env.CLIENT_CLI_DIR }} - - name: Install dependencies using Poetry + - name: Install torii api dependencies using Poetry + working-directory: torii/pytests + run: | + poetry install + - name: Run torii api tests + working-directory: torii/pytests + run: | + poetry run pytest + - name: Install client cli dependencies using Poetry working-directory: client_cli/pytests run: | poetry install diff --git a/Dockerfile.build b/Dockerfile.build index 4f335d3869d..f745e682204 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -14,10 +14,6 @@ RUN pacman -Syu rustup mold musl rust-musl openssl libgit2 jq \ python python-pip --noconfirm --disable-download-timeout && \ curl -sSL https://install.python-poetry.org | python3 - -WORKDIR /client_cli/pytests -COPY /client_cli/pytests/pyproject.toml /client_cli/pytests/poetry.lock $WORKDIR -RUN poetry install - RUN rustup toolchain install nightly-2024-04-18-x86_64-unknown-linux-gnu RUN rustup default nightly-2024-04-18-x86_64-unknown-linux-gnu RUN rustup component add llvm-tools-preview clippy diff --git a/torii/pytests/README.md b/torii/pytests/README.md new file mode 100644 index 00000000000..fa2c1bc28da --- /dev/null +++ b/torii/pytests/README.md @@ -0,0 +1,30 @@ +# Torii API Testing Project + +This project is dedicated to automating the testing of the Torii API, which is part of the Iroha 2 blockchain framework. + +## Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. + +### Prerequisites + +- Python 3.11+ +- Poetry for Python package management + +### Installation + +First, use Poetry to install the dependencies: + +```bash +poetry install +``` + +This command will create a virtual environment and install all the necessary dependencies. + +### Running the Tests + +To run the automated tests, use the following command: + +```bash +poetry run pytest +``` diff --git a/torii/pytests/common/schemas/get_configuration_response_schema.json b/torii/pytests/common/schemas/get_configuration_response_schema.json new file mode 100644 index 00000000000..3e7ef8e95e5 --- /dev/null +++ b/torii/pytests/common/schemas/get_configuration_response_schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "logger": { + "type": "object", + "properties": { + "level": { + "type": "string", + "enum": ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"] + } + }, + "required": ["level"] + } + }, + "required": ["logger"] +} diff --git a/torii/pytests/common/schemas/get_schema_response.json b/torii/pytests/common/schemas/get_schema_response.json new file mode 100644 index 00000000000..81cd83a8027 --- /dev/null +++ b/torii/pytests/common/schemas/get_schema_response.json @@ -0,0 +1,162 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_]+$": { + "oneOf": [ + { "$ref": "#/definitions/UnitType" }, + { "$ref": "#/definitions/DirectAlias" }, + { "$ref": "#/definitions/MapDefinition" }, + { "$ref": "#/definitions/VecDefinition" }, + { "$ref": "#/definitions/OptionDefinition" }, + { "$ref": "#/definitions/NamedStructDefinition" }, + { "$ref": "#/definitions/EnumDefinition" }, + { "$ref": "#/definitions/ArrayDefinition" }, + { "$ref": "#/definitions/IntDefinition" }, + { "$ref": "#/definitions/FixedPointDefinition" }, + { "$ref": "#/definitions/TupleDef" }, + { "$ref": "#/definitions/BitmapDef" } + ] + } + }, + "definitions": { + "UnitType": { + "type": "null" + }, + "DirectAlias": { + "type": "string" + }, + "MapDefinition": { + "type": "object", + "properties": { + "Map": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "value": { "type": "string" } + }, + "required": ["key", "value"] + } + }, + "required": ["Map"] + }, + "TupleDef": { + "type": "object", + "properties": { + "Tuple": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["Tuple"] + }, + "VecDefinition": { + "type": "object", + "properties": { + "Vec": { "type": "string" } + }, + "required": ["Vec"] + }, + "ArrayDefinition": { + "type": "object", + "properties": { + "Array": { + "type": "object", + "properties": { + "len": { "type": "integer" }, + "type": { "type": "string" } + }, + "required": ["len", "type"] + } + }, + "required": ["Array"] + }, + "OptionDefinition": { + "type": "object", + "properties": { + "Option": { "type": "string" } + }, + "required": ["Option"] + }, + "NamedStructDefinition": { + "type": "object", + "properties": { + "Struct": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "type": "string" } + }, + "required": ["name", "type"] + } + } + }, + "required": ["Struct"] + }, + "EnumDefinition": { + "type": "object", + "properties": { + "Enum": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tag": { "type": "string" }, + "discriminant": { "type": "integer" }, + "type": { "type": "string" } + }, + "required": ["tag", "discriminant"] + } + } + }, + "required": ["Enum"] + }, + "IntDefinition": { + "type": "object", + "properties": { + "Int": { "type": "string" } + }, + "required": ["Int"] + }, + "FixedPointDefinition": { + "type": "object", + "properties": { + "FixedPoint": { + "type": "object", + "properties": { + "base": { "type": "string" }, + "decimal_places": { "type": "integer" } + }, + "required": ["base", "decimal_places"] + } + }, + "required": ["FixedPoint"] + }, + "BitmapDef": { + "type": "object", + "properties": { + "Bitmap": { + "type": "object", + "properties": { + "repr": { "type": "string" }, + "masks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "mask": { "type": "integer" } + }, + "required": ["name", "mask"] + } + } + }, + "required": ["repr", "masks"] + } + }, + "required": ["Bitmap"] + } + } +} \ No newline at end of file diff --git a/torii/pytests/common/schemas/get_status_response.json b/torii/pytests/common/schemas/get_status_response.json new file mode 100644 index 00000000000..efc5ccdccbc --- /dev/null +++ b/torii/pytests/common/schemas/get_status_response.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "peers": { + "type": "integer" + }, + "blocks": { + "type": "integer" + }, + "txs_accepted": { + "type": "integer" + }, + "txs_rejected": { + "type": "integer" + }, + "uptime": { + "type": "object", + "properties": { + "secs": { + "type": "integer" + }, + "nanos": { + "type": "integer" + } + }, + "required": ["secs", "nanos"] + }, + "view_changes": { + "type": "integer" + }, + "queue_size": { + "type": "integer" + } + }, + "required": ["peers", "blocks", "txs_accepted", "txs_rejected", "uptime", "view_changes", "queue_size"] +} diff --git a/torii/pytests/common/settings.py b/torii/pytests/common/settings.py new file mode 100644 index 00000000000..ed56a58eb51 --- /dev/null +++ b/torii/pytests/common/settings.py @@ -0,0 +1,15 @@ +import os +from dotenv import load_dotenv +import toml + +load_dotenv() + +BASE_DIR = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +) +CONFIG_DIR = os.path.join(BASE_DIR, "configs/swarm/client.toml") + +with open(CONFIG_DIR, "r") as file: + config = toml.load(file) +BASE_URL = config.get("torii_url", "http://127.0.0.1:8080").rstrip("/") +print(BASE_URL) diff --git a/torii/pytests/poetry.lock b/torii/pytests/poetry.lock new file mode 100644 index 00000000000..cf783246069 --- /dev/null +++ b/torii/pytests/poetry.lock @@ -0,0 +1,807 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "allure-pytest" +version = "2.13.3" +description = "Allure pytest integration" +optional = false +python-versions = "*" +files = [ + {file = "allure-pytest-2.13.3.tar.gz", hash = "sha256:59cae25239907b3b57f72bd66476d670ce73a6fd3c339f80ed3fac78b4aa32a5"}, + {file = "allure_pytest-2.13.3-py3-none-any.whl", hash = "sha256:48dead8667587f10d75ff94d89be9c1f948f605f266e1376e436f4b47aba2440"}, +] + +[package.dependencies] +allure-python-commons = "2.13.3" +pytest = ">=4.5.0" + +[[package]] +name = "allure-python-commons" +version = "2.13.3" +description = "('Contains the API for end users as well as helper functions and classes to build Allure adapters for Python test frameworks',)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "allure-python-commons-2.13.3.tar.gz", hash = "sha256:2a22c6071bd3700bb380bf5288d8a81697ba3e8e4918836aa4cc4b4b17a99a7e"}, + {file = "allure_python_commons-2.13.3-py3-none-any.whl", hash = "sha256:ee890a6ad6b820819ec11e702bb9fb64dd855de5e57925a2601ac66de65ef5f1"}, +] + +[package.dependencies] +attrs = ">=16.0.0" +pluggy = ">=0.4.0" + +[[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 = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[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 = "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 = "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 = "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 = "faker" +version = "24.2.0" +description = "Faker is a Python package that generates fake data for you." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Faker-24.2.0-py3-none-any.whl", hash = "sha256:dce4754921f9fa7e2003c26834093361b8f45072e0f46f172d6ca1234774ecd4"}, + {file = "Faker-24.2.0.tar.gz", hash = "sha256:87d5e7730426e7b36817921679c4eaf3d810cedb8c81194f47adc3df2122ca18"}, +] + +[package.dependencies] +python-dateutil = ">=2.4" + +[[package]] +name = "flake8" +version = "7.0.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, + {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.2.0,<3.3.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 = "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 = "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" +version = "1.10.0" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "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 = "platformdirs" +version = "4.2.2" +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.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[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)"] +type = ["mypy (>=1.8)"] + +[[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 = "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 = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "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_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {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.34.0" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.34.0-py3-none-any.whl", hash = "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4"}, + {file = "referencing-0.34.0.tar.gz", hash = "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844"}, +] + +[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 = "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 = "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 = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20240521" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20240521.tar.gz", hash = "sha256:c5c4a0ae95aad51f1bf6dae9eed04a78f7f2575d4b171da37b622e08b93eb5d3"}, + {file = "types_requests-2.32.0.20240521-py3-none-any.whl", hash = "sha256:ab728ba43ffb073db31f21202ecb97db8753ded4a9dc49cb480d8a5350c5c421"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "types-toml" +version = "0.10.8.20240310" +description = "Typing stubs for toml" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-toml-0.10.8.20240310.tar.gz", hash = "sha256:3d41501302972436a6b8b239c850b26689657e25281b48ff0ec06345b8830331"}, + {file = "types_toml-0.10.8.20240310-py3-none-any.whl", hash = "sha256:627b47775d25fa29977d9c70dc0cbab3f314f32c8d8d0c012f2ef5de7aaec05d"}, +] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "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"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "3023490c44b2dede59d17d476f19b19e3c06ff288a07e75176e89b006fd6b138" diff --git a/torii/pytests/pyproject.toml b/torii/pytests/pyproject.toml new file mode 100644 index 00000000000..dae400337db --- /dev/null +++ b/torii/pytests/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "iroha2-torii-api-tests" +version = "0.1.0" +description = "" +authors = ["alexstroke <111361420+astrokov7@users.noreply.github.com>"] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +requests = "^2.31.0" +pytest = "^8.0.2" +websocket-client = "^1.7.0" +allure-pytest = "^2.13.2" +python-dotenv = "^1.0.1" +faker = "^24.2.0" +jsonschema = "^4.21.1" +pyyaml = "^6.0.1" +toml = "^0.10.2" +black = "^24.4.2" +mypy = "^1.10.0" +flake8 = "^7.0.0" +types-toml = "^0.10.8.20240310" +types-requests = "^2.32.0.20240521" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/torii/pytests/test/__init__.py b/torii/pytests/test/__init__.py new file mode 100644 index 00000000000..f785a1191b9 --- /dev/null +++ b/torii/pytests/test/__init__.py @@ -0,0 +1,5 @@ +""" +This module provides access to fixtures for testing. +""" + +from .conftest import GIVEN_api_up_and_running diff --git a/torii/pytests/test/api_version/conftest.py b/torii/pytests/test/api_version/conftest.py new file mode 100644 index 00000000000..26895d3c099 --- /dev/null +++ b/torii/pytests/test/api_version/conftest.py @@ -0,0 +1,20 @@ +from test import GIVEN_api_up_and_running + +import allure +import pytest +import requests + + +from common.settings import BASE_URL + + +@pytest.fixture(scope="module") +def GIVEN_get_request_to_api_version_enpoint_is_sent(): + with allure.step("GIVEN GET request to /api_version is sent"): + return requests.get(f"{BASE_URL}/api_version") + + +@pytest.fixture(scope="module") +def GIVEN_get_request_with_unexpected_param_to_api_version_enpoint_is_sent(): + with allure.step("GIVEN GET request with unexpected param to /api_version is sent"): + return requests.get(f"{BASE_URL}/api_version", params={"unexpected": "param"}) diff --git a/torii/pytests/test/api_version/test_api_version.py b/torii/pytests/test/api_version/test_api_version.py new file mode 100644 index 00000000000..c21c6b2e957 --- /dev/null +++ b/torii/pytests/test/api_version/test_api_version.py @@ -0,0 +1,99 @@ +import re +import time + +import requests +import pytest +import allure + +from common.settings import BASE_URL + + +@pytest.fixture(scope="function", autouse=True) +def setup_api_version(): + allure.dynamic.label("endpoint", "/api_version") + allure.dynamic.label("method", "GET") + allure.dynamic.label("status_code", "200") + + +@allure.id("1036") +def test_api_version_responce_presence( + GIVEN_get_request_to_api_version_enpoint_is_sent, +): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_api_version_enpoint_is_sent + with allure.step("THEN the response should have a information"): + assert response is not None, "Response does not contain any information" + + +@allure.id("1032") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_api_version_response_pattern(GIVEN_get_request_to_api_version_enpoint_is_sent): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_api_version_enpoint_is_sent + with allure.step( + "THEN the version should be present and match the expected format" + ): + assert re.match(r"^\d+", response), "Version is missing or in incorrect format" + + +@allure.id("1026") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_api_version_request_with_unexpected_param( + GIVEN_get_request_with_unexpected_param_to_api_version_enpoint_is_sent, +): + with allure.step("WHEN I get the response"): + response = ( + GIVEN_get_request_with_unexpected_param_to_api_version_enpoint_is_sent + ) + with allure.step( + "THEN the version should be present and match the expected format" + ): + version = response.get("version") + assert version and re.match( + r"^\d+", version + ), "Version is missing or in incorrect format" + + +@allure.id("1033") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_api_version_response_content_type( + GIVEN_get_request_to_api_version_enpoint_is_sent, +): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_api_version_enpoint_is_sent + with allure.step("THEN the Content-Type should be text/plain; charset=utf-8"): + assert ( + response.headers["Content-Type"] == "text/plain; charset=utf-8" + ), "Content-Type is not text/plain; charset=utf-8" + + +@allure.id("1037") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_api_version_response_format(GIVEN_get_request_to_api_version_enpoint_is_sent): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_api_version_enpoint_is_sent + with allure.step("THEN the response should be plain text"): + assert response, "Response is not a valid plain text" + + +@allure.id("1031") +def test_api_version_response_time(): + start_time = time.time() + with allure.step("WHEN I send GET request to /api_version"): + requests.get(f"{BASE_URL}/api_version") + elapsed_time = time.time() - start_time + with allure.step("THEN the response time should be less than 100ms"): + assert ( + elapsed_time < 0.1 + ), f"Response time is {elapsed_time}s, which is longer than 100ms" + + +@allure.id("1024") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_api_version_response_content_length( + GIVEN_get_request_to_api_version_enpoint_is_sent, +): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_api_version_enpoint_is_sent + with allure.step("THEN the content length should be 1 byte"): + assert len(response.content) == 1, "Response content is 1 byte" diff --git a/torii/pytests/test/configuration/conftest.py b/torii/pytests/test/configuration/conftest.py new file mode 100644 index 00000000000..ac976c6b325 --- /dev/null +++ b/torii/pytests/test/configuration/conftest.py @@ -0,0 +1,26 @@ +import allure +import pytest +import requests + + +from common.settings import BASE_URL + + +@pytest.fixture(scope="module") +def GIVEN_get_request_to_configuration_endpoint_is_sent(): + with allure.step("GIVEN GET request to /configuration is sent"): + return requests.get(f"{BASE_URL}/configuration") + + +@pytest.fixture(scope="module") +def GIVEN_get_request_with_unexpected_param_to_configuration_enpoint_is_sent(): + with allure.step( + "GIVEN GET request with unexpected param to /configuration is sent" + ): + return requests.get(f"{BASE_URL}/configuration", params={"unexpected": "param"}) + + +@pytest.fixture(scope="module") +def GIVEN_post_request_to_configuration_endpoint_is_sent(): + with allure.step("GIVEN POST request to /configuration is sent"): + return requests.post(f"{BASE_URL}/configuration") diff --git a/torii/pytests/test/configuration/test_get_configuration.py b/torii/pytests/test/configuration/test_get_configuration.py new file mode 100644 index 00000000000..173c43471ff --- /dev/null +++ b/torii/pytests/test/configuration/test_get_configuration.py @@ -0,0 +1,119 @@ +import json +import time +from pathlib import Path + +import requests +import pytest +import allure +from jsonschema import validate +from jsonschema.exceptions import ValidationError + +from common.settings import BASE_URL + +valid_log_levels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"] + + +@pytest.fixture(scope="function", autouse=True) +def setup_configuration(): + allure.dynamic.label("endpoint", "/configuration") + allure.dynamic.label("method", "GET") + allure.dynamic.label("status_code", "200") + + +@allure.id("1329") +def test_configuration_response_json_format( + GIVEN_get_request_to_configuration_endpoint_is_sent, +): + with allure.step("WHEN I send GET request to /configuration"): + response = GIVEN_get_request_to_configuration_endpoint_is_sent + with allure.step("THEN the response should be in JSON format"): + assert response.json(), "Response is not a valid JSON object" + + +@allure.id("1388") +def test_configuration_request_with_unexpected_param( + GIVEN_get_request_with_unexpected_param_to_configuration_enpoint_is_sent, +): + with allure.step("WHEN I send GET request to /configuration"): + response = ( + GIVEN_get_request_with_unexpected_param_to_configuration_enpoint_is_sent + ) + with allure.step("THEN the response should be in JSON format"): + assert response.json(), "Response is not a valid JSON object" + + +@allure.id("1330") +def test_configuration_response_content_type( + GIVEN_get_request_to_configuration_endpoint_is_sent, +): + with allure.step("WHEN I send GET request to /configuration"): + response = GIVEN_get_request_to_configuration_endpoint_is_sent + with allure.step("THEN the Content-Type should be application/json"): + assert ( + response.headers["Content-Type"] == "application/json" + ), "Content-Type is not application/json" + + +@allure.id("1328") +def test_configuration_response_json_schema( + GIVEN_get_request_to_configuration_endpoint_is_sent, +): + schema_file_path = ( + Path(__file__).parents[2] + / "common" + / "schemas" + / "get_configuration_response_schema.json" + ) + with open(schema_file_path) as schema_file: + schema = json.load(schema_file) + + with allure.step("WHEN I send a GET request to /configuration"): + response = GIVEN_get_request_to_configuration_endpoint_is_sent.json() + + with allure.step("THEN the response JSON should match the expected schema"): + try: + validate(instance=response, schema=schema) + except ValidationError as ve: + assert False, f"Response JSON does not match the expected schema: {ve}" + + +@allure.id("1354") +def test_configuration_response_time(): + start_time = time.time() + with allure.step("WHEN I send GET request to /configuration"): + requests.get(f"{BASE_URL}/configuration") + elapsed_time = time.time() - start_time + with allure.step("THEN the response time should be less than 100ms"): + assert ( + elapsed_time < 0.1 + ), f"Response time is {elapsed_time}s, which is longer than 100ms" + + +@allure.id("1327") +def test_configuration_response_logger_level( + GIVEN_get_request_to_configuration_endpoint_is_sent, +): + + with allure.step("WHEN I send a GET request to /configuration"): + response = GIVEN_get_request_to_configuration_endpoint_is_sent.json() + + with allure.step("THEN the 'logger.level' should be one of the valid log levels"): + assert ( + response["logger"]["level"] in valid_log_levels + ), f"Logger level '{response['logger']['level']}' is not a valid log level" + + +@allure.id("1387") +def test_configuration_response_content_length( + GIVEN_get_request_to_configuration_endpoint_is_sent, +): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_configuration_endpoint_is_sent + + with allure.step( + "THEN the content length should be within the expected range 27-28 bytes" + ): + content_length = len(response.content) + assert ( + 27 <= content_length <= 28 + ), f"Response content length is {content_length} bytes" diff --git a/torii/pytests/test/configuration/test_post_configuration.py b/torii/pytests/test/configuration/test_post_configuration.py new file mode 100644 index 00000000000..f4e8281b036 --- /dev/null +++ b/torii/pytests/test/configuration/test_post_configuration.py @@ -0,0 +1,49 @@ +import json +import requests +import pytest +import allure +from jsonschema import validate +from jsonschema.exceptions import ValidationError + +from common.settings import BASE_URL + +valid_log_levels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"] + + +@pytest.fixture(scope="function", autouse=True) +def setup_configuration(): + allure.dynamic.label("endpoint", "/configuration") + allure.dynamic.label("method", "POST") + + +@allure.id("1552") +@allure.label("status_code", "400") +def test_post_configuration_invalid_data(): + with allure.step("WHEN I send POST request with invalid data to /configuration"): + response = requests.post( + f"{BASE_URL}/configuration", + data=json.dumps({"logger": {"level": "invalid"}}), + ) + + with allure.step("THEN the response status code should be a client error"): + assert ( + 400 == response.status_code + ), "Response status code is not a client or server error for invalid data" + + +@allure.label("status_code", "202") +@pytest.mark.parametrize("log_level", valid_log_levels) +def test_post_configuration_valid_logger_level(log_level): + with allure.step( + f"WHEN I send POST request to /configuration with logger level {log_level}" + ): + requests.post( + f"{BASE_URL}/configuration", + data=json.dumps({"logger": {"level": log_level}}), + ) + + with allure.step(f"THEN the log level should be {log_level}"): + get_response = requests.get(f"{BASE_URL}/configuration") + assert ( + get_response.json()["logger"]["level"] == log_level + ), f"Logger level '{get_response.json()['logger']['level']}' is not {log_level}" diff --git a/torii/pytests/test/conftest.py b/torii/pytests/test/conftest.py new file mode 100644 index 00000000000..4b67702ad9a --- /dev/null +++ b/torii/pytests/test/conftest.py @@ -0,0 +1,8 @@ +import allure +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def GIVEN_api_up_and_running(): + with allure.step("Given the API is up and running"): + pass diff --git a/torii/pytests/test/general/conftest.py b/torii/pytests/test/general/conftest.py new file mode 100644 index 00000000000..b20dcc25f4f --- /dev/null +++ b/torii/pytests/test/general/conftest.py @@ -0,0 +1 @@ +from test import GIVEN_api_up_and_running diff --git a/torii/pytests/test/general/test_200_status_codes.py b/torii/pytests/test/general/test_200_status_codes.py new file mode 100644 index 00000000000..72d118b69e8 --- /dev/null +++ b/torii/pytests/test/general/test_200_status_codes.py @@ -0,0 +1,175 @@ +import allure +import pytest +import requests +import json + +from common.settings import BASE_URL + + +@pytest.fixture(scope="function", autouse=True) +def status_codes_200(): + allure.dynamic.label("status_code", "200") + + +valid_log_levels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"] + + +@allure.id("1553") +@allure.label("endpoint", "/configuration") +@allure.label("method", "POST") +@pytest.mark.parametrize("log_level", valid_log_levels) +def test_post_configuration_logger_level(log_level): + with allure.step( + f"WHEN I send POST request to /configuration with logger level {log_level}" + ): + response = requests.post( + f"{BASE_URL}/configuration", + data=json.dumps({"logger": {"level": log_level}}), + ) + + with allure.step("THEN the response should be accepted"): + assert ( + response.status_code == 202 + ), f"Expected status code 202, but got {response.status_code}" + + +@allure.id("1097") +@allure.label("endpoint", "/api_version") +@allure.label("method", "GET") +def test_api_version_status_code_200(): + with allure.step("WHEN I send GET request to /api_version"): + response = requests.get(f"{BASE_URL}/api_version") + with allure.step("THEN the response status code should be 200"): + assert response.status_code == 200, "Status code is not 200 for /api_version" + + +@allure.id("1099") +@allure.label("endpoint", "/configuration") +@allure.label("method", "GET") +def test_configuration_status_code_200(): + with allure.step("WHEN I send GET request to /configuration"): + response = requests.get(f"{BASE_URL}/configuration") + with allure.step("THEN the response status code should be 200"): + assert response.status_code == 200, "Status code is not 200 for /configuration" + + +@allure.id("1090") +@allure.label("endpoint", "/health") +@allure.label("method", "GET") +def test_health_status_code_200(): + with allure.step("WHEN I send GET request to /health"): + response = requests.get(f"{BASE_URL}/health") + with allure.step("THEN the response status code should be 200"): + assert response.status_code == 200, "Status code is not 200 for /health" + + +@allure.id("1094") +@allure.label("endpoint", "/schema") +@allure.label("method", "GET") +def test_schema_status_code_200(): + with allure.step("WHEN I send GET request to /schema"): + response = requests.get(f"{BASE_URL}/schema") + with allure.step("THEN the response status code should be 200"): + assert response.status_code == 200, "Status code is not 200 for /schema" + + +@allure.id("1096") +@allure.label("endpoint", "/status") +@allure.label("method", "GET") +def test_status_status_code_200(): + with allure.step("WHEN I send GET request to /status"): + response = requests.get(f"{BASE_URL}/status") + with allure.step("THEN the response status code should be 200"): + assert response.status_code == 200, "Status code is not 200 for /status" + + +@allure.id("1098") +@allure.label("endpoint", "/metrics") +@allure.label("method", "GET") +def test_metrics_status_code_200(): + with allure.step("WHEN I send GET request to /metrics"): + response = requests.get(f"{BASE_URL}/metrics") + with allure.step("THEN the response status code should be 200"): + assert response.status_code == 200, "Status code is not 200 for /metrics" + + +@allure.id("1092") +@allure.label("endpoint", "/api_version") +@allure.label("method", "GET") +def test_api_version_status_code_200_with_unexpected_param(): + with allure.step( + "WHEN I send GET request to /api_version with an unexpected parameter" + ): + response = requests.get( + f"{BASE_URL}/api_version", params={"unexpected": "param"} + ) + with allure.step("THEN the response status code should be 200"): + assert ( + response.status_code == 200 + ), "Status code is not 200 for /api_version with unexpected parameter" + + +@allure.id("1099") +@allure.label("endpoint", "/configuration") +@allure.label("method", "GET") +def test_configuration_status_code_200_with_unexpected_param(): + with allure.step( + "WHEN I send GET request to /configuration with an unexpected parameter" + ): + response = requests.get( + f"{BASE_URL}/configuration", params={"unexpected": "param"} + ) + with allure.step("THEN the response status code should be 200"): + assert ( + response.status_code == 200 + ), "Status code is not 200 for /configuration with unexpected parameter" + + +@allure.id("1093") +@allure.label("endpoint", "/health") +@allure.label("method", "GET") +def test_health_status_code_200_with_unexpected_param(): + with allure.step("WHEN I send GET request to /health with an unexpected parameter"): + response = requests.get(f"{BASE_URL}/health", params={"unexpected": "param"}) + with allure.step("THEN the response status code should be 200"): + assert ( + response.status_code == 200 + ), "Status code is not 200 for /health with unexpected parameter" + + +@allure.id("1095") +@allure.label("endpoint", "/schema") +@allure.label("method", "GET") +def test_schema_status_code_200_with_unexpected_param(): + with allure.step("WHEN I send GET request to /schema with an unexpected parameter"): + response = requests.get(f"{BASE_URL}/schema", params={"unexpected": "param"}) + with allure.step("THEN the response status code should be 200"): + assert ( + response.status_code == 200 + ), "Status code is not 200 for /schema with unexpected parameter" + + +@allure.id("1100") +@allure.label("endpoint", "/status") +@allure.label("method", "GET") +def test_status_status_code_200_with_unexpected_param(): + with allure.step("WHEN I send GET request to /status with an unexpected parameter"): + response = requests.get(f"{BASE_URL}/status", params={"unexpected": "param"}) + with allure.step("THEN the response status code should be 200"): + assert ( + response.status_code == 200 + ), "Status code is not 200 for /status with unexpected parameter" + + +@allure.id("1101") +@allure.label("endpoint", "/metrics") +@allure.label("method", "GET") +def test_metrics_status_code_200_with_unexpected_param(): + with allure.step( + "WHEN I send GET request to /metrics with an unexpected parameter" + ): + response = requests.get(f"{BASE_URL}/metrics", params={"unexpected": "param"}) + with allure.step("THEN the response status code should be 200"): + assert ( + response.status_code == 200 + ), "Status code is not 200 for /metrics with unexpected parameter" diff --git a/torii/pytests/test/general/test_400_status_codes.py b/torii/pytests/test/general/test_400_status_codes.py new file mode 100644 index 00000000000..863e9dac276 --- /dev/null +++ b/torii/pytests/test/general/test_400_status_codes.py @@ -0,0 +1,36 @@ +import allure +import pytest +import requests + +from common.settings import BASE_URL + + +@pytest.fixture(scope="function", autouse=True) +def status_codes_400(): + allure.dynamic.label("endpoint", "general") + + +@allure.id("1255") +@allure.label("method", "GET") +@allure.label("status_code", "405") +def test_method_not_allowed(): + with allure.step("WHEN I send GET request to /method_not_allowed"): + response = requests.get(f"{BASE_URL}/method_not_allowed") + with allure.step("THEN the response status code should be 405"): + assert ( + response.status_code == 405 + ), "Status code is not 405 for /method_not_allowed" + + +@allure.id("1288") +@allure.label("method", "GET") +@allure.label("status_code", "414") +def test_request_uri_too_long(): + with allure.step("WHEN I send an oversized GET request to /metrics"): + response = requests.get( + f"{BASE_URL}/metrics", params={"long_param": "a" * 65515} + ) + with allure.step( + "THEN the response status code should be 414 (Request-URI Too Long)" + ): + assert response.status_code == 414, "Status code is not 414" diff --git a/torii/pytests/test/health/conftest.py b/torii/pytests/test/health/conftest.py new file mode 100644 index 00000000000..ea4fe940154 --- /dev/null +++ b/torii/pytests/test/health/conftest.py @@ -0,0 +1,20 @@ +from test import GIVEN_api_up_and_running + +import allure +import pytest +import requests + + +from common.settings import BASE_URL + + +@pytest.fixture(scope="module") +def GIVEN_get_request_to_health_endpoint_is_sent(): + with allure.step("GIVEN GET request to /health is sent"): + return requests.get(f"{BASE_URL}/health") + + +@pytest.fixture(scope="module") +def GIVEN_get_request_with_unexpected_param_to_health_enpoint_is_sent(): + with allure.step("GIVEN GET request with unexpected param to /health is sent"): + return requests.get(f"{BASE_URL}/health", params={"unexpected": "param"}) diff --git a/torii/pytests/test/health/test_health.py b/torii/pytests/test/health/test_health.py new file mode 100644 index 00000000000..587de8452e3 --- /dev/null +++ b/torii/pytests/test/health/test_health.py @@ -0,0 +1,69 @@ +import time + +import requests +import pytest +import allure + +from common.settings import BASE_URL + + +@pytest.fixture(scope="function", autouse=True) +def setup_health_check(): + allure.dynamic.label("endpoint", "/health") + allure.dynamic.label("method", "GET") + allure.dynamic.label("status_code", "200") + + +@allure.id("1035") +def test_health_status_presence(GIVEN_get_request_to_health_endpoint_is_sent): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_health_endpoint_is_sent + with allure.step("THEN the response should contain health status"): + assert response is not None, "Response does not contain any information" + + +@allure.id("1029") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_health_content_type(GIVEN_get_request_to_health_endpoint_is_sent): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_health_endpoint_is_sent + with allure.step("THEN the Content-Type should be text/plain; charset=utf-8"): + assert ( + response.headers["Content-Type"] == "text/plain; charset=utf-8" + ), "Content-Type is not text/plain; charset=utf-8" + + +@allure.id("1030") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_health_format_with_unexpected_param( + GIVEN_get_request_with_unexpected_param_to_health_enpoint_is_sent, +): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_with_unexpected_param_to_health_enpoint_is_sent + with allure.step( + "THEN the version should be present and match the expected format" + ): + assert response == "Healthy", "Healthy is missing or in incorrect format" + + +@allure.id("1028") +@pytest.mark.xfail(reason="https://github.com/hyperledger/iroha/issues/4218") +def test_health_response_format(GIVEN_get_request_to_health_enpoint_is_sent): + with allure.step("WHEN I get the response"): + response = GIVEN_get_request_to_health_enpoint_is_sent + with allure.step( + "THEN the version should be present and match the expected format" + ): + assert response == "Healthy", "Healthy is missing or in incorrect format" + + +@allure.id("1027") +def test_health_response_time(): + start_time = time.time() + with allure.step("WHEN I send GET request to /health"): + requests.get(f"{BASE_URL}/health") + elapsed_time = time.time() - start_time + with allure.step("THEN the response time should be less than 100ms"): + assert ( + elapsed_time < 0.1 + ), f"Response time is {elapsed_time}s, which is longer than 100ms" diff --git a/torii/pytests/test/schema/conftest.py b/torii/pytests/test/schema/conftest.py new file mode 100644 index 00000000000..27a82e6b434 --- /dev/null +++ b/torii/pytests/test/schema/conftest.py @@ -0,0 +1,18 @@ +import allure +import pytest +import requests + + +from common.settings import BASE_URL + + +@pytest.fixture(scope="module") +def GIVEN_get_request_to_schema_endpoint_is_sent(): + with allure.step("GIVEN GET request to /schema is sent"): + return requests.get(f"{BASE_URL}/schema") + + +@pytest.fixture(scope="module") +def GIVEN_get_request_with_unexpected_param_to_schema_enpoint_is_sent(): + with allure.step("GIVEN GET request with unexpected param to /schema is sent"): + return requests.get(f"{BASE_URL}/schema", params={"unexpected": "param"}) diff --git a/torii/pytests/test/schema/test_schema.py b/torii/pytests/test/schema/test_schema.py new file mode 100644 index 00000000000..e737a47060c --- /dev/null +++ b/torii/pytests/test/schema/test_schema.py @@ -0,0 +1,76 @@ +import json +import time +from pathlib import Path + +import requests +import pytest +import allure +from jsonschema import validate +from jsonschema.exceptions import ValidationError + +from common.settings import BASE_URL + + +@pytest.fixture(scope="function", autouse=True) +def setup_schema(): + allure.dynamic.label("endpoint", "/schema") + allure.dynamic.label("method", "GET") + allure.dynamic.label("status_code", "200") + + +@allure.id("1422") +def test_schema_response_json_format(GIVEN_get_request_to_schema_endpoint_is_sent): + with allure.step("WHEN I send GET request to /schema"): + response = GIVEN_get_request_to_schema_endpoint_is_sent + with allure.step("THEN the response should be in JSON format"): + assert response.json(), "Response is not a valid JSON object" + + +@allure.id("1420") +def test_schema_request_with_unexpected_param( + GIVEN_get_request_with_unexpected_param_to_schema_enpoint_is_sent, +): + with allure.step("WHEN I send GET request to /schema with unexpected param"): + response = GIVEN_get_request_with_unexpected_param_to_schema_enpoint_is_sent + with allure.step("THEN the response should be in JSON format"): + assert response.json(), "Response is not a valid JSON object" + + +@allure.id("1421") +def test_schema_response_content_type(GIVEN_get_request_to_schema_endpoint_is_sent): + with allure.step("WHEN I send GET request to /schema"): + response = GIVEN_get_request_to_schema_endpoint_is_sent + with allure.step("THEN the Content-Type should be application/json"): + assert ( + response.headers["Content-Type"] == "application/json" + ), "Content-Type is not application/json" + + +@allure.id("1424") +def test_schema_response_json_schema(GIVEN_get_request_to_schema_endpoint_is_sent): + schema_file_path = ( + Path(__file__).parents[2] / "common" / "schemas" / "get_schema_response.json" + ) + with open(schema_file_path) as schema_file: + schema = json.load(schema_file) + + with allure.step("WHEN I send a GET request to /schema"): + response = GIVEN_get_request_to_schema_endpoint_is_sent.json() + + with allure.step("THEN the response JSON should match the expected schema"): + try: + validate(instance=response, schema=schema) + except ValidationError as ve: + assert False, f"Response JSON does not match the expected schema: {ve}" + + +@allure.id("1423") +def test_schema_response_time(): + start_time = time.time() + with allure.step("WHEN I send GET request to /schema"): + requests.get(f"{BASE_URL}/schema") + elapsed_time = time.time() - start_time + with allure.step("THEN the response time should be less than 100ms"): + assert ( + elapsed_time < 0.1 + ), f"Response time is {elapsed_time}s, which is longer than 100ms" diff --git a/torii/pytests/test/status/conftest.py b/torii/pytests/test/status/conftest.py new file mode 100644 index 00000000000..b477261e064 --- /dev/null +++ b/torii/pytests/test/status/conftest.py @@ -0,0 +1,18 @@ +import allure +import pytest +import requests + + +from common.settings import BASE_URL + + +@pytest.fixture(scope="module") +def GIVEN_get_request_to_status_endpoint_is_sent(): + with allure.step("GIVEN GET request to /status is sent"): + return requests.get(f"{BASE_URL}/status") + + +@pytest.fixture(scope="module") +def GIVEN_get_request_with_unexpected_param_to_status_enpoint_is_sent(): + with allure.step("GIVEN GET request with unexpected param to /status is sent"): + return requests.get(f"{BASE_URL}/status", params={"unexpected": "param"}) diff --git a/torii/pytests/test/status/test_status.py b/torii/pytests/test/status/test_status.py new file mode 100644 index 00000000000..b6ec8182a99 --- /dev/null +++ b/torii/pytests/test/status/test_status.py @@ -0,0 +1,74 @@ +import json +import time +from pathlib import Path + +import requests +import pytest +import allure +from jsonschema import validate +from jsonschema.exceptions import ValidationError + +from common.settings import BASE_URL + + +@pytest.fixture(scope="function", autouse=True) +def setup_status(): + allure.dynamic.label("endpoint", "/status") + allure.dynamic.label("method", "GET") + allure.dynamic.label("status_code", "200") + + +@allure.id("1454") +def test_status_response_json_format(GIVEN_get_request_to_status_endpoint_is_sent): + with allure.step("WHEN I send GET request to /status"): + response = GIVEN_get_request_to_status_endpoint_is_sent + with allure.step("THEN the response should be in JSON format"): + assert response.json(), "Response is not a valid JSON object" + + +@allure.id("1453") +def test_status_request_with_unexpected_header( + GIVEN_get_request_with_unexpected_param_to_status_enpoint_is_sent, +): + with allure.step("WHEN I send GET request to /status with unexpected header"): + response = GIVEN_get_request_with_unexpected_param_to_status_enpoint_is_sent + with allure.step("THEN the response should be in JSON format"): + assert response.json(), "Response is not a valid JSON object" + + +@allure.id("1456") +def test_status_response_content_type(GIVEN_get_request_to_status_endpoint_is_sent): + with allure.step("WHEN I send GET request to /status"): + response = GIVEN_get_request_to_status_endpoint_is_sent + with allure.step("THEN the Content-Type should be application/json"): + assert ( + response.headers["Content-Type"] == "application/json" + ), "Content-Type is not application/json" + + +@allure.id("1457") +def test_status_response_json_schema(GIVEN_get_request_to_status_endpoint_is_sent): + schema_file_path = ( + Path(__file__).parents[2] / "common" / "schemas" / "get_status_response.json" + ) + with open(schema_file_path) as schema_file: + schema = json.load(schema_file) + with allure.step("WHEN I send a GET request to /status"): + response = GIVEN_get_request_to_status_endpoint_is_sent.json() + with allure.step("THEN the response JSON should match the expected schema"): + try: + validate(instance=response, schema=schema) + except ValidationError as ve: + assert False, f"Response JSON does not match the expected schema: {ve}" + + +@allure.id("1455") +def test_status_response_time(GIVEN_api_up_and_running): + start_time = time.time() + with allure.step("WHEN I send GET request to /status"): + requests.get(f"{BASE_URL}/status") + elapsed_time = time.time() - start_time + with allure.step("THEN the response time should be less than 100ms"): + assert ( + elapsed_time < 0.1 + ), f"Response time is {elapsed_time}s, which is longer than 100ms"