diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..0da48c28a --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: michaelboulton diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16ba3804e..53fddc11b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,18 +20,11 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.8" - - - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'requirements.in') }} - restore-keys: | - ${{ runner.os }}-pip- + python-version: "3.11" - uses: pre-commit/action@v3.0.0 - test: + unit-tests: runs-on: ubuntu-latest needs: simple-checks @@ -45,6 +38,49 @@ jobs: - TOXENV: py3 TOXCFG: tox.ini + env: + TOXENV: ${{ matrix.TOXENV }} + TOXCFG: ${{ matrix.TOXCFG }} + + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache@v3 + env: + cache-name: cache-${{ matrix.TOXENV }} + with: + path: .tox + key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'requirements.in') }} + + - uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml', 'requirements.in') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: install deps + run: | + pip install tox -c constraints.txt + + - name: tox + run: | + tox -c ${TOXCFG} -e ${TOXENV} + + integration-tests: + runs-on: ubuntu-latest + needs: unit-tests + + strategy: + fail-fast: false + matrix: + include: + # integration tests - TOXENV: py3-generic TOXCFG: tox-integration.ini - TOXENV: py3-mqtt @@ -65,6 +101,9 @@ jobs: TOXCFG: ${{ matrix.TOXCFG }} steps: + - uses: jpribyl/action-docker-layer-caching@v0.1.1 + continue-on-error: true + - uses: actions/checkout@v3 - uses: actions/cache@v3 @@ -72,7 +111,7 @@ jobs: cache-name: cache-${{ matrix.TOXENV }} with: path: .tox - key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('tox.ini', 'tox-integration.ini', 'pyproject.toml', 'requirements.in') }} + key: ${{ runner.os }}-tox-${{ env.cache-name }}-${{ hashFiles('pyproject.toml', 'requirements.in') }} - uses: actions/cache@v3 with: @@ -81,18 +120,15 @@ jobs: restore-keys: | ${{ runner.os }}-pip- - - uses: jpribyl/action-docker-layer-caching@v0.1.1 - continue-on-error: true - - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.8" + python-version: "3.11" - name: install deps run: | - pip install tox-travis -c constraints.txt + pip install tox -c constraints.txt - name: tox run: | - tox -c ${TOXCFG} + tox -c ${TOXCFG} -e ${TOXENV} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8f311706..b00190726 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,16 @@ repos: - repo: https://github.com/ambv/black - rev: 23.1.0 + rev: 23.7.0 hooks: - id: black files: "(tavern|tests)" - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.0.246" + rev: "v0.0.280" hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v3.0.0 hooks: - id: prettier types_or: [yaml] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..89d61f33e --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/source/conf.py + +python: + install: + - requirements: requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index d2bcd4864..794d86554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,8 +166,6 @@ calling run() directly will now cause a pytest isntance to be run in the backgro # 0.18.0 Add 'timeout' parameter for http requests (2018-08-24) -## show Bump version: 0.17.2 → 0.18.0 (2018-08-24) - ## 0.18.1 Add content type/encoding to uploaded files (2018-09-05) ## 0.18.2 Fix formatting environment variables in command line global config files (2018-09-21) @@ -261,8 +259,6 @@ The initial 2 hooks should allow a user to do something before every test and af ## 1.1.1 Travis fix (2020-05-23) -## travis-force Attempt to force travis to commit (2020-05-23) - ## 1.1.2 fforce new verison to make travis actually commit (2020-05-23) ## 1.1.3 travis (2020-05-23) @@ -408,3 +404,17 @@ This is technically not a operational change but I'm adding a new tag so it can ## 2.0.5 Attempt to fix deadlock in subscribe locks (2023-02-16) ## 2.0.6 Fix a few small MQTT issues (2023-03-13) + +## 2.0.7 Lock pytest to <7.3 to fix issue with marks (2023-04-15) + +# 2.1.0 Allow multi part file uploads with the same form field name (2023-06-04) + +# 2.2.0 Allow wildcards in MQTT topics (2023-06-25) + +## 2.2.1 Update some dependencies (2023-07-30) + +# 2.3.0 Add 'finally' block (2023-08-05) + +## 2.3.1 Fix error formatting when including files with curly braces (2023-09-18) + +# 2.4.0 Allow using an ext function to create a URL (2023-09-18) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b145d4ce9..f42a1331a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,10 @@ Run every so often to update the pre-commit hooks black tavern/ tests/ ruff --fix tavern/ tests/ +### Fix yaml formatting issues + + pre-commit run --all-files + ## Creating a new release 1. Setup `~/.pypirc` @@ -58,3 +62,10 @@ Run every so often to update the pre-commit hooks 1. Tag and push to git with `tbump --tag-message ""` 1. Upload to pypi with `flit publish` + +## Building the documentation + +```shell +mkdir -p dist/ +sphinx-build docs/source/ dist/ +``` \ No newline at end of file diff --git a/constraints.txt b/constraints.txt index ad6ce2f8f..9db20955f 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,9 +1,11 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --all-extras --output-file=constraints.txt --resolver=backtracking --strip-extras pyproject.toml +# pip-compile --all-extras --output-file=constraints.txt --strip-extras pyproject.toml # +alabaster==0.7.13 + # via sphinx allure-pytest==2.12.0 # via tavern (pyproject.toml) allure-python-commons==2.12.0 @@ -13,10 +15,8 @@ attrs==22.1.0 # allure-python-commons # jsonschema # pytest -bcrypt==4.0.1 - # via paramiko -black==23.1.0 - # via tavern (pyproject.toml) +babel==2.13.0 + # via sphinx bleach==5.0.1 # via readme-renderer build==0.9.0 @@ -24,52 +24,49 @@ build==0.9.0 bump2version==1.0.1 # via tavern (pyproject.toml) cachetools==5.3.0 - # via google-auth + # via + # google-auth + # tox certifi==2022.12.7 # via requests cffi==1.15.1 - # via - # cryptography - # pynacl + # via cryptography cfgv==3.3.1 # via pre-commit +chardet==5.2.0 + # via tox charset-normalizer==2.1.1 # via requests click==8.1.3 # via - # black # flask # pip-tools +colorama==0.4.6 + # via tox colorlog==6.7.0 # via tavern (pyproject.toml) commonmark==0.9.1 - # via rich + # via + # recommonmark + # rich + # tavern (pyproject.toml) coverage==7.0.0 # via # pytest-cov # tavern (pyproject.toml) cryptography==39.0.1 - # via - # paramiko - # secretstorage + # via secretstorage distlib==0.3.6 # via virtualenv -distro==1.8.0 - # via docker-compose -docker==6.0.1 - # via docker-compose -docker-compose==1.29.2 - # via tavern (pyproject.toml) -dockerpty==0.4.1 - # via docker-compose docopt==0.6.2 - # via - # docker-compose - # pykwalify + # via pykwalify docutils==0.19 # via # flit # readme-renderer + # recommonmark + # sphinx + # tavern (pyproject.toml) exceptiongroup==1.0.4 # via pytest execnet==1.9.0 @@ -80,7 +77,7 @@ filelock==3.8.2 # via # tox # virtualenv -flask==2.2.2 +flask==2.2.5 # via tavern (pyproject.toml) flit==3.8.0 # via tavern (pyproject.toml) @@ -126,9 +123,10 @@ identify==2.5.10 # via pre-commit idna==3.4 # via requests +imagesize==1.4.1 + # via sphinx importlib-metadata==5.2.0 # via - # flask # keyring # twine iniconfig==1.1.1 @@ -144,15 +142,17 @@ jeepney==0.8.0 # keyring # secretstorage jinja2==3.1.2 - # via flask + # via + # flask + # sphinx jmespath==1.0.1 # via tavern (pyproject.toml) jsonschema==3.2.0 - # via - # docker-compose - # tavern (pyproject.toml) + # via tavern (pyproject.toml) keyring==23.13.1 # via twine +markdown==3.5 + # via sphinx-markdown-tables markupsafe==2.1.1 # via # jinja2 @@ -165,24 +165,19 @@ mypy==0.991 # via tavern (pyproject.toml) mypy-extensions==0.4.3 # via - # black # mypy # tavern (pyproject.toml) nodeenv==1.7.0 # via pre-commit packaging==22.0 # via - # black # build - # docker + # pyproject-api # pytest + # sphinx # tox paho-mqtt==1.5.1 # via tavern (pyproject.toml) -paramiko==2.12.0 - # via docker -pathspec==0.10.3 - # via black pbr==5.11.0 # via stevedore pep517==0.13.0 @@ -193,7 +188,7 @@ pkginfo==1.9.2 # via twine platformdirs==2.6.0 # via - # black + # tox # virtualenv pluggy==1.0.0 # via @@ -214,9 +209,7 @@ protobuf==4.22.0 # proto-plus # tavern (pyproject.toml) py==1.11.0 - # via - # tavern (pyproject.toml) - # tox + # via tavern (pyproject.toml) pyasn1==0.4.8 # via # pyasn1-modules @@ -229,15 +222,16 @@ pygments==2.13.0 # via # readme-renderer # rich + # sphinx # tavern (pyproject.toml) pyjwt==2.6.0 # via tavern (pyproject.toml) pykwalify==1.8.0 # via tavern (pyproject.toml) -pynacl==1.5.0 - # via paramiko pyparsing==3.0.9 # via httplib2 +pyproject-api==1.5.0 + # via tox pyrsistent==0.19.2 # via jsonschema pytest==7.2.0 @@ -256,22 +250,20 @@ python-dateutil==2.8.2 # via # faker # pykwalify -python-dotenv==0.21.0 - # via docker-compose -pyyaml==5.4.1 +pyyaml==6.0.1 # via - # docker-compose # pre-commit # tavern (pyproject.toml) readme-renderer==37.3 # via twine +recommonmark==0.7.1 + # via tavern (pyproject.toml) requests==2.28.1 # via - # docker - # docker-compose # flit # google-api-core # requests-toolbelt + # sphinx # tavern (pyproject.toml) # twine requests-toolbelt==0.10.1 @@ -286,8 +278,6 @@ ruamel-yaml==0.17.21 # via pykwalify ruamel-yaml-clib==0.2.7 # via ruamel-yaml -ruff==0.0.246 - # via tavern (pyproject.toml) secretstorage==3.3.3 # via keyring six==1.16.0 @@ -295,50 +285,71 @@ six==1.16.0 # allure-pytest # allure-python-commons # bleach - # dockerpty # google-auth # google-auth-httplib2 # jsonschema - # paramiko # python-dateutil - # tox - # websocket-client +snowballstemmer==2.2.0 + # via sphinx +sphinx==7.1.2 + # via + # recommonmark + # sphinx-rtd-theme + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml + # tavern (pyproject.toml) +sphinx-markdown-tables==0.0.17 + # via tavern (pyproject.toml) +sphinx-rtd-theme==0.5.1 + # via tavern (pyproject.toml) +sphinxcontrib-applehelp==1.0.7 + # via sphinx +sphinxcontrib-devhelp==1.0.5 + # via sphinx +sphinxcontrib-htmlhelp==2.0.4 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==1.0.6 + # via sphinx +sphinxcontrib-serializinghtml==1.1.9 + # via sphinx stevedore==4.1.1 # via tavern (pyproject.toml) -texttable==1.6.7 - # via docker-compose toml==0.10.2 # via pre-commit tomli==2.0.1 # via - # black # build # coverage # mypy # pep517 + # pyproject-api # pytest # tox tomli-w==1.0.0 # via flit -tox==3.28.0 - # via - # tavern (pyproject.toml) - # tox-travis -tox-travis==0.12 +tox==4.1.1 # via tavern (pyproject.toml) twine==4.0.2 # via tavern (pyproject.toml) types-pyyaml==6.0.12.2 # via tavern (pyproject.toml) +types-requests==2.31.0.6 + # via tavern (pyproject.toml) +types-setuptools==68.2.0.0 + # via tavern (pyproject.toml) +types-urllib3==1.26.25.14 + # via types-requests typing-extensions==4.4.0 - # via - # black - # mypy + # via mypy uritemplate==4.1.1 # via google-api-python-client urllib3==1.26.13 # via - # docker # requests # twine virtualenv==20.17.1 @@ -347,10 +358,6 @@ virtualenv==20.17.1 # tox webencodings==0.5.1 # via bleach -websocket-client==0.59.0 - # via - # docker - # docker-compose werkzeug==2.2.3 # via flask wheel==0.38.4 diff --git a/docs/source/basics.md b/docs/source/basics.md index 11852ede6..e0c379349 100644 --- a/docs/source/basics.md +++ b/docs/source/basics.md @@ -52,17 +52,24 @@ just be to check that an endpoint returns a 401 with no login information. A more complicated one might be: 1. Log in to server - - `POST` login information in body - - Expect login details to be returned in body + +- `POST` login information in body +- Expect login details to be returned in body + 2. Get user information - - `GET` with login information in `Authorization` header - - Expect user information returned in body + +- `GET` with login information in `Authorization` header +- Expect user information returned in body + 3. Create a new resource with that user information - - `POST` with login information in `Authorization` header and user information in body - - Expect a 201 with the created resource in the body + +- `POST` with login information in `Authorization` header and user information in body +- Expect a 201 with the created resource in the body + 4. Make sure it's stored on the server - - `GET` with login information in `Authorization` header - - Expect the same information returned as in the previous step + +- `GET` with login information in `Authorization` header +- Expect the same information returned as in the previous step The **name** of each stage is a description of what is happening in that particular test. @@ -117,11 +124,14 @@ and lists recursively. If the response is: ```json { - "thing": { - "nested": [ - 1, 2, 3, 4 - ] - } + "thing": { + "nested": [ + 1, + 2, + 3, + 4 + ] + } } ``` @@ -148,7 +158,8 @@ response: nested_thing: "thing" ``` -This will save `{"nested": [1, 2, 3, 4]}` into the `nested_thing` variable. See the documentation for the `force_format_include` tag for how this can be used. +This will save `{"nested": [1, 2, 3, 4]}` into the `nested_thing` variable. See the documentation for +the `force_format_include` tag for how this can be used. **NOTE**: The behaviour of these queries used to be different and indexing into an array was done like `thing.nested.0`. This will be deprecated in the @@ -157,7 +168,8 @@ an array was done like `thing.nested.0`. This will be deprecated in the It is also possible to save data using function calls, [explained below](#saving-data-from-a-response). For a more formal definition of the schema that the tests are validated against, -check [tests schema](https://github.com/taverntesting/tavern/blob/master/tavern/schemas/tests.schema.yaml) in the main Tavern repository. +check [tests schema](https://github.com/taverntesting/tavern/blob/master/tavern/schemas/tests.schema.yaml) in the main +Tavern repository. ## Generating Test Reports @@ -167,7 +179,7 @@ to your Pip dependencies and pass the `--alluredir=` flag when running Tave a test report with the stages that were run, the responses, any fixtures used, and any errors. See the [Allure documentation](https://docs.qameta.io/allure/#_installing_a_commandline) for more -information on how to use it. +information on how to use it. ## Variable formatting @@ -361,14 +373,14 @@ response: type: seq required: True sequence: - - type: map - mapping: - user_number: - type: int - required: False - user_name: - type: str - required: True + - type: map + mapping: + user_number: + type: int + required: False + user_name: + type: str + required: True ``` If an external function you are using raises any exception, the test will be @@ -400,6 +412,7 @@ above that this function should _not_ take any arguments): # utils.py from box import Box + def generate_bearer_token(): token = sign_a_jwt() auth_header = { @@ -435,6 +448,7 @@ Input from external functions can be merged into a request instead by specifying def return_hello(): return {"hello": "there"} ``` + ```yaml request: url: "{host}/echo" @@ -445,14 +459,15 @@ def return_hello(): function: ext_functions:return_hello ``` -If `tavern-merge-ext-function-values` is set, this will send "hello" and "goodbye" in -the request. If not, it will just send "hello". +If `tavern-merge-ext-function-values` is set, this will send "hello" and "goodbye" in +the request. If not, it will just send "hello". Example `pytest.ini` setting `tavern-merge-ext-function-values` as an argument. + ```python # pytest.ini [pytest] -addopts = --tavern-merge-ext-function-values +addopts = --tavern - merge - ext - function - values ``` #### Saving data from a response @@ -466,10 +481,10 @@ Say that we have a server which returns a response like this: ```json { - "user": { - "name": "John Smith", - "id": "abcdef12345" - } + "user": { + "name": "John Smith", + "id": "abcdef12345" + } } ``` @@ -480,6 +495,7 @@ that this function should take the response object as the first argument): # utils.py from box import Box + def test_function(response): return Box({"test_user_name": response.json()["user"]["name"]}) ``` @@ -500,7 +516,8 @@ in later requests. #### A more complicated example For a more practical example, the built in `validate_jwt` function also returns the -decoded token as a dictionary wrapped in a [Box](https://pypi.python.org/pypi/python-box/) object, which allows dot-notation +decoded token as a dictionary wrapped in a [Box](https://pypi.python.org/pypi/python-box/) object, which allows +dot-notation access to members. This means that the contents of the token can be used for future requests. Because Tavern will already be in the Python path (because you installed it as a library) you do not need to modify the `PYTHONPATH`. @@ -602,12 +619,12 @@ response: The behaviour of various levels of 'strictness' based on the response: -| Response | strict=on | strict=off | -| ---- | -------- | ------ | -| `{ "first": 1, "second": { "nested": 2 } }` | PASS | PASS | -| `{ "first": 1 }` | FAIL | PASS | -| `{ "first": 1, "second": { "another": 2 } }` | FAIL | FAIL | -| `{ "first": 1, "second": { "nested": 2, "another": 2 } }` | FAIL | PASS | +| Response | strict=on | strict=off | +|-----------------------------------------------------------|-----------|------------| +| `{ "first": 1, "second": { "nested": 2 } }` | PASS | PASS | +| `{ "first": 1 }` | FAIL | PASS | +| `{ "first": 1, "second": { "another": 2 } }` | FAIL | FAIL | +| `{ "first": 1, "second": { "nested": 2, "another": 2 } }` | FAIL | PASS | Turning 'strict' off also means that extra items in lists will be ignored as long as the ones specified in the test response are present. For example, if the @@ -675,7 +692,7 @@ whichever configuration file Pytest is using. ```ini [pytest] -tavern-strict=json:off headers:on +tavern-strict = json:off headers:on ``` #### Per test @@ -966,8 +983,8 @@ stages: status_code: 200 json: location: - road: 123 Fake Street - country: England + road: 123 Fake Street + country: England --- test_name: Make sure giving premium works @@ -1006,7 +1023,6 @@ stages: has_premium: true ``` - ## Including external files Even with being able to use anchors within the same file, there is often some @@ -1057,9 +1073,8 @@ automatically be loaded and available for formatting as before. Multiple include files can be specified. The environment variable TAVERN_INCLUDE can contain a : separated list of -paths to search for include files. Each path in TAVERN_INCLUDE has -environment variables expanded before it is searched. - +paths to search for include files. Each path in TAVERN_INCLUDE has +environment variables expanded before it is searched. ### Including global configuration files @@ -1197,17 +1212,17 @@ might not work as expected: # pytest.ini [pytest] addopts = - # This will work +# This will work --tavern-global-cfg=integration_tests/local_urls.yaml - # This will not! - # --tavern-global-cfg integration_tests/local_urls.yaml +# This will not! +# --tavern-global-cfg integration_tests/local_urls.yaml ``` Instead, use the `tavern-global-cfg` option in your pytest.ini file: ```ini [pytest] -tavern-global-cfg= +tavern-global-cfg = integration_tests/local_urls.yaml ``` @@ -1226,10 +1241,11 @@ every test: $ tavern-ci --tavern-global-cfg common.yaml test_urls.yaml -- test_server.tavern.yaml $ py.test --tavern-global-cfg common.yaml local_docker_urls.yaml -- test_server.tavern.yaml ``` + ```ini # pytest.ini [pytest] -tavern-global-cfg= +tavern-global-cfg = common.yaml test_urls.yaml ``` @@ -1419,7 +1435,6 @@ This is also how things such as strict key checking is controlled via the An example of using `pytest_args` to exit on the first failure: - ```python from tavern.core import run @@ -1455,6 +1470,7 @@ This would match both of these response bodies: ```yaml returned_block: hello ``` + ```yaml returned_block: nested: value @@ -1495,7 +1511,7 @@ third block must start with 4 and the third block must start with 8, 9, "A", or ```yaml - name: Check that uuidv4 is returned request: - url: {host}/get_uuid/v4 + url: { host }/get_uuid/v4 method: GET response: status_code: 200 @@ -1517,31 +1533,31 @@ format `v1.2.3-510c2665d771e1`: ```yaml stages: -- name: get a token by id - request: - url: "{host}/tokens/get" - method: GET - params: - id: 456 - response: - status_code: 200 - json: - code: abc123 - id: 456 - meta: - version: !anystr - hash: 456 - save: - $ext: - function: tavern.helpers:validate_regex - extra_kwargs: - expression: "v(?P[\d\.]+)-[\w\d]+" - in_jmespath: "meta.version" + - name: get a token by id + request: + url: "{host}/tokens/get" + method: GET + params: + id: 456 + response: + status_code: 200 + json: + code: abc123 + id: 456 + meta: + version: !anystr + hash: 456 + save: + $ext: + function: tavern.helpers:validate_regex + extra_kwargs: + expression: "v(?P[\d\.]+)-[\w\d]+" + in_jmespath: "meta.version" ``` This is a more flexible version of the helper which can also be used to save values as in the example. If a named matching group is used as shown above, the saved values - can then be accessed in subsequent stages by using the `regex.` syntax, eg: +can then be accessed in subsequent stages by using the `regex.` syntax, eg: ```yaml - name: Reuse thing specified in first request @@ -1643,7 +1659,7 @@ could be done by response: status_code: 200 # Expect no users - json: [] + json: [ ] ``` Any blocks of JSON that are included this way will not be recursively formatted. @@ -2020,7 +2036,6 @@ variable_. Using the above example, perhaps we just want to test the server works correctly with the items "rotten apple", "fresh orange", and "unripe pear" rather than the 9 combinations listed above. This can be done like this: - ```yaml --- test_name: Test post a new fruit @@ -2031,9 +2046,9 @@ marks: - fruit - edible vals: - - [rotten, apple] - - [fresh, orange] - - [unripe, pear] + - [ rotten, apple ] + - [ fresh, orange ] + - [ unripe, pear ] # NOTE: we can specify a nested list like this as well: # - # - unripe @@ -2056,7 +2071,6 @@ This can be combined with the 'simpler' style of parametrisation as well - for example, to run the above test but also to specify whether the fruit was expensive or cheap: - ```yaml --- test_name: Test post a new fruit and price @@ -2067,9 +2081,9 @@ marks: - fruit - edible vals: - - [rotten, apple] - - [fresh, orange] - - [unripe, pear] + - [ rotten, apple ] + - [ fresh, orange ] + - [ unripe, pear ] - parametrize: key: price vals: @@ -2106,11 +2120,11 @@ test_name: Test sending a list of list of keys where one is not a string marks: - parametrize: key: - - fruit - - colours + - fruit + - colours vals: - - [ apple, [red, green, pink] ] - - [ pear, [yellow, green] ] + - [ apple, [ red, green, pink ] ] + - [ pear, [ yellow, green ] ] stages: - name: Send fruit and colours @@ -2142,28 +2156,28 @@ functions can be used to read values. For example this block will create 6 tests test_name: Test parametrizing random different data types in the same test marks: -- parametrize: - key: value_to_send - vals: - - a - - [b, c] - - more: stuff - - yet: [more, stuff] - - $ext: - function: ext_functions:return_string - - and: this - $ext: - function: ext_functions:return_dict - - # If 'return_dict' returns {"keys: ["a","b","c"]} this results in: - # { - # "and": "this", - # "keys": [ - # "a", - # "b", - # "c" - # ] - # } + - parametrize: + key: value_to_send + vals: + - a + - [ b, c ] + - more: stuff + - yet: [ more, stuff ] + - $ext: + function: ext_functions:return_string + - and: this + $ext: + function: ext_functions:return_dict + + # If 'return_dict' returns {"keys: ["a","b","c"]} this results in: + # { + # "and": "this", + # "keys": [ + # "a", + # "b", + # "c" + # ] + # } ``` As see in the last example, if the `$ext` function returns a dictionary then it will also be merged @@ -2196,6 +2210,7 @@ import pytest import logging import time + @pytest.fixture def server_password(): with open("/path/to/password/file", "r") as pfile: @@ -2203,6 +2218,7 @@ def server_password(): return password + @pytest.fixture(name="time_request") def fix_time_request(): t0 = time.time() @@ -2258,7 +2274,7 @@ There are some limitations on fixtures: Fixtures which are specified as `autouse` can also be used without explicitly using `usefixtures` in a test. This is a good way to essentially precompute a format variable without also having to use an external function or specify a -`usefixtures` block in every test where you need it. +`usefixtures` block in every test where you need it. To do this, just pass the `autouse=True` parameter to your fixtures along with the relevant scope. Using 'session' will evalute the fixture once at the beginning @@ -2313,6 +2329,7 @@ Example usage: ```python import logging + def pytest_tavern_beta_before_every_test_run(test_dict, variables): logging.info("Starting test %s", test_dict["test_name"]) @@ -2321,7 +2338,7 @@ def pytest_tavern_beta_before_every_test_run(test_dict, variables): ### After every test run -This hook is called _after_ execution of each test, regardless of the test +This hook is called _after_ execution of each test, regardless of the test result. The hook can, for example, be used to perform cleanup after the test is run. Example usage: @@ -2329,6 +2346,7 @@ Example usage: ```python import logging + def pytest_tavern_beta_after_every_test_run(test_dict, variables): logging.info("Ending test %s", test_dict["test_name"]) ``` @@ -2350,14 +2368,123 @@ def pytest_tavern_beta_after_every_response(expected, response): ### Before every request This hook is called just before each request with the arguments passed to the request -"function". By default, this is Session.request (from requests) for HTTP and Client.publish -(from paho-mqtt) for MQTT. +"function". By default, this is Session.request (from requests) for HTTP and Client.publish +(from paho-mqtt) for MQTT. Example usage: ```python import logging + def pytest_tavern_beta_before_every_request(request_args): logging.info("Making request: %s", request_args) ``` + +## Tinctures + +Another way of running functions at certain times is to use the 'tinctures' functionality: + +```python +# package/helpers.py + +import logging +import time + +logger = logging.getLogger(__name__) + + +def time_request(stage): + t0 = time.time() + yield + t1 = time.time() + logger.info("Request for stage %s took %s", stage, t1 - t0) + + +def print_response(_, extra_print="affa"): + logger.info("STARTING:") + (expected, response) = yield + logger.info("Response is %s (%s)", response, extra_print) +``` + +```yaml +--- +test_name: Test tincture + +tinctures: + - function: package.helpers:time_request + +stages: + - name: Make a request + tinctures: + - function: package.helpers:print_response + extra_kwargs: + extra_print: "blooble" + request: + url: "{host}/echo" + method: POST + json: + value: "one" + + - name: Make another request + request: + url: "{host}/echo" + method: POST + json: + value: "two" +``` + +Tinctures can be specified on a per-stage level or a per-test level. When specified on the test level, the tincture is +run for every stage in the test. In the above example, the `time_request` function will be run for both stages, but +the 'print_response' function will only be run for the first stage. + +Tinctures are _similar_ to fixtures but are more similar to [external functions](#calling-external-functions). Tincture +functions do not need to be annotated with a function like Pytest fixtures, and are referred to in the same +way (`path.to.package:function`), and have arguments passed to them in the same way (`extra_kwargs`, `extra_args`) as +external functions. + +The first argument to a tincture is always a dictionary of the stage to be run. + +If a tincture has a `yield` in the middle of it, during the `yield` the stage itself will be run. If a return value is +expected from the `yield` (eg `(expected, response) = yield` in the example above) then the _expected_ return values and +the response object from the stage will be returned. This allows a tincture to introspect the response, and compare it +against the expected, the same as the `pytest_tavern_beta_after_every_response` [hook](#after-every-response). This +response object will be different for MQTT and HTTP tests! + +If you need to run something before _every_ stage or after _every_ response in your test suite, look at using +the [hooks](#hooks) instead. + +## Finalising stages + +If you need a stage to run after a test runs, whether it passes or fails (for example, to log out of a service or +invalidate a short-lived auth token) you can use the `finally` block: + +```yaml +--- +test_name: Test finally block doing nothing + +stages: + - name: stage 1 + ... + + - name: stage 2 + ... + + - name: stage 3 + ... + +finally: + - name: clean up + request: + url: "{global_host}/cleanup" + method: POST +``` + +The `finally` block accepts a list of stages which will always be run after the rest of the test finishes, whether it +passed or failed. Each stage in run in order - if one of the `finally` stages fails, the rest will not be run. + +In the above example, if "stage 2" fails then the execution order would be: + +- stage 1 +- stage 2 (fails) +- clean up diff --git a/docs/source/conf.py b/docs/source/conf.py index 7ae58bfc3..be8ed3dd4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,14 +73,14 @@ # The short X.Y version. version = "1.0" # The full version, including alpha/beta/rc tags. -release = "2.0.7" +release = "2.5.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. diff --git a/docs/source/cookbook.md b/docs/source/cookbook.md index a48d9d9bb..c6929a6d4 100644 --- a/docs/source/cookbook.md +++ b/docs/source/cookbook.md @@ -30,7 +30,7 @@ RUN pip3 install tavern Build with: -```shell script +```shell docker build --file tavern.Dockerfile --tag tavern:latest . ``` @@ -44,7 +44,7 @@ ARG TAVERNVER RUN pip3 install tavern==$TAVERNVER ``` -```shell script +```shell export TAVERNVER=0.24.0 docker build --build-arg TAVERNVER=$TAVERNVER --file tavern.Dockerfile --tag tavern:$TAVERNVER . ``` @@ -56,7 +56,7 @@ does not take an incredibly long time to start up - see the documentation for information on how to create one. This can be used by running it on the command line with `docker run`, but -it is often easier to use it in a docker-compose file like this: +it is often easier to use it in a docker compose file like this: ```yaml --- diff --git a/docs/source/http.md b/docs/source/http.md index d73fa77ca..b0d5c60fb 100644 --- a/docs/source/http.md +++ b/docs/source/http.md @@ -171,7 +171,7 @@ stages: request: url: "{host}/expect_cookie" method: GET - cookies: [] + cookies: [ ] response: status_code: 403 json: diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt deleted file mode 100644 index 6144b1577..000000000 --- a/docs/source/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -sphinx==1.8.3 -sphinx_rtd_theme -recommonmark==0.5.0 -commonmark==0.8.1 -docutils==0.14 -pygments==2.7.4 -sphinx-markdown-tables==0.0.9 diff --git a/example/advanced/Dockerfile b/example/advanced/Dockerfile index 490cc8ddc..09ad42407 100644 --- a/example/advanced/Dockerfile +++ b/example/advanced/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.10-alpine -RUN pip3 install pyjwt~=2.4.0 flask~=2.0.3 +RUN pip3 install 'pyjwt>=2.4.0,<3' 'flask>=2.2.3' ENV FLASK_DEBUG=1 ENV PYTHONUNBUFFERED=0 diff --git a/example/advanced/server.py b/example/advanced/server.py index c9b399a72..726cd99e2 100644 --- a/example/advanced/server.py +++ b/example/advanced/server.py @@ -1,9 +1,10 @@ -import sqlite3 +import contextlib import datetime import functools -from flask import Flask, jsonify, request, g -import jwt +import sqlite3 +import jwt +from flask import Flask, g, jsonify, request app = Flask(__name__) @@ -19,12 +20,11 @@ def get_db(): db = g._database = sqlite3.connect(DATABASE) with db: - try: + with contextlib.suppress(Exception): db.execute( "CREATE TABLE numbers_table (name TEXT NOT NULL, number INTEGER NOT NULL)" ) - except Exception: - pass + return db diff --git a/example/components/server.py b/example/components/server.py index b7792c456..3bbc59bc1 100644 --- a/example/components/server.py +++ b/example/components/server.py @@ -1,8 +1,8 @@ import datetime import functools -from flask import Flask, jsonify, request import jwt +from flask import Flask, jsonify, request app = Flask(__name__) diff --git a/example/cookies/server.py b/example/cookies/server.py index e7629a062..9026f1381 100644 --- a/example/cookies/server.py +++ b/example/cookies/server.py @@ -1,7 +1,8 @@ +import contextlib import functools import sqlite3 -from flask import Flask, jsonify, request, g, session +from flask import Flask, g, jsonify, request, session app = Flask(__name__) app.secret_key = "t1uNraxw+9oxUyCuXHO2G0u38ig=" @@ -17,12 +18,11 @@ def get_db(): db = g._database = sqlite3.connect(DATABASE) with db: - try: + with contextlib.suppress(Exception): db.execute( "CREATE TABLE numbers_table (name TEXT NOT NULL, number INTEGER NOT NULL)" ) - except: - pass + return db diff --git a/example/generate_from_openapi/pub_tavern.py b/example/generate_from_openapi/pub_tavern.py index 371a82841..33fd60f94 100644 --- a/example/generate_from_openapi/pub_tavern.py +++ b/example/generate_from_openapi/pub_tavern.py @@ -1,7 +1,8 @@ import sys from urllib.parse import urlparse -from coreapi import Client + import yaml +from coreapi import Client def generate_tavern_yaml(json_path): @@ -68,7 +69,7 @@ def display_help(): print( "eg: pub_tavern.py https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/petstore-simple.json" ) - exit(-1) + sys.exit(-1) if __name__ == "__main__": diff --git a/example/hooks/Dockerfile b/example/hooks/Dockerfile index 68b46a7f7..edd2733d2 100644 --- a/example/hooks/Dockerfile +++ b/example/hooks/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.10-alpine -RUN pip3 install pyjwt~=2.4.0 flask~=2.0.3 +RUN pip3 install 'pyjwt>=2.4.0,<3' 'flask>=2.2.3' ENV FLASK_DEBUG=1 ENV PYTHONUNBUFFERED=0 diff --git a/example/hooks/conftest.py b/example/hooks/conftest.py index 76164c7b7..bac833490 100644 --- a/example/hooks/conftest.py +++ b/example/hooks/conftest.py @@ -1,6 +1,6 @@ +import logging import os import tempfile -import logging import pytest diff --git a/example/hooks/server.py b/example/hooks/server.py index 9f965f9b1..91952128f 100644 --- a/example/hooks/server.py +++ b/example/hooks/server.py @@ -1,6 +1,5 @@ from flask import Flask, jsonify, request - app = Flask(__name__) diff --git a/example/mqtt/README.md b/example/mqtt/README.md index 59d75915e..99d319f86 100644 --- a/example/mqtt/README.md +++ b/example/mqtt/README.md @@ -13,6 +13,6 @@ The server queries this database when a `GET` request is made to `/get_device_state` and returns whether the lights are on or off. The tavern test file includes examples of how to test such a setup, using the -keys `mqtt_publish` and `mqtt_response`. Run `docker-compose up --build` in one +keys `mqtt_publish` and `mqtt_response`. Run `docker compose up --build` in one terminal and run `py.test` in another terminal, and output from the mosquitto MQTT broker, the server, and the listener will be shown inline. diff --git a/example/mqtt/conftest.py b/example/mqtt/conftest.py index cabeddffa..d75e282a2 100644 --- a/example/mqtt/conftest.py +++ b/example/mqtt/conftest.py @@ -1,4 +1,3 @@ -import datetime import logging import logging.config import random @@ -51,7 +50,7 @@ def setup_logging(): - stderr level: DEBUG propagate: False - tavern: + tavern: <<: *log tavern.mqtt: &reduced_log diff --git a/example/mqtt/docker-compose.yaml b/example/mqtt/docker-compose.yaml index 97426b613..55d872050 100644 --- a/example/mqtt/docker-compose.yaml +++ b/example/mqtt/docker-compose.yaml @@ -44,7 +44,7 @@ services: read_only: true fluent: - image: fluent/fluentd + image: fluent/fluentd:v1.16 ports: - "24224:24224" volumes: diff --git a/example/mqtt/listener.py b/example/mqtt/listener.py index 756b4735a..f641831a5 100644 --- a/example/mqtt/listener.py +++ b/example/mqtt/listener.py @@ -3,7 +3,6 @@ import logging.config import os import sqlite3 -import time import paho.mqtt.client as paho import yaml diff --git a/example/mqtt/server.py b/example/mqtt/server.py index 55210119c..bc73c910d 100644 --- a/example/mqtt/server.py +++ b/example/mqtt/server.py @@ -42,7 +42,6 @@ def get_cached_db(): return db -@app.before_first_request def setup_logging(): log_cfg = """ version: 1 @@ -194,10 +193,9 @@ def _reset_db(db): with db: def attempt(query): - try: + with contextlib.suppress(Exception): db.execute(query) - except: - pass + attempt("DELETE FROM devices_table") attempt( @@ -208,5 +206,6 @@ def attempt(query): if __name__ == "__main__": + setup_logging() db = get_db() _reset_db(db) diff --git a/example/mqtt/test_mqtt.tavern.yaml b/example/mqtt/test_mqtt.tavern.yaml index f3b6929a0..b3d57b31f 100644 --- a/example/mqtt/test_mqtt.tavern.yaml +++ b/example/mqtt/test_mqtt.tavern.yaml @@ -68,6 +68,31 @@ stages: --- +test_name: Test mqtt wildcard subscription + +includes: + - !include common.yaml + + +paho-mqtt: *mqtt_spec + +stages: + - *setup_device_for_test + + - name: Echo json + mqtt_publish: + topic: /device/{random_device_id}/echo + json: + message: hello world + mqtt_response: + topic: /device/+/echo/response + json: + message: hello world + timeout: 5 + qos: 1 + +--- + test_name: Test mqtt message echo json formatted topic name marks: diff --git a/example/simple/server.py b/example/simple/server.py index 9f965f9b1..91952128f 100644 --- a/example/simple/server.py +++ b/example/simple/server.py @@ -1,6 +1,5 @@ from flask import Flask, jsonify, request - app = Flask(__name__) diff --git a/pyproject.toml b/pyproject.toml index 93c32d118..81ff4f964 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,14 +21,14 @@ keywords = ["testing", "pytest"] name = "tavern" description = "Simple testing of RESTful APIs" -version = "2.0.7" +version = "2.5.0" dependencies = [ - "PyYAML>=5.3.1,<7", + "PyYAML>=6.0.1,<7", "jmespath>=1,<2", "jsonschema>=3.2.0,<5", "paho-mqtt>=1.3.1,<=1.6.1", - "pyjwt>=2.4.0,<3", + "pyjwt>=2.5.0,<3", "pykwalify>=1.8.0,<2", "pytest>=7,<7.3", "python-box>=6,<7", @@ -64,29 +64,34 @@ Source = "https://github.com/taverntesting/tavern" dev = [ "Faker", "allure-pytest", - "black==23.1.0", "bump2version", "colorlog", - "docker-compose", - "flask", + "flask>=2.2.3", "fluent-logger", "itsdangerous", "mypy", "mypy-extensions", "coverage[toml]", - "types-PyYAML", "flit >=3.2,<4", "pip-tools", "pre-commit", "pygments", "pytest-cov", - "ruff>=0.0.246", "pytest-xdist", "py", - "tox>=3,<4", - "tox-travis", + "tox>=4,<5", "twine", "wheel", + "types-PyYAML", + "types-setuptools", + "types-requests", + "sphinx>=7,<8", + "sphinx_rtd_theme", + "recommonmark", + "commonmark", + "docutils", + "pygments", + "sphinx-markdown-tables", "grpcio-tools", "grpc-interceptor", # This has to be installed separately, otherwise you can't upload to pypi @@ -143,6 +148,7 @@ addopts = [ "--strict-markers", "-p", "no:logging", "--tb=short", + "--color=yes", "--tavern-setup-init-logging", ] norecursedirs = [ @@ -159,19 +165,20 @@ ignore = [ "B905", # zip(..., strict=True) only valid from 3.10+ "PLR0912", "PLR0915", "PLR0911", "PLR0913", # too many branches/variables/return values - sometimes this is just unavoidable "PLR2004", # 'magic numbers' + "PLW2901", # Loop variable overridden ] select = ["E", "F", "B", "W", "I", "S", "C4", "ICN", "T20", "PLE", "RUF", "SIM105", "PL"] # Look at: UP target-version = "py38" [tool.ruff.per-file-ignores] -"tests/*" = ["S"] +"tests/*" = ["S", "RUF"] [tool.ruff.isort] known-first-party = ["tavern"] [tool.tbump.version] -current = "2.0.7" +current = "2.5.0" regex = ''' (?P\d+) diff --git a/requirements.txt b/requirements.txt index 9ebe4398d..0ba586ce5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,93 +1,53 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --all-extras --generate-hashes --output-file=requirements.txt --resolver=backtracking pyproject.toml +# pip-compile --all-extras --generate-hashes --output-file=requirements.txt pyproject.toml # -allure-pytest==2.12.0 \ - --hash=sha256:1a10b2b78334443097d7be890a53c991e857e13d14781377c2f8d11eb4b5582c \ - --hash=sha256:85b73b1dbe9908ba4f84b80118a93e1049c02dd593209260d8c1c950cf286f6c +alabaster==0.7.13 \ + --hash=sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3 \ + --hash=sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2 + # via sphinx +allure-pytest==2.13.2 \ + --hash=sha256:17de9dbee7f61c8e66a5b5e818b00e419dbcea44cb55c24319401ba813220690 \ + --hash=sha256:22243159e8ec81ce2b5254b4013802198821b1b42f118f69d4a289396607c7b3 # via tavern (pyproject.toml) -allure-python-commons==2.12.0 \ - --hash=sha256:d5c362dd01167f086331822e9b1912d4e6fd6cbc2d1a006dd048e77e82a7ae73 \ - --hash=sha256:f968c69d4e656bcf274f2ef6578f24afcaab663f8814e238d0ab2b8bec2e0134 +allure-python-commons==2.13.2 \ + --hash=sha256:2bb3646ec3fbf5b36d178a5e735002bc130ae9f9ba80f080af97d368ba375051 \ + --hash=sha256:8a03681330231b1deadd86b97ff68841c6591320114ae638570f1ed60d7a2033 # via allure-pytest -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via # allure-python-commons # jsonschema # pytest -bcrypt==4.0.1 \ - --hash=sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535 \ - --hash=sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0 \ - --hash=sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410 \ - --hash=sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd \ - --hash=sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665 \ - --hash=sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab \ - --hash=sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71 \ - --hash=sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215 \ - --hash=sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b \ - --hash=sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda \ - --hash=sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9 \ - --hash=sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a \ - --hash=sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344 \ - --hash=sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f \ - --hash=sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d \ - --hash=sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c \ - --hash=sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c \ - --hash=sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2 \ - --hash=sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d \ - --hash=sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e \ - --hash=sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3 - # via paramiko -black==23.1.0 \ - --hash=sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd \ - --hash=sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555 \ - --hash=sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481 \ - --hash=sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468 \ - --hash=sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9 \ - --hash=sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a \ - --hash=sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958 \ - --hash=sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580 \ - --hash=sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26 \ - --hash=sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32 \ - --hash=sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8 \ - --hash=sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753 \ - --hash=sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b \ - --hash=sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074 \ - --hash=sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651 \ - --hash=sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24 \ - --hash=sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6 \ - --hash=sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad \ - --hash=sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac \ - --hash=sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221 \ - --hash=sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06 \ - --hash=sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27 \ - --hash=sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648 \ - --hash=sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739 \ - --hash=sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104 - # via tavern (pyproject.toml) -bleach==5.0.1 \ - --hash=sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a \ - --hash=sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c - # via readme-renderer -build==0.9.0 \ - --hash=sha256:1a07724e891cbd898923145eb7752ee7653674c511378eb9c7691aab1612bc3c \ - --hash=sha256:38a7a2b7a0bdc61a42a0a67509d88c71ecfc37b393baba770fae34e20929ff69 +babel==2.12.1 \ + --hash=sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610 \ + --hash=sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455 + # via sphinx +blinker==1.6.2 \ + --hash=sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213 \ + --hash=sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0 + # via flask +build==1.0.3 \ + --hash=sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b \ + --hash=sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f # via pip-tools bump2version==1.0.1 \ --hash=sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410 \ --hash=sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6 # via tavern (pyproject.toml) -cachetools==5.3.0 \ - --hash=sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14 \ - --hash=sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4 - # via google-auth -certifi==2022.12.7 \ - --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \ - --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 +cachetools==5.3.1 \ + --hash=sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590 \ + --hash=sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b + # via + # google-auth + # tox +certifi==2023.7.22 \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 # via requests cffi==1.15.1 \ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \ @@ -154,24 +114,102 @@ cffi==1.15.1 \ --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \ --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 - # via - # cryptography - # pynacl -cfgv==3.3.1 \ - --hash=sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426 \ - --hash=sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736 + # via cryptography +cfgv==3.4.0 \ + --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ + --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 # via pre-commit -charset-normalizer==2.1.1 \ - --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \ - --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f +chardet==5.2.0 \ + --hash=sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7 \ + --hash=sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970 + # via tox +charset-normalizer==3.1.0 \ + --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ + --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ + --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ + --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ + --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ + --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ + --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ + --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ + --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ + --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ + --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ + --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ + --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ + --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ + --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ + --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ + --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ + --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ + --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ + --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ + --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ + --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ + --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ + --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ + --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ + --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ + --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ + --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ + --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ + --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ + --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ + --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ + --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ + --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ + --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ + --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ + --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ + --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ + --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ + --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ + --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ + --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ + --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ + --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ + --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ + --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ + --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ + --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ + --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ + --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ + --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ + --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ + --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ + --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ + --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ + --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ + --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ + --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ + --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ + --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ + --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ + --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ + --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ + --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ + --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ + --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ + --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ + --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ + --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ + --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ + --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ + --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ + --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ + --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ + --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab # via requests -click==8.1.3 \ - --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \ - --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de # via - # black # flask # pip-tools +colorama==0.4.6 \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via tox colorlog==6.7.0 \ --hash=sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662 \ --hash=sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5 @@ -179,311 +217,322 @@ colorlog==6.7.0 \ commonmark==0.9.1 \ --hash=sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60 \ --hash=sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9 - # via rich -coverage[toml]==7.0.0 \ - --hash=sha256:0a8b0e86bede874bf5da566b02194fbb12dd14ce3585cabd58452007f272ba81 \ - --hash=sha256:100546219af59d2ad82d4575de03a303eb27b75ea36ffbd1677371924d50bcbc \ - --hash=sha256:10b6246cae61896ab4c7568e498e492cbb73a2dfa4c3af79141c43cf806f929a \ - --hash=sha256:215f40ef86f1958a1151fa7fad2b4f2f99534c4e10a34a1e065eba3f19ef8868 \ - --hash=sha256:2331b7bd84a1be79bd17ca8e103ce38db8cbf7cb354dc56e651ba489cf849212 \ - --hash=sha256:30220518dd89c4878908d73f5f3d1269f86e9e045354436534587a18c7b9da85 \ - --hash=sha256:32b94ad926e933976627f040f96dd1d9b0ac91f8d27e868c30a28253b9b6ac2d \ - --hash=sha256:33efe89cd0efef016db19d8d05aa46631f76793de90a61b6717acb202b36fe60 \ - --hash=sha256:36b62f0220459e528ad5806cc7dede71aa716e067d2cb10cb4a09686b8791fba \ - --hash=sha256:3c0deee68e0dae1d6e3fe6943c76d7e66fbeb6519bd08e4e5366bcc28a8a9aca \ - --hash=sha256:3ec256a592b497f26054195f7d7148892aca8c4cdcc064a7cc66ef7a0455b811 \ - --hash=sha256:43ec1935c6d6caab4f3bc126d20bd709c0002a175d62208ebe745be37a826a41 \ - --hash=sha256:5885a4ceb6dde34271bb0adafa4a248a7f589c89821e9da3110c39f92f41e21b \ - --hash=sha256:59e71912c7fc78d08a567ee65656123878f49ca1b5672e660ea70bf8dfbebf8f \ - --hash=sha256:793dcd9d42035746fc7637df4336f7581df19d33c5c5253cf988c99d8e93a8ba \ - --hash=sha256:8593c9baf1f0f273afa22f5b45508b76adc7b8e94e17e7d98fbe1e3cd5812af2 \ - --hash=sha256:8938f3a10f45019b502020ba9567b97b6ecc8c76b664b421705c5406d4f92fe8 \ - --hash=sha256:8dbf83a4611c591b5de65069b6fd4dd3889200ed270cd2f7f5ac765d3842889f \ - --hash=sha256:8f1e6d9c70d45a960d3f3d781ea62b167fdf2e0e1f6bb282b96feea653adb923 \ - --hash=sha256:96b5b1f1079e48f56bfccf103bcf44d48b9eb5163f1ea523fad580f15d3fe5e0 \ - --hash=sha256:97c0b001ff15b8e8882995fc07ac0a08c8baf8b13c1145f3f12e0587bbb0e335 \ - --hash=sha256:9a175da2a7320e18fc3ee1d147639a2b3a8f037e508c96aa2da160294eb50e17 \ - --hash=sha256:9fadd15f9fcfd7b16d9cccce9f5e6ec6f9b8df860633ad9aa62c2b14c259560f \ - --hash=sha256:a290b7921c1c05787b953e5854d394e887df40696f21381cc33c4e2179bf50ac \ - --hash=sha256:a30b646fbdd5bc52f506e149fa4fbdef82432baf6b81774e61ec4e3b43b9cbde \ - --hash=sha256:a6fff0f08bc5ffd0d78db821971472b4adc2ee876b86f743e46d634fb8e3c22f \ - --hash=sha256:a7e1bb36b4e57a2d304322021b35d4e4a25fa0d501ba56e8e51efaebf4480556 \ - --hash=sha256:a8785791c2120af114ea7a06137f7778632e568a5aa2bbfc3b46c573b702af74 \ - --hash=sha256:ae088eb1cbdad8206931b1bf3f11dee644e038a9300be84d3e705e29356e5b1d \ - --hash=sha256:b18df11efa615b79b9ecc13035a712957ff6283f7b244e57684e1c092869f541 \ - --hash=sha256:b8f7cd942dda3795fc9eadf303cc53a422ac057e3b70c2ad6d4276ec6a83a541 \ - --hash=sha256:bc904aa96105d73357de03de76336b1e3db28e2b12067d36625fd9646ab043fd \ - --hash=sha256:bcaf18e46668057051a312c714a4548b81f7e8fb3454116ad97be7562d2a99e4 \ - --hash=sha256:bf437a04b9790d3c9cd5b48e9ce9aa84229040e3ae7d6c670a55118906113c5a \ - --hash=sha256:c1ba6e63b831112b9484ff5905370d89e43d4316bac76d403031f60d61597466 \ - --hash=sha256:c4b63888bef2928d0eca12cbce0760cfb696acb4fe226eb55178b6a2a039328a \ - --hash=sha256:c685fc17d6f4f1a3833e9dac27d0b931f7ccb52be6c30d269374203c7d0204a2 \ - --hash=sha256:cda63459eb20652b22e038729a8f5063862c189a3963cb042a764b753172f75e \ - --hash=sha256:d43d406a4d73aa7f855fa44fa77ff47e739b565b2af3844600cdc016d01e46b9 \ - --hash=sha256:d564142a03d3bc8913499a458e931b52ddfe952f69b6cd4b24d810fd2959044a \ - --hash=sha256:d6b4af31fb49a2ae8de1cd505fa66c403bfcc5066e845ac19d8904dcfc9d40da \ - --hash=sha256:db8141856dc9be0917413df7200f53accf1d84c8b156868e6af058a1ea8e903a \ - --hash=sha256:de06e7585abe88c6d38c1b73ce4c3cb4c1a79fbb0da0d0f8e8689ef5729ec60d \ - --hash=sha256:e06abac1a4aec1ff989131e43ca917fc7bd296f34bf0cfe86cbf74343b21566d \ - --hash=sha256:e645c73cbfc4577d93747d3f793115acf6f907a7eb9208fa807fdcf2da1964a4 \ - --hash=sha256:e907db8bdd0ad1253a33c20fdc5f0f6209d271114a9c6f1fcdf96617343f7ca0 \ - --hash=sha256:f2569682d6ea9628da8d6ba38579a48b1e53081226ec7a6c82b5024b3ce5009f \ - --hash=sha256:f6a4bf5bdee93f6817797beba7086292c2ebde6df0d5822e0c33f8b05415c339 \ - --hash=sha256:f9071e197faa24837b967bc9aa0b9ef961f805a75f1ee3ea1f3367f55cd46c3c \ - --hash=sha256:fb85b7a7a4b204bd59d6d0b0c8d87d9ffa820da225e691dfaffc3137dc05b5f6 \ - --hash=sha256:fee283cd36c3f14422d9c1b51da24ddbb5e1eed89ad2480f6a9f115df38b5df8 # via - # pytest-cov + # recommonmark # tavern (pyproject.toml) -cryptography==39.0.1 \ - --hash=sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4 \ - --hash=sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f \ - --hash=sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502 \ - --hash=sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41 \ - --hash=sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965 \ - --hash=sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e \ - --hash=sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc \ - --hash=sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad \ - --hash=sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505 \ - --hash=sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388 \ - --hash=sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6 \ - --hash=sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2 \ - --hash=sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac \ - --hash=sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695 \ - --hash=sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6 \ - --hash=sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336 \ - --hash=sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0 \ - --hash=sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c \ - --hash=sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106 \ - --hash=sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a \ - --hash=sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8 +coverage[toml]==7.3.1 \ + --hash=sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375 \ + --hash=sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344 \ + --hash=sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e \ + --hash=sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745 \ + --hash=sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f \ + --hash=sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194 \ + --hash=sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a \ + --hash=sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f \ + --hash=sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760 \ + --hash=sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8 \ + --hash=sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392 \ + --hash=sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d \ + --hash=sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc \ + --hash=sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40 \ + --hash=sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981 \ + --hash=sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0 \ + --hash=sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92 \ + --hash=sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3 \ + --hash=sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0 \ + --hash=sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086 \ + --hash=sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7 \ + --hash=sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465 \ + --hash=sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140 \ + --hash=sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952 \ + --hash=sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3 \ + --hash=sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8 \ + --hash=sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f \ + --hash=sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593 \ + --hash=sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0 \ + --hash=sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204 \ + --hash=sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037 \ + --hash=sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276 \ + --hash=sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9 \ + --hash=sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26 \ + --hash=sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce \ + --hash=sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7 \ + --hash=sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136 \ + --hash=sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a \ + --hash=sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4 \ + --hash=sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c \ + --hash=sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f \ + --hash=sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832 \ + --hash=sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3 \ + --hash=sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969 \ + --hash=sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520 \ + --hash=sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887 \ + --hash=sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3 \ + --hash=sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6 \ + --hash=sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1 \ + --hash=sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff \ + --hash=sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981 \ + --hash=sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e # via - # paramiko - # secretstorage -distlib==0.3.6 \ - --hash=sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46 \ - --hash=sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e + # pytest-cov + # tavern (pyproject.toml) +cryptography==41.0.4 \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f + # via secretstorage +distlib==0.3.7 \ + --hash=sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057 \ + --hash=sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8 # via virtualenv -distro==1.8.0 \ - --hash=sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8 \ - --hash=sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff - # via docker-compose -docker[ssh]==6.0.1 \ - --hash=sha256:896c4282e5c7af5c45e8b683b0b0c33932974fe6e50fc6906a0a83616ab3da97 \ - --hash=sha256:dbcb3bd2fa80dca0788ed908218bf43972772009b881ed1e20dfc29a65e49782 - # via docker-compose -docker-compose==1.29.2 \ - --hash=sha256:4c8cd9d21d237412793d18bd33110049ee9af8dab3fe2c213bbd0733959b09b7 \ - --hash=sha256:8d5589373b35c8d3b1c8c1182c6e4a4ff14bffa3dd0b605fcd08f73c94cef809 - # via tavern (pyproject.toml) -dockerpty==0.4.1 \ - --hash=sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce - # via docker-compose docopt==0.6.2 \ --hash=sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491 - # via - # docker-compose - # pykwalify -docutils==0.19 \ - --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \ - --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc + # via pykwalify +docutils==0.18.1 \ + --hash=sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c \ + --hash=sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06 # via # flit # readme-renderer -exceptiongroup==1.0.4 \ - --hash=sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828 \ - --hash=sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec + # recommonmark + # sphinx + # sphinx-rtd-theme + # tavern (pyproject.toml) +exceptiongroup==1.1.3 \ + --hash=sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9 \ + --hash=sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3 # via pytest -execnet==1.9.0 \ - --hash=sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5 \ - --hash=sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142 +execnet==2.0.2 \ + --hash=sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41 \ + --hash=sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af # via pytest-xdist -faker==15.3.4 \ - --hash=sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc \ - --hash=sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76 +faker==19.6.1 \ + --hash=sha256:5d6b7880b3bea708075ddf91938424453f07053a59f8fa0453c1870df6ff3292 \ + --hash=sha256:64c8513c53c3a809075ee527b323a0ba61517814123f3137e4912f5d43350139 # via tavern (pyproject.toml) -filelock==3.8.2 \ - --hash=sha256:7565f628ea56bfcd8e54e42bdc55da899c85c1abfe1b5bcfd147e9188cebb3b2 \ - --hash=sha256:8df285554452285f79c035efb0c861eb33a4bcfa5b7a137016e32e6a90f9792c +filelock==3.12.4 \ + --hash=sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4 \ + --hash=sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd # via # tox # virtualenv -flask==2.2.2 \ - --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \ - --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526 +flask==2.3.3 \ + --hash=sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc \ + --hash=sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b # via tavern (pyproject.toml) -flit==3.8.0 \ - --hash=sha256:5ee0f88fd1cfa4160d1a8fa01237e96d06d677ae0403a0bbabbb277cb37c5e9c \ - --hash=sha256:d0f2a8f4bd45dc794befbf5839ecc0fd3830d65a57bd52b5997542fac5d5e937 +flit==3.9.0 \ + --hash=sha256:076c3aaba5ac24cf0ad3251f910900d95a08218e6bcb26f21fef1036cc4679ca \ + --hash=sha256:d75edf5eb324da20d53570a6a6f87f51e606eee8384925cd66a90611140844c7 # via tavern (pyproject.toml) -flit-core==3.8.0 \ - --hash=sha256:64a29ec845164a6abe1136bf4bc5ae012bdfe758ed42fc7571a9059a7c80bd83 \ - --hash=sha256:b305b30c99526df5e63d6022dd2310a0a941a187bd3884f4c8ef0418df6c39f3 +flit-core==3.9.0 \ + --hash=sha256:72ad266176c4a3fcfab5f2930d76896059851240570ce9a98733b658cb786eba \ + --hash=sha256:7aada352fb0c7f5538c4fafeddf314d3a6a92ee8e2b1de70482329e42de70301 # via flit fluent-logger==0.10.0 \ --hash=sha256:543637e5e62ec3fc3c92b44e5a4e148a3cea88a0f8ca4fae26c7e60fda7564c1 \ --hash=sha256:678bda90c513ff0393964b64544ce41ef25669d2089ce6c3b63d9a18554b9bfa # via tavern (pyproject.toml) -google-api-core==2.11.0 \ - --hash=sha256:4b9bb5d5a380a0befa0573b302651b8a9a89262c1730e37bf423cec511804c22 \ - --hash=sha256:ce222e27b0de0d7bc63eb043b956996d6dccab14cc3b690aaea91c9cc99dc16e +google-api-core==2.12.0 \ + --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ + --hash=sha256:ec6054f7d64ad13b41e43d96f735acbd763b0f3b695dabaa2d579673f6a6e160 # via google-api-python-client -google-api-python-client==2.79.0 \ - --hash=sha256:577c0aeae1eb3c754eacb9122d369d67609fef759bc6a4fa16cafeab4f30019b \ - --hash=sha256:b9b6dc5f139892310093ba75d0df4c78f48655078953c923957dab1ec86129e7 +google-api-python-client==2.104.0 \ + --hash=sha256:867061526aa6dc6c1481d118e913a8a38a02a01eed589413968397ebd77df71d \ + --hash=sha256:bbc66520e7fe9417b93fd113f2a0a1afa789d686de9009b6e94e48fdea50a60f # via tavern (pyproject.toml) -google-auth==2.16.1 \ - --hash=sha256:5fd170986bce6bfd7bb5c845c4b8362edb1e0cba901e062196e83f8bb5d5d32c \ - --hash=sha256:75d76ea857df65938e1f71dcbcd7d0cd48e3f80b34b8870ba229c9292081f7ef +google-auth==2.23.3 \ + --hash=sha256:6864247895eea5d13b9c57c9e03abb49cb94ce2dc7c58e91cba3248c7477c9e3 \ + --hash=sha256:a8f4608e65c244ead9e0538f181a96c6e11199ec114d41f1d7b1bffa96937bda # via # google-api-core # google-api-python-client # google-auth-httplib2 -google-auth-httplib2==0.1.0 \ - --hash=sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10 \ - --hash=sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac +google-auth-httplib2==0.1.1 \ + --hash=sha256:42c50900b8e4dcdf8222364d1f0efe32b8421fb6ed72f2613f12f75cc933478c \ + --hash=sha256:c64bc555fdc6dd788ea62ecf7bccffcf497bf77244887a3f3d7a5a02f8e3fc29 # via google-api-python-client -googleapis-common-protos==1.58.0 \ - --hash=sha256:c727251ec025947d545184ba17e3578840fc3a24a0516a020479edab660457df \ - --hash=sha256:ca3befcd4580dab6ad49356b46bf165bb68ff4b32389f028f1abd7c10ab9519a +googleapis-common-protos==1.61.0 \ + --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ + --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b # via # google-api-core # grpcio-status -grpc-interceptor==0.15.1 \ - --hash=sha256:1cc52c34b0d7ff34512fb7780742ecda37bf3caa18ecc5f33f09b4f74e96b276 \ - --hash=sha256:3efadbc9aead272ac7a360c75c4bd96233094c9a5192dbb51c6156246bd64ba0 +grpc-interceptor==0.15.3 \ + --hash=sha256:33592cb9d8c00fceed5755c71029f75aef55b273496dbced06f1d48f2571fcc3 \ + --hash=sha256:96be2043b7e49f9deb444f18b61c373ea28d22d81c90cd3b82127a4744eb9247 # via tavern (pyproject.toml) -grpcio==1.51.1 \ - --hash=sha256:094e64236253590d9d4075665c77b329d707b6fca864dd62b144255e199b4f87 \ - --hash=sha256:0dc5354e38e5adf2498312f7241b14c7ce3484eefa0082db4297189dcbe272e6 \ - --hash=sha256:0e1a9e1b4a23808f1132aa35f968cd8e659f60af3ffd6fb00bcf9a65e7db279f \ - --hash=sha256:0fb93051331acbb75b49a2a0fd9239c6ba9528f6bdc1dd400ad1cb66cf864292 \ - --hash=sha256:16c71740640ba3a882f50b01bf58154681d44b51f09a5728180a8fdc66c67bd5 \ - --hash=sha256:172405ca6bdfedd6054c74c62085946e45ad4d9cec9f3c42b4c9a02546c4c7e9 \ - --hash=sha256:17ec9b13cec4a286b9e606b48191e560ca2f3bbdf3986f91e480a95d1582e1a7 \ - --hash=sha256:22b011674090594f1f3245960ced7386f6af35485a38901f8afee8ad01541dbd \ - --hash=sha256:24ac1154c4b2ab4a0c5326a76161547e70664cd2c39ba75f00fc8a2170964ea2 \ - --hash=sha256:257478300735ce3c98d65a930bbda3db172bd4e00968ba743e6a1154ea6edf10 \ - --hash=sha256:29cb97d41a4ead83b7bcad23bdb25bdd170b1e2cba16db6d3acbb090bc2de43c \ - --hash=sha256:2b170eaf51518275c9b6b22ccb59450537c5a8555326fd96ff7391b5dd75303c \ - --hash=sha256:31bb6bc7ff145e2771c9baf612f4b9ebbc9605ccdc5f3ff3d5553de7fc0e0d79 \ - --hash=sha256:3c2b3842dcf870912da31a503454a33a697392f60c5e2697c91d133130c2c85d \ - --hash=sha256:3f9b0023c2c92bebd1be72cdfca23004ea748be1813a66d684d49d67d836adde \ - --hash=sha256:471d39d3370ca923a316d49c8aac66356cea708a11e647e3bdc3d0b5de4f0a40 \ - --hash=sha256:49d680356a975d9c66a678eb2dde192d5dc427a7994fb977363634e781614f7c \ - --hash=sha256:4c4423ea38a7825b8fed8934d6d9aeebdf646c97e3c608c3b0bcf23616f33877 \ - --hash=sha256:506b9b7a4cede87d7219bfb31014d7b471cfc77157da9e820a737ec1ea4b0663 \ - --hash=sha256:538d981818e49b6ed1e9c8d5e5adf29f71c4e334e7d459bf47e9b7abb3c30e09 \ - --hash=sha256:59dffade859f157bcc55243714d57b286da6ae16469bf1ac0614d281b5f49b67 \ - --hash=sha256:5a6ebcdef0ef12005d56d38be30f5156d1cb3373b52e96f147f4a24b0ddb3a9d \ - --hash=sha256:5dca372268c6ab6372d37d6b9f9343e7e5b4bc09779f819f9470cd88b2ece3c3 \ - --hash=sha256:6df3b63538c362312bc5fa95fb965069c65c3ea91d7ce78ad9c47cab57226f54 \ - --hash=sha256:6f0b89967ee11f2b654c23b27086d88ad7bf08c0b3c2a280362f28c3698b2896 \ - --hash=sha256:75e29a90dc319f0ad4d87ba6d20083615a00d8276b51512e04ad7452b5c23b04 \ - --hash=sha256:7942b32a291421460d6a07883033e392167d30724aa84987e6956cd15f1a21b9 \ - --hash=sha256:9235dcd5144a83f9ca6f431bd0eccc46b90e2c22fe27b7f7d77cabb2fb515595 \ - --hash=sha256:97d67983189e2e45550eac194d6234fc38b8c3b5396c153821f2d906ed46e0ce \ - --hash=sha256:9ff42c5620b4e4530609e11afefa4a62ca91fa0abb045a8957e509ef84e54d30 \ - --hash=sha256:a8a0b77e992c64880e6efbe0086fe54dfc0bbd56f72a92d9e48264dcd2a3db98 \ - --hash=sha256:aacb54f7789ede5cbf1d007637f792d3e87f1c9841f57dd51abf89337d1b8472 \ - --hash=sha256:bc59f7ba87972ab236f8669d8ca7400f02a0eadf273ca00e02af64d588046f02 \ - --hash=sha256:cc2bece1737b44d878cc1510ea04469a8073dbbcdd762175168937ae4742dfb3 \ - --hash=sha256:cd3baccea2bc5c38aeb14e5b00167bd4e2373a373a5e4d8d850bd193edad150c \ - --hash=sha256:dad6533411d033b77f5369eafe87af8583178efd4039c41d7515d3336c53b4f1 \ - --hash=sha256:e223a9793522680beae44671b9ed8f6d25bbe5ddf8887e66aebad5e0686049ef \ - --hash=sha256:e473525c28251558337b5c1ad3fa969511e42304524a4e404065e165b084c9e4 \ - --hash=sha256:e4ef09f8997c4be5f3504cefa6b5c6cc3cf648274ce3cede84d4342a35d76db6 \ - --hash=sha256:e6dfc2b6567b1c261739b43d9c59d201c1b89e017afd9e684d85aa7a186c9f7a \ - --hash=sha256:eacad297ea60c72dd280d3353d93fb1dcca952ec11de6bb3c49d12a572ba31dd \ - --hash=sha256:f1158bccbb919da42544a4d3af5d9296a3358539ffa01018307337365a9a0c64 \ - --hash=sha256:f1fec3abaf274cdb85bf3878167cfde5ad4a4d97c68421afda95174de85ba813 \ - --hash=sha256:f96ace1540223f26fbe7c4ebbf8a98e3929a6aa0290c8033d12526847b291c0f \ - --hash=sha256:fbdbe9a849854fe484c00823f45b7baab159bdd4a46075302281998cb8719df5 +grpcio==1.59.0 \ + --hash=sha256:0ae444221b2c16d8211b55326f8ba173ba8f8c76349bfc1768198ba592b58f74 \ + --hash=sha256:0b84445fa94d59e6806c10266b977f92fa997db3585f125d6b751af02ff8b9fe \ + --hash=sha256:14890da86a0c0e9dc1ea8e90101d7a3e0e7b1e71f4487fab36e2bfd2ecadd13c \ + --hash=sha256:15f03bd714f987d48ae57fe092cf81960ae36da4e520e729392a59a75cda4f29 \ + --hash=sha256:1a839ba86764cc48226f50b924216000c79779c563a301586a107bda9cbe9dcf \ + --hash=sha256:225e5fa61c35eeaebb4e7491cd2d768cd8eb6ed00f2664fa83a58f29418b39fd \ + --hash=sha256:228b91ce454876d7eed74041aff24a8f04c0306b7250a2da99d35dd25e2a1211 \ + --hash=sha256:2ea95cd6abbe20138b8df965b4a8674ec312aaef3147c0f46a0bac661f09e8d0 \ + --hash=sha256:2f120d27051e4c59db2f267b71b833796770d3ea36ca712befa8c5fff5da6ebd \ + --hash=sha256:34341d9e81a4b669a5f5dca3b2a760b6798e95cdda2b173e65d29d0b16692857 \ + --hash=sha256:3859917de234a0a2a52132489c4425a73669de9c458b01c9a83687f1f31b5b10 \ + --hash=sha256:38823bd088c69f59966f594d087d3a929d1ef310506bee9e3648317660d65b81 \ + --hash=sha256:38da5310ef84e16d638ad89550b5b9424df508fd5c7b968b90eb9629ca9be4b9 \ + --hash=sha256:3b8ff795d35a93d1df6531f31c1502673d1cebeeba93d0f9bd74617381507e3f \ + --hash=sha256:50eff97397e29eeee5df106ea1afce3ee134d567aa2c8e04fabab05c79d791a7 \ + --hash=sha256:5711c51e204dc52065f4a3327dca46e69636a0b76d3e98c2c28c4ccef9b04c52 \ + --hash=sha256:598f3530231cf10ae03f4ab92d48c3be1fee0c52213a1d5958df1a90957e6a88 \ + --hash=sha256:611d9aa0017fa386809bddcb76653a5ab18c264faf4d9ff35cb904d44745f575 \ + --hash=sha256:61bc72a00ecc2b79d9695220b4d02e8ba53b702b42411397e831c9b0589f08a3 \ + --hash=sha256:63982150a7d598281fa1d7ffead6096e543ff8be189d3235dd2b5604f2c553e5 \ + --hash=sha256:6c4b1cc3a9dc1924d2eb26eec8792fedd4b3fcd10111e26c1d551f2e4eda79ce \ + --hash=sha256:81d86a096ccd24a57fa5772a544c9e566218bc4de49e8c909882dae9d73392df \ + --hash=sha256:849c47ef42424c86af069a9c5e691a765e304079755d5c29eff511263fad9c2a \ + --hash=sha256:871371ce0c0055d3db2a86fdebd1e1d647cf21a8912acc30052660297a5a6901 \ + --hash=sha256:8cd2d38c2d52f607d75a74143113174c36d8a416d9472415eab834f837580cf7 \ + --hash=sha256:936b2e04663660c600d5173bc2cc84e15adbad9c8f71946eb833b0afc205b996 \ + --hash=sha256:93e9cb546e610829e462147ce724a9cb108e61647a3454500438a6deef610be1 \ + --hash=sha256:956f0b7cb465a65de1bd90d5a7475b4dc55089b25042fe0f6c870707e9aabb1d \ + --hash=sha256:986de4aa75646e963466b386a8c5055c8b23a26a36a6c99052385d6fe8aaf180 \ + --hash=sha256:aca8a24fef80bef73f83eb8153f5f5a0134d9539b4c436a716256b311dda90a6 \ + --hash=sha256:acf70a63cf09dd494000007b798aff88a436e1c03b394995ce450be437b8e54f \ + --hash=sha256:b34c7a4c31841a2ea27246a05eed8a80c319bfc0d3e644412ec9ce437105ff6c \ + --hash=sha256:b95ec8ecc4f703f5caaa8d96e93e40c7f589bad299a2617bdb8becbcce525539 \ + --hash=sha256:ba0ca727a173ee093f49ead932c051af463258b4b493b956a2c099696f38aa66 \ + --hash=sha256:c041a91712bf23b2a910f61e16565a05869e505dc5a5c025d429ca6de5de842c \ + --hash=sha256:c0488c2b0528e6072010182075615620071371701733c63ab5be49140ed8f7f0 \ + --hash=sha256:c173a87d622ea074ce79be33b952f0b424fa92182063c3bda8625c11d3585d09 \ + --hash=sha256:c251d22de8f9f5cca9ee47e4bade7c5c853e6e40743f47f5cc02288ee7a87252 \ + --hash=sha256:c4dfdb49f4997dc664f30116af2d34751b91aa031f8c8ee251ce4dcfc11277b0 \ + --hash=sha256:ca87ee6183421b7cea3544190061f6c1c3dfc959e0b57a5286b108511fd34ff4 \ + --hash=sha256:ceb1e68135788c3fce2211de86a7597591f0b9a0d2bb80e8401fd1d915991bac \ + --hash=sha256:d09bd2a4e9f5a44d36bb8684f284835c14d30c22d8ec92ce796655af12163588 \ + --hash=sha256:d0fcf53df684fcc0154b1e61f6b4a8c4cf5f49d98a63511e3f30966feff39cd0 \ + --hash=sha256:d74f7d2d7c242a6af9d4d069552ec3669965b74fed6b92946e0e13b4168374f9 \ + --hash=sha256:de2599985b7c1b4ce7526e15c969d66b93687571aa008ca749d6235d056b7205 \ + --hash=sha256:e5378785dce2b91eb2e5b857ec7602305a3b5cf78311767146464bfa365fc897 \ + --hash=sha256:ec78aebb9b6771d6a1de7b6ca2f779a2f6113b9108d486e904bde323d51f5589 \ + --hash=sha256:f1feb034321ae2f718172d86b8276c03599846dc7bb1792ae370af02718f91c5 \ + --hash=sha256:f21917aa50b40842b51aff2de6ebf9e2f6af3fe0971c31960ad6a3a2b24988f4 \ + --hash=sha256:f367e4b524cb319e50acbdea57bb63c3b717c5d561974ace0b065a648bb3bad3 \ + --hash=sha256:f6cfe44a5d7c7d5f1017a7da1c8160304091ca5dc64a0f85bca0d63008c3137a \ + --hash=sha256:fa66cac32861500f280bb60fe7d5b3e22d68c51e18e65367e38f8669b78cea3b \ + --hash=sha256:fc8bf2e7bc725e76c0c11e474634a08c8f24bcf7426c0c6d60c8f9c6e70e4d4a \ + --hash=sha256:fe976910de34d21057bcb53b2c5e667843588b48bf11339da2a75f5c4c5b4055 # via # grpc-interceptor # grpcio-reflection # grpcio-status # grpcio-tools # tavern (pyproject.toml) -grpcio-reflection==1.51.1 \ - --hash=sha256:b70af764a83e42a44f65df1edb232e972ab69e72bc7fbbad481e66c29a9d8cb8 \ - --hash=sha256:c07a93c0c36ef88fe475744289863b4787005eff4de0cc04213ecad718b01aae +grpcio-reflection==1.59.0 \ + --hash=sha256:1fe8f0dd6c180fdcf4e12ced2a8f784d9c741ccbc0b198585b1df024b7f8f3f2 \ + --hash=sha256:bf4efc7e2e8162e5be9736f4d0a0b324c9bf0c04ad597a9d78fcaf1fbdf818ec # via tavern (pyproject.toml) -grpcio-status==1.51.1 \ - --hash=sha256:a52cbdc4b18f325bfc13d319ae7c7ae7a0fee07f3d9a005504d6097896d7a495 \ - --hash=sha256:ac2617a3095935ebd785e2228958f24b10a0d527a0c9eb5a0863c784f648a816 +grpcio-status==1.59.0 \ + --hash=sha256:cb5a222b14a80ee050bff9676623822e953bff0c50d2d29180de723652fdf10d \ + --hash=sha256:f93b9c33e0a26162ef8431bfcffcc3e1fb217ccd8d7b5b3061b6e9f813e698b5 # via tavern (pyproject.toml) -grpcio-tools==1.51.1 \ - --hash=sha256:048793747339f327ea091d8f022c6756d89713d8080dffde5ce7380cc348ea8e \ - --hash=sha256:055819992ddd30c642a7fd6f344a03747be3afa95cb910f8a2e5efaabd41cde5 \ - --hash=sha256:0a218f64e667f3332b74080bdc5440aaf0fa6700ae07a0b54ecf085aaef2aa9f \ - --hash=sha256:14e82c2b3ee7e300611c2c729d411b3b911e4cca5f4ec14787457a2fb72ff9d4 \ - --hash=sha256:15b8acf4eaa0ebe37e2f69108de49efd935b7abe9c7e58ba737490b99906aa76 \ - --hash=sha256:16b8b915625dc6eb2ea7efdfb06f1fae44a9066c9016453a2ca120c034f33090 \ - --hash=sha256:1c44b57a6770b78a1eafe355878ff1ec59a2fa07455a2cbd522c071eedae04d4 \ - --hash=sha256:2281180490c475d09b7aa05dabafa5e09de9902176931e7295113f636c2b5360 \ - --hash=sha256:27113b354f7587684eb55125733e6e5be1f489458abfe12344dabd918d8dcc54 \ - --hash=sha256:331a897306adeec3c67470431ea8d8b4972b689d32966f94506d91f4dac20952 \ - --hash=sha256:392ad4cd004f7b843cf7d916d9a15b2d6585965bfef235be1c88d8f8649777e5 \ - --hash=sha256:3a671466158ed74c07ee070fb940ed783acf59ba6e6e53cb4de8fd63819c6c7f \ - --hash=sha256:40ef70e8c5d0310dedff9af502b520b4c7e215bce94094527fb959150a0c594a \ - --hash=sha256:4957f1ffa16598aa5379505fcbaeb47d65693a46b0817f4ee61db76707092aeb \ - --hash=sha256:49624394805568acd7d767dea5a00d970fca5ad8f395fe0161eeea0de5133eba \ - --hash=sha256:4e3249a2ec435b3b972610c66c8a714c188844500d564c910f57a2771dc61978 \ - --hash=sha256:531586c5598a99658249f3c5e92826d6d2bb117abd6ffc88527d1e1d9eaef924 \ - --hash=sha256:566809d9942e78821b279af70f3cf159a328127f9f3d5fee8d83ad8b2d27b2fe \ - --hash=sha256:64d8ad369417759f5fdb8ffb7cbd6374fecc06ab51c9a226dee9bbd7d311c3b5 \ - --hash=sha256:674b340f2f7bb2adbc3f15144bd37ce5ea83239f78b68dbbd0ea3cba00107e2b \ - --hash=sha256:67b304282cad38642587ebae68617e450e1ad4fa1c0c8b19e9e30274dbb32716 \ - --hash=sha256:6b83d7fc2597c6d392c225177d1fbbcff74900f8cc40b33236987fd1ff841330 \ - --hash=sha256:6d6626a6e4dbe843df96dc8c08dd244d2191a75324f54bfa4ebaa3e76b0b1958 \ - --hash=sha256:6e72a30be1746ea0749a8486d0ca0120c0b2757fe84fc246a5144b1ef66d7b89 \ - --hash=sha256:794f26a09b70f4f101df5cf54c6c12dc1b65747ab1dee5bda02c2991389ade56 \ - --hash=sha256:79c06d2577cb4d977922bbf01234de3b20f73d1784d3cbe3179deee1bdb9a60b \ - --hash=sha256:87bc5f3e3698c65907d397003c64d25c3ea84e3d6aa46dac133bd98bf66835ee \ - --hash=sha256:8e62d23d3fed9d4f81738f98dd193dbd2e21aed4a8f0dd715e75b5439e649727 \ - --hash=sha256:98777b5031f1b3c58b688815ffa83435c103b2152c26eb144f80f4a4bb34addb \ - --hash=sha256:9906fb6bf6d9c30c23d85153f12d130f44325afe8f9ebe58aa7a6c82ecade9d8 \ - --hash=sha256:9dfe6c12b0e2c07f6a4a91a9912ef4e5bd007672533891a44e6f433ffbf7c3b1 \ - --hash=sha256:a66b3a5d18a7615f0f828b72e2d2935751459c89cc4725e56bdfb3d2cd93281f \ - --hash=sha256:aab24a342642329de38139cb26f8492882ca0d8551bb87f6530bcc613945a0d0 \ - --hash=sha256:b4fb8ed6d29f2d6cf03ef99ffaad635bbc132a59be77013691392fe557e67144 \ - --hash=sha256:c4649af7f5d9553975ee66b6bfae20a84be779f13e163fa835e782961895e63c \ - --hash=sha256:ccd37165d7a3e93f460096a2eb62b7a9c1ebe5c424eaee42d8e92740d0c8f6bc \ - --hash=sha256:d5e033c04b416afcddd5231b3ff94a34fb5d26fba2416eb940e69b05f22cfd25 \ - --hash=sha256:d7b186183515ad6b8584ffe4bd820b72b00f6e7d121fb1c36294edeea9092313 \ - --hash=sha256:d8cc862a1ad30f94528d66cc6f95fb9e659005e568313e54a23550535b649573 \ - --hash=sha256:de51a0a71845b854f6a5967756c893c96bd03e37f39e5dce87b4f409dac36ee2 \ - --hash=sha256:e9abc03d67793b1bf33dc766caa69a3333f9db029869ba6e8fc6cd9c251c0080 \ - --hash=sha256:ecf1494cb695afead36995534f787761ee33fb9e116b23030113a37fe6057a83 \ - --hash=sha256:f06bb0753b7cecbff154b523cfb8f45dee2c31b0a4c72bed7da44c57f1cba113 \ - --hash=sha256:f336ad9be661d92fa45940e74e8ff3d78e67ebe9b4f7ea8774b2d680c17aeb6c \ - --hash=sha256:f6caf36e7752728329a28f93afec7c4ec9015fc1c6e4460bd1eb0f3737e1c55a +grpcio-tools==1.59.0 \ + --hash=sha256:0548e901894399886ff4a4cd808cb850b60c021feb4a8977a0751f14dd7e55d9 \ + --hash=sha256:05bf7b3ed01c8a562bb7e840f864c58acedbd6924eb616367c0bd0a760bdf483 \ + --hash=sha256:1d551ff42962c7c333c3da5c70d5e617a87dee581fa2e2c5ae2d5137c8886779 \ + --hash=sha256:1df755951f204e65bf9232a9cac5afe7d6b8e4c87ac084d3ecd738fdc7aa4174 \ + --hash=sha256:204e08f807b1d83f5f0efea30c4e680afe26a43dec8ba614a45fa698a7ef0a19 \ + --hash=sha256:240a7a3c2c54f77f1f66085a635bca72003d02f56a670e7db19aec531eda8f78 \ + --hash=sha256:26eb2eebf150a33ebf088e67c1acf37eb2ac4133d9bfccbaa011ad2148c08b42 \ + --hash=sha256:27a7f226b741b2ebf7e2d0779d2c9b17f446d1b839d59886c1619e62cc2ae472 \ + --hash=sha256:2d970aa26854f535ffb94ea098aa8b43de020d9a14682e4a15dcdaeac7801b27 \ + --hash=sha256:2ee960904dde12a7fa48e1591a5b3eeae054bdce57bacf9fd26685a98138f5bf \ + --hash=sha256:335e2f355a0c544a88854e2c053aff8a3f398b84a263a96fa19d063ca1fe513a \ + --hash=sha256:387662bee8e4c0b52cc0f61eaaca0ca583f5b227103f685b76083a3590a71a3e \ + --hash=sha256:40cbf712769242c2ba237745285ef789114d7fcfe8865fc4817d87f20015e99a \ + --hash=sha256:4499d4bc5aa9c7b645018d8b0db4bebd663d427aabcd7bee7777046cb1bcbca7 \ + --hash=sha256:498e7be0b14385980efa681444ba481349c131fc5ec88003819f5d929646947c \ + --hash=sha256:4a10e59cca462208b489478340b52a96d64e8b8b6f1ac097f3e8cb211d3f66c0 \ + --hash=sha256:4ee443abcd241a5befb05629013fbf2eac637faa94aaa3056351aded8a31c1bc \ + --hash=sha256:51d9595629998d8b519126c5a610f15deb0327cd6325ed10796b47d1d292e70b \ + --hash=sha256:520c0c83ea79d14b0679ba43e19c64ca31d30926b26ad2ca7db37cbd89c167e2 \ + --hash=sha256:5b2d6da553980c590487f2e7fd3ec9c1ad8805ff2ec77977b92faa7e3ca14e1f \ + --hash=sha256:6119f62c462d119c63227b9534210f0f13506a888151b9bf586f71e7edf5088b \ + --hash=sha256:6aec8a4ed3808b7dfc1276fe51e3e24bec0eeaf610d395bcd42934647cf902a3 \ + --hash=sha256:71cc6db1d66da3bc3730d9937bddc320f7b1f1dfdff6342bcb5741515fe4110b \ + --hash=sha256:784aa52965916fec5afa1a28eeee6f0073bb43a2a1d7fedf963393898843077a \ + --hash=sha256:821dba464d84ebbcffd9d420302404db2fa7a40c7ff4c4c4c93726f72bfa2769 \ + --hash=sha256:868892ad9e00651a38dace3e4924bae82fc4fd4df2c65d37b74381570ee8deb1 \ + --hash=sha256:882b809b42b5464bee55288f4e60837297f9618e53e69ae3eea6d61b05ce48fa \ + --hash=sha256:8c4634b3589efa156a8d5860c0a2547315bd5c9e52d14c960d716fe86e0927be \ + --hash=sha256:8f0da5861ee276ca68493b217daef358960e8527cc63c7cb292ca1c9c54939af \ + --hash=sha256:962d1a3067129152cee3e172213486cb218a6bad703836991f46f216caefcf00 \ + --hash=sha256:99b3bde646720bbfb77f263f5ba3e1a0de50632d43c38d405a0ef9c7e94373cd \ + --hash=sha256:9af7e138baa9b2895cf1f3eb718ac96fc5ae2f8e31fca405e21e0e5cd1643c52 \ + --hash=sha256:9ed05197c5ab071e91bcef28901e97ca168c4ae94510cb67a14cb4931b94255a \ + --hash=sha256:9fc02a6e517c34dcf885ff3b57260b646551083903e3d2c780b4971ce7d4ab7c \ + --hash=sha256:a4f6cae381f21fee1ef0a5cbbbb146680164311157ae618edf3061742d844383 \ + --hash=sha256:aa4018f2d8662ac4d9830445d3d253a11b3e096e8afe20865547137aa1160e93 \ + --hash=sha256:b519f2ecde9a579cad2f4a7057d5bb4e040ad17caab8b5e691ed7a13b9db0be9 \ + --hash=sha256:b8e95d921cc2a1521d4750eedefec9f16031457920a6677edebe9d1b2ad6ae60 \ + --hash=sha256:bb87158dbbb9e5a79effe78d54837599caa16df52d8d35366e06a91723b587ae \ + --hash=sha256:bfa4b2b7d21c5634b62e5f03462243bd705adc1a21806b5356b8ce06d902e160 \ + --hash=sha256:c683be38a9bf4024c223929b4cd2f0a0858c94e9dc8b36d7eaa5a48ce9323a6f \ + --hash=sha256:cb63055739808144b541986291679d643bae58755d0eb082157c4d4c04443905 \ + --hash=sha256:d0f0806de1161c7f248e4c183633ee7a58dfe45c2b77ddf0136e2e7ad0650b1b \ + --hash=sha256:db030140d0da2368319e2f23655df3baec278c7e0078ecbe051eaf609a69382c \ + --hash=sha256:de156c18b0c638aaee3be6ad650c8ba7dec94ed4bac26403aec3dce95ffe9407 \ + --hash=sha256:df85096fcac7cea8aa5bd84b7a39c4cdbf556b93669bb4772eb96aacd3222a4e \ + --hash=sha256:e312ddc2d8bec1a23306a661ad52734f984c9aad5d8f126ebb222a778d95407d \ + --hash=sha256:eeed386971bb8afc3ec45593df6a1154d680d87be1209ef8e782e44f85f47e64 \ + --hash=sha256:ef3e8aca2261f7f07436d4e2111556c1fb9bf1f9cfcdf35262743ccdee1b6ce9 \ + --hash=sha256:f14a6e4f700dfd30ff8f0e6695f944affc16ae5a1e738666b3fae4e44b65637e \ + --hash=sha256:f1c684c0d9226d04cadafced620a46ab38c346d0780eaac7448da96bf12066a3 \ + --hash=sha256:f381ae3ad6a5eb27aad8d810438937d8228977067c54e0bd456fce7e11fdbf3d \ + --hash=sha256:f6263b85261b62471cb97b7505df72d72b8b62e5e22d8184924871a6155b4dbf \ + --hash=sha256:f965707da2b48a33128615bcfebedd215a3a30e346447e885bb3da37a143177a # via tavern (pyproject.toml) -httplib2==0.21.0 \ - --hash=sha256:987c8bb3eb82d3fa60c68699510a692aa2ad9c4bd4f123e51dfb1488c14cdd01 \ - --hash=sha256:fc144f091c7286b82bec71bdbd9b27323ba709cc612568d3000893bfd9cb4b34 +httplib2==0.22.0 \ + --hash=sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc \ + --hash=sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81 # via # google-api-python-client # google-auth-httplib2 -identify==2.5.10 \ - --hash=sha256:dce9e31fee7dbc45fea36a9e855c316b8fbf807e65a862f160840bb5a2bf5dfd \ - --hash=sha256:fb7c2feaeca6976a3ffa31ec3236a6911fbc51aec9acc111de2aed99f244ade2 +identify==2.5.29 \ + --hash=sha256:24437fbf6f4d3fe6efd0eb9d67e24dd9106db99af5ceb27996a5f7895f24bf1b \ + --hash=sha256:d43d52b86b15918c137e3a74fff5224f60385cd0e9c38e99d07c257f02f151a5 # via pre-commit idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -importlib-metadata==5.2.0 \ - --hash=sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f \ - --hash=sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd +imagesize==1.4.1 \ + --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ + --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a + # via sphinx +importlib-metadata==6.8.0 \ + --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ + --hash=sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743 # via - # flask # keyring # twine -iniconfig==1.1.1 \ - --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ - --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 # via pytest itsdangerous==2.1.2 \ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ @@ -491,9 +540,9 @@ itsdangerous==2.1.2 \ # via # flask # tavern (pyproject.toml) -jaraco-classes==3.2.3 \ - --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \ - --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a +jaraco-classes==3.3.0 \ + --hash=sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb \ + --hash=sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621 # via keyring jeepney==0.8.0 \ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \ @@ -504,7 +553,9 @@ jeepney==0.8.0 \ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - # via flask + # via + # flask + # sphinx jmespath==1.0.1 \ --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe @@ -512,199 +563,242 @@ jmespath==1.0.1 \ jsonschema==3.2.0 \ --hash=sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163 \ --hash=sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a - # via - # docker-compose - # tavern (pyproject.toml) -keyring==23.13.1 \ - --hash=sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd \ - --hash=sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678 + # via tavern (pyproject.toml) +keyring==24.2.0 \ + --hash=sha256:4901caaf597bfd3bbd78c9a0c7c4c29fcd8310dab2cffefe749e916b6527acd6 \ + --hash=sha256:ca0746a19ec421219f4d713f848fa297a661a8a8c1504867e55bfb5e09091509 # via twine -markupsafe==2.1.1 \ - --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \ - --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \ - --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \ - --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \ - --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \ - --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \ - --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \ - --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \ - --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \ - --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \ - --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \ - --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \ - --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \ - --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \ - --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \ - --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \ - --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \ - --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \ - --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \ - --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \ - --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \ - --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \ - --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \ - --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \ - --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \ - --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \ - --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \ - --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \ - --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \ - --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \ - --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \ - --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \ - --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \ - --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \ - --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \ - --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \ - --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \ - --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \ - --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \ - --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7 +markdown==3.4.4 \ + --hash=sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6 \ + --hash=sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941 + # via sphinx-markdown-tables +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb \ + --hash=sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939 \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007 \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 \ + --hash=sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11 # via # jinja2 # werkzeug -more-itertools==9.0.0 \ - --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \ - --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +more-itertools==10.1.0 \ + --hash=sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a \ + --hash=sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6 # via jaraco-classes -msgpack==1.0.4 \ - --hash=sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467 \ - --hash=sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae \ - --hash=sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92 \ - --hash=sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef \ - --hash=sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624 \ - --hash=sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227 \ - --hash=sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88 \ - --hash=sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9 \ - --hash=sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8 \ - --hash=sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd \ - --hash=sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6 \ - --hash=sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55 \ - --hash=sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e \ - --hash=sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2 \ - --hash=sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44 \ - --hash=sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6 \ - --hash=sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9 \ - --hash=sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab \ - --hash=sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae \ - --hash=sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa \ - --hash=sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9 \ - --hash=sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e \ - --hash=sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250 \ - --hash=sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce \ - --hash=sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075 \ - --hash=sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236 \ - --hash=sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae \ - --hash=sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e \ - --hash=sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f \ - --hash=sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08 \ - --hash=sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6 \ - --hash=sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d \ - --hash=sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43 \ - --hash=sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1 \ - --hash=sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6 \ - --hash=sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0 \ - --hash=sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c \ - --hash=sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff \ - --hash=sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db \ - --hash=sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243 \ - --hash=sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661 \ - --hash=sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba \ - --hash=sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e \ - --hash=sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb \ - --hash=sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52 \ - --hash=sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6 \ - --hash=sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1 \ - --hash=sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f \ - --hash=sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da \ - --hash=sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f \ - --hash=sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c \ - --hash=sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8 +msgpack==1.0.5 \ + --hash=sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164 \ + --hash=sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b \ + --hash=sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c \ + --hash=sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf \ + --hash=sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd \ + --hash=sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d \ + --hash=sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c \ + --hash=sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a \ + --hash=sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e \ + --hash=sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd \ + --hash=sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025 \ + --hash=sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5 \ + --hash=sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705 \ + --hash=sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a \ + --hash=sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d \ + --hash=sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb \ + --hash=sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11 \ + --hash=sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f \ + --hash=sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c \ + --hash=sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d \ + --hash=sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea \ + --hash=sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba \ + --hash=sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87 \ + --hash=sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a \ + --hash=sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c \ + --hash=sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080 \ + --hash=sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198 \ + --hash=sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9 \ + --hash=sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a \ + --hash=sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b \ + --hash=sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f \ + --hash=sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437 \ + --hash=sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f \ + --hash=sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7 \ + --hash=sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2 \ + --hash=sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0 \ + --hash=sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48 \ + --hash=sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898 \ + --hash=sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0 \ + --hash=sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57 \ + --hash=sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8 \ + --hash=sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282 \ + --hash=sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1 \ + --hash=sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82 \ + --hash=sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc \ + --hash=sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb \ + --hash=sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6 \ + --hash=sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7 \ + --hash=sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9 \ + --hash=sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c \ + --hash=sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1 \ + --hash=sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed \ + --hash=sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c \ + --hash=sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c \ + --hash=sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77 \ + --hash=sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81 \ + --hash=sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a \ + --hash=sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3 \ + --hash=sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086 \ + --hash=sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9 \ + --hash=sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f \ + --hash=sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b \ + --hash=sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d # via fluent-logger -mypy==0.991 \ - --hash=sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d \ - --hash=sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6 \ - --hash=sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf \ - --hash=sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f \ - --hash=sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813 \ - --hash=sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33 \ - --hash=sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad \ - --hash=sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05 \ - --hash=sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297 \ - --hash=sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06 \ - --hash=sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd \ - --hash=sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243 \ - --hash=sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305 \ - --hash=sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476 \ - --hash=sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711 \ - --hash=sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70 \ - --hash=sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5 \ - --hash=sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461 \ - --hash=sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab \ - --hash=sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c \ - --hash=sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d \ - --hash=sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135 \ - --hash=sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93 \ - --hash=sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648 \ - --hash=sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a \ - --hash=sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb \ - --hash=sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3 \ - --hash=sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372 \ - --hash=sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb \ - --hash=sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef +mypy==1.5.1 \ + --hash=sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315 \ + --hash=sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0 \ + --hash=sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373 \ + --hash=sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a \ + --hash=sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161 \ + --hash=sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275 \ + --hash=sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693 \ + --hash=sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb \ + --hash=sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65 \ + --hash=sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4 \ + --hash=sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb \ + --hash=sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243 \ + --hash=sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14 \ + --hash=sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4 \ + --hash=sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1 \ + --hash=sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a \ + --hash=sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160 \ + --hash=sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25 \ + --hash=sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12 \ + --hash=sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d \ + --hash=sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92 \ + --hash=sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770 \ + --hash=sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2 \ + --hash=sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70 \ + --hash=sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb \ + --hash=sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5 \ + --hash=sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f # via tavern (pyproject.toml) -mypy-extensions==0.4.3 \ - --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ - --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 +mypy-extensions==1.0.0 \ + --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ + --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 # via - # black # mypy # tavern (pyproject.toml) -nodeenv==1.7.0 \ - --hash=sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e \ - --hash=sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b +nh3==0.2.14 \ + --hash=sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873 \ + --hash=sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad \ + --hash=sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5 \ + --hash=sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525 \ + --hash=sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2 \ + --hash=sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e \ + --hash=sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d \ + --hash=sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450 \ + --hash=sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e \ + --hash=sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6 \ + --hash=sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a \ + --hash=sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4 \ + --hash=sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4 \ + --hash=sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6 \ + --hash=sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e \ + --hash=sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75 + # via readme-renderer +nodeenv==1.8.0 \ + --hash=sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2 \ + --hash=sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec # via pre-commit -packaging==22.0 \ - --hash=sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3 \ - --hash=sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3 +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f # via - # black # build - # docker + # pyproject-api # pytest + # sphinx # tox -paho-mqtt==1.5.1 \ - --hash=sha256:9feb068e822be7b3a116324e01fb6028eb1d66412bf98595ae72698965cb1cae +paho-mqtt==1.6.1 \ + --hash=sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f # via tavern (pyproject.toml) -paramiko==2.12.0 \ - --hash=sha256:376885c05c5d6aa6e1f4608aac2a6b5b0548b1add40274477324605903d9cd49 \ - --hash=sha256:b2df1a6325f6996ef55a8789d0462f5b502ea83b3c990cbb5bbe57345c6812c4 - # via docker -pathspec==0.10.3 \ - --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \ - --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6 - # via black -pbr==5.11.0 \ - --hash=sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe \ - --hash=sha256:db2317ff07c84c4c63648c9064a79fe9d9f5c7ce85a9099d4b6258b3db83225a +pbr==5.11.1 \ + --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ + --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 # via stevedore -pep517==0.13.0 \ - --hash=sha256:4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b \ - --hash=sha256:ae69927c5c172be1add9203726d4b84cf3ebad1edcd5f71fcdc746e66e829f59 - # via build -pip-tools==6.12.1 \ - --hash=sha256:88efb7b29a923ffeac0713e6f23ef8529cc6175527d42b93f73756cc94387293 \ - --hash=sha256:f0c0c0ec57b58250afce458e2e6058b1f30a4263db895b7d72fd6311bf1dc6f7 +pip-tools==7.3.0 \ + --hash=sha256:8717693288720a8c6ebd07149c93ab0be1fced0b5191df9e9decd3263e20d85e \ + --hash=sha256:8e9c99127fe024c025b46a0b2d15c7bd47f18f33226cf7330d35493663fc1d1d # via tavern (pyproject.toml) -pkginfo==1.9.2 \ - --hash=sha256:ac03e37e4d601aaee40f8087f63fc4a2a6c9814dda2c8fa6aab1b1829653bdfa \ - --hash=sha256:d580059503f2f4549ad6e4c106d7437356dbd430e2c7df99ee1efe03d75f691e +pkginfo==1.9.6 \ + --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \ + --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046 # via twine -platformdirs==2.6.0 \ - --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \ - --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e +platformdirs==3.10.0 \ + --hash=sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d \ + --hash=sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d # via - # black + # tox # virtualenv pluggy==1.0.0 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 \ @@ -713,28 +807,28 @@ pluggy==1.0.0 \ # allure-python-commons # pytest # tox -pre-commit==2.20.0 \ - --hash=sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7 \ - --hash=sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959 +pre-commit==3.4.0 \ + --hash=sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522 \ + --hash=sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945 # via tavern (pyproject.toml) -proto-plus==1.22.2 \ - --hash=sha256:0e8cda3d5a634d9895b75c573c9352c16486cb75deb0e078b5fda34db4243165 \ - --hash=sha256:de34e52d6c9c6fcd704192f09767cb561bb4ee64e70eede20b0834d841f0be4d +proto-plus==1.22.3 \ + --hash=sha256:a49cd903bc0b6ab41f76bf65510439d56ca76f868adf0274e738bfdd096894df \ + --hash=sha256:fdcd09713cbd42480740d2fe29c990f7fbd885a67efc328aa8be6ee3e9f76a6b # via tavern (pyproject.toml) -protobuf==4.22.0 \ - --hash=sha256:1669cb7524221a8e2d9008d0842453dbefdd0fcdd64d67672f657244867635fb \ - --hash=sha256:29288813aacaa302afa2381db1d6e0482165737b0afdf2811df5fa99185c457b \ - --hash=sha256:47d31bdf58222dd296976aa1646c68c6ee80b96d22e0a3c336c9174e253fd35e \ - --hash=sha256:652d8dfece122a24d98eebfef30e31e455d300efa41999d1182e015984ac5930 \ - --hash=sha256:7c535d126e7dcc714105ab20b418c4fedbd28f8b8afc42b7350b1e317bbbcc71 \ - --hash=sha256:86c3d20428b007537ba6792b475c0853bba7f66b1f60e610d913b77d94b486e4 \ - --hash=sha256:a33a273d21852f911b8bda47f39f4383fe7c061eb1814db2c76c9875c89c2491 \ - --hash=sha256:ab4d043865dd04e6b09386981fe8f80b39a1e46139fb4a3c206229d6b9f36ff6 \ - --hash=sha256:b2fea9dc8e3c0f32c38124790ef16cba2ee0628fe2022a52e435e1117bfef9b1 \ - --hash=sha256:c27f371f0159feb70e6ea52ed7e768b3f3a4c5676c1900a7e51a24740381650e \ - --hash=sha256:c3325803095fb4c2a48649c321d2fbde59f8fbfcb9bfc7a86df27d112831c571 \ - --hash=sha256:e474b63bab0a2ea32a7b26a4d8eec59e33e709321e5e16fb66e766b61b82a95e \ - --hash=sha256:e894e9ae603e963f0842498c4cd5d39c6a60f0d7e4c103df50ee939564298658 +protobuf==4.24.4 \ + --hash=sha256:02212557a76cd99574775a81fefeba8738d0f668d6abd0c6b1d3adcc75503dbe \ + --hash=sha256:1badab72aa8a3a2b812eacfede5020472e16c6b2212d737cefd685884c191085 \ + --hash=sha256:2fa3886dfaae6b4c5ed2730d3bf47c7a38a72b3a1f0acb4d4caf68e6874b947b \ + --hash=sha256:5a70731910cd9104762161719c3d883c960151eea077134458503723b60e3667 \ + --hash=sha256:6b7d2e1c753715dcfe9d284a25a52d67818dd43c4932574307daf836f0071e37 \ + --hash=sha256:80797ce7424f8c8d2f2547e2d42bfbb6c08230ce5832d6c099a37335c9c90a92 \ + --hash=sha256:8e61a27f362369c2f33248a0ff6896c20dcd47b5d48239cb9720134bef6082e4 \ + --hash=sha256:9fee5e8aa20ef1b84123bb9232b3f4a5114d9897ed89b4b8142d81924e05d79b \ + --hash=sha256:b493cb590960ff863743b9ff1452c413c2ee12b782f48beca77c8da3e2ffe9d9 \ + --hash=sha256:b77272f3e28bb416e2071186cb39efd4abbf696d682cbb5dc731308ad37fa6dd \ + --hash=sha256:bffa46ad9612e6779d0e51ae586fde768339b791a50610d85eb162daeb23661e \ + --hash=sha256:dbbed8a56e56cee8d9d522ce844a1379a72a70f453bde6243e3c86c30c2a3d46 \ + --hash=sha256:ec9912d5cb6714a5710e28e592ee1093d68c5ebfeda61983b3f40331da0b1ebb # via # google-api-core # googleapis-common-protos @@ -746,93 +840,93 @@ protobuf==4.22.0 \ py==1.11.0 \ --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \ --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 - # via - # tavern (pyproject.toml) - # tox -pyasn1==0.4.8 \ - --hash=sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d \ - --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba + # via tavern (pyproject.toml) +pyasn1==0.5.0 \ + --hash=sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57 \ + --hash=sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde # via # pyasn1-modules # rsa -pyasn1-modules==0.2.8 \ - --hash=sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e \ - --hash=sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74 +pyasn1-modules==0.3.0 \ + --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ + --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d # via google-auth pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pygments==2.13.0 \ - --hash=sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1 \ - --hash=sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42 +pygments==2.16.1 \ + --hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \ + --hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29 # via # readme-renderer # rich + # sphinx # tavern (pyproject.toml) -pyjwt==2.6.0 \ - --hash=sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd \ - --hash=sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14 +pyjwt==2.7.0 \ + --hash=sha256:ba2b425b15ad5ef12f200dc67dd56af4e26de2331f965c5439994dad075876e1 \ + --hash=sha256:bd6ca4a3c4285c1a2d4349e5a035fdf8fb94e04ccd0fcbe6ba289dae9cc3e074 # via tavern (pyproject.toml) pykwalify==1.8.0 \ --hash=sha256:731dfa87338cca9f559d1fca2bdea37299116e3139b73f78ca90a543722d6651 \ --hash=sha256:796b2ad3ed4cb99b88308b533fb2f559c30fa6efb4fa9fda11347f483d245884 # via tavern (pyproject.toml) -pynacl==1.5.0 \ - --hash=sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858 \ - --hash=sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d \ - --hash=sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93 \ - --hash=sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1 \ - --hash=sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92 \ - --hash=sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff \ - --hash=sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba \ - --hash=sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394 \ - --hash=sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b \ - --hash=sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543 - # via paramiko -pyparsing==3.0.9 \ - --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \ - --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc +pyparsing==3.1.1 \ + --hash=sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb \ + --hash=sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db # via httplib2 -pyrsistent==0.19.2 \ - --hash=sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed \ - --hash=sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb \ - --hash=sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a \ - --hash=sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95 \ - --hash=sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712 \ - --hash=sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73 \ - --hash=sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41 \ - --hash=sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b \ - --hash=sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78 \ - --hash=sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab \ - --hash=sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308 \ - --hash=sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425 \ - --hash=sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2 \ - --hash=sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e \ - --hash=sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6 \ - --hash=sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2 \ - --hash=sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a \ - --hash=sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291 \ - --hash=sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584 \ - --hash=sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a \ - --hash=sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0 \ - --hash=sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770 +pyproject-api==1.6.1 \ + --hash=sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538 \ + --hash=sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675 + # via tox +pyproject-hooks==1.0.0 \ + --hash=sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8 \ + --hash=sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5 + # via build +pyrsistent==0.19.3 \ + --hash=sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8 \ + --hash=sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440 \ + --hash=sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a \ + --hash=sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c \ + --hash=sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3 \ + --hash=sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393 \ + --hash=sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9 \ + --hash=sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da \ + --hash=sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf \ + --hash=sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64 \ + --hash=sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a \ + --hash=sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3 \ + --hash=sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98 \ + --hash=sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2 \ + --hash=sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8 \ + --hash=sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf \ + --hash=sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc \ + --hash=sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7 \ + --hash=sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28 \ + --hash=sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2 \ + --hash=sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b \ + --hash=sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a \ + --hash=sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64 \ + --hash=sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19 \ + --hash=sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1 \ + --hash=sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9 \ + --hash=sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c # via jsonschema -pytest==7.2.0 \ - --hash=sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71 \ - --hash=sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59 +pytest==7.2.2 \ + --hash=sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e \ + --hash=sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4 # via # allure-pytest # pytest-cov # pytest-xdist # tavern (pyproject.toml) -pytest-cov==4.0.0 \ - --hash=sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b \ - --hash=sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470 +pytest-cov==4.1.0 \ + --hash=sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6 \ + --hash=sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a # via tavern (pyproject.toml) -pytest-xdist==3.1.0 \ - --hash=sha256:40fdb8f3544921c5dfcd486ac080ce22870e71d82ced6d2e78fa97c2addd480c \ - --hash=sha256:70a76f191d8a1d2d6be69fc440cdf85f3e4c03c08b520fd5dc5d338d6cf07d89 +pytest-xdist==3.3.1 \ + --hash=sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93 \ + --hash=sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2 # via tavern (pyproject.toml) python-box==6.1.0 \ --hash=sha256:11cbe62f0dace8a6e2a10d210a5e87b99ad1a1286865568862516794c923a988 \ @@ -859,83 +953,93 @@ python-dateutil==2.8.2 \ # via # faker # pykwalify -python-dotenv==0.21.0 \ - --hash=sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5 \ - --hash=sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045 - # via docker-compose -pyyaml==5.4.1 \ - --hash=sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf \ - --hash=sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696 \ - --hash=sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393 \ - --hash=sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77 \ - --hash=sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922 \ - --hash=sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5 \ - --hash=sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8 \ - --hash=sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10 \ - --hash=sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc \ - --hash=sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018 \ - --hash=sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e \ - --hash=sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253 \ - --hash=sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347 \ - --hash=sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183 \ - --hash=sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541 \ - --hash=sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb \ - --hash=sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185 \ - --hash=sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc \ - --hash=sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db \ - --hash=sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa \ - --hash=sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46 \ - --hash=sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122 \ - --hash=sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b \ - --hash=sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63 \ - --hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \ - --hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \ - --hash=sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247 \ - --hash=sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6 \ - --hash=sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0 +pyyaml==6.0.1 \ + --hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \ + --hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \ + --hash=sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206 \ + --hash=sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27 \ + --hash=sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595 \ + --hash=sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62 \ + --hash=sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98 \ + --hash=sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696 \ + --hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \ + --hash=sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867 \ + --hash=sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47 \ + --hash=sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486 \ + --hash=sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6 \ + --hash=sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3 \ + --hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \ + --hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \ + --hash=sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c \ + --hash=sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735 \ + --hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \ + --hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \ + --hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \ + --hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \ + --hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \ + --hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \ + --hash=sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0 \ + --hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \ + --hash=sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c \ + --hash=sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c \ + --hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \ + --hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \ + --hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \ + --hash=sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859 \ + --hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \ + --hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \ + --hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \ + --hash=sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa \ + --hash=sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c \ + --hash=sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585 \ + --hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \ + --hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f # via - # docker-compose # pre-commit # tavern (pyproject.toml) -readme-renderer==37.3 \ - --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \ - --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343 +readme-renderer==42.0 \ + --hash=sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d \ + --hash=sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1 # via twine -requests==2.28.1 \ - --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \ - --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349 +recommonmark==0.7.1 \ + --hash=sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f \ + --hash=sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67 + # via tavern (pyproject.toml) +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 # via - # docker - # docker-compose # flit # google-api-core # requests-toolbelt + # sphinx # tavern (pyproject.toml) # twine -requests-toolbelt==0.10.1 \ - --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \ - --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d +requests-toolbelt==1.0.0 \ + --hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \ + --hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06 # via twine rfc3986==2.0.0 \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c # via twine -rich==12.6.0 \ - --hash=sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e \ - --hash=sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0 +rich==13.5.3 \ + --hash=sha256:87b43e0543149efa1253f485cd845bb7ee54df16c9617b8a893650ab84b4acb6 \ + --hash=sha256:9257b468badc3d347e146a4faa268ff229039d4c2d176ab0cffb4c4fbc73d5d9 # via twine rsa==4.9 \ --hash=sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7 \ --hash=sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21 # via google-auth -ruamel-yaml==0.17.21 \ - --hash=sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7 \ - --hash=sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af +ruamel-yaml==0.17.31 \ + --hash=sha256:098ed1eb6d338a684891a72380277c1e6fc4d4ae0e120de9a447275056dda335 \ + --hash=sha256:3cf153f0047ced526e723097ac615d3009371779432e304dbd5596b6f3a4c777 # via pykwalify ruamel-yaml-clib==0.2.7 \ --hash=sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e \ --hash=sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3 \ --hash=sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5 \ + --hash=sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81 \ --hash=sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497 \ --hash=sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f \ --hash=sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac \ @@ -946,13 +1050,13 @@ ruamel-yaml-clib==0.2.7 \ --hash=sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1 \ --hash=sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072 \ --hash=sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9 \ - --hash=sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5 \ --hash=sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231 \ --hash=sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93 \ --hash=sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b \ --hash=sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb \ --hash=sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f \ --hash=sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307 \ + --hash=sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf \ --hash=sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8 \ --hash=sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b \ --hash=sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b \ @@ -961,31 +1065,15 @@ ruamel-yaml-clib==0.2.7 \ --hash=sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a \ --hash=sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71 \ --hash=sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8 \ + --hash=sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122 \ --hash=sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7 \ --hash=sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80 \ --hash=sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e \ --hash=sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab \ --hash=sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0 \ - --hash=sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646 + --hash=sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646 \ + --hash=sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38 # via ruamel-yaml -ruff==0.0.246 \ - --hash=sha256:2474a805c4244cfaf0390a745a0c5ea9e5452f52fbce0be74930fece8bd40b90 \ - --hash=sha256:449e6632c13902df06ac14ccfc7bad71841ab90bfa64e8cb3f1a2ea91e647df0 \ - --hash=sha256:45cde6f9df94fb393ffeeada2daca279569b9d53d1d95d49b9b5b418fe70bd23 \ - --hash=sha256:468f282e26d1845f4a06dcd76fdc355f4288208e6b97f061951a7ffd6725102e \ - --hash=sha256:529a7d72f48331b97367cd6d42273f3cafa764d9373e74b75561367135958546 \ - --hash=sha256:5839a213a90220845693c95d7e6a19ab26751b9e37047ef8f4a58dc49230c817 \ - --hash=sha256:589aff453085cad28b8d1f517161a6b37a6d359cda419f64c009e0f7ff424d72 \ - --hash=sha256:64af553f8ff4d1f51a24104ac8f81b5ce6df9c230d776fa4dd22db96699efdb0 \ - --hash=sha256:67d5fa9abdcb764a7cee242fb65e303662c9cb80104a2dd0657e96fca8a7c6d8 \ - --hash=sha256:734ff8fef2e7105cf6946e525b3e8cbed035edc9d58c4f47aac7205dbd1e55c0 \ - --hash=sha256:8173a00766b88b47431e8e744f577d06c6c52c0e18181ac29a701a9d5c035b39 \ - --hash=sha256:ca7d1ee44144460ae119a6212aaff77a671a5729d543b981c786c052011cdfe3 \ - --hash=sha256:dd4f58b9295615ebb01563a38a5594fcb4664bb6106b2ccd00b90c0f1d14cf8c \ - --hash=sha256:ebe6052bc87ee51d84af231ccd27e5338fdc30d8bf49e51bdcfceb44c51c5625 \ - --hash=sha256:f6004332134580f0ede29d86a9a16102ba07c25799e0ab9683359216a419366b \ - --hash=sha256:f8403e31e64b15c9b3e2745b0400e2f43eea81493ae0fa85e275ed0800a89c19 - # via tavern (pyproject.toml) secretstorage==3.3.3 \ --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \ --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99 @@ -994,111 +1082,142 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # allure-pytest - # allure-python-commons - # bleach - # dockerpty - # google-auth - # google-auth-httplib2 # jsonschema - # paramiko # python-dateutil - # tox - # websocket-client +snowballstemmer==2.2.0 \ + --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ + --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a + # via sphinx +sphinx==7.2.6 \ + --hash=sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560 \ + --hash=sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5 + # via + # recommonmark + # sphinx-rtd-theme + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-jquery + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml + # tavern (pyproject.toml) +sphinx-markdown-tables==0.0.17 \ + --hash=sha256:2bd0c30779653e4dd120300cbd9ca412c480738cc2241f6dea477a883f299e04 \ + --hash=sha256:6bc6d3d400eaccfeebd288446bc08dd83083367c58b85d40fe6c12d77ef592f1 + # via tavern (pyproject.toml) +sphinx-rtd-theme==1.3.0 \ + --hash=sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0 \ + --hash=sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931 + # via tavern (pyproject.toml) +sphinxcontrib-applehelp==1.0.7 \ + --hash=sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d \ + --hash=sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa + # via sphinx +sphinxcontrib-devhelp==1.0.5 \ + --hash=sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212 \ + --hash=sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f + # via sphinx +sphinxcontrib-htmlhelp==2.0.4 \ + --hash=sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a \ + --hash=sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9 + # via sphinx +sphinxcontrib-jquery==4.1 \ + --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \ + --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae + # via sphinx-rtd-theme +sphinxcontrib-jsmath==1.0.1 \ + --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ + --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 + # via sphinx +sphinxcontrib-qthelp==1.0.6 \ + --hash=sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d \ + --hash=sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4 + # via sphinx +sphinxcontrib-serializinghtml==1.1.9 \ + --hash=sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54 \ + --hash=sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1 + # via sphinx stevedore==4.1.1 \ --hash=sha256:7f8aeb6e3f90f96832c301bff21a7eb5eefbe894c88c506483d355565d88cc1a \ --hash=sha256:aa6436565c069b2946fe4ebff07f5041e0c8bf18c7376dd29edf80cf7d524e4e # via tavern (pyproject.toml) -texttable==1.6.7 \ - --hash=sha256:290348fb67f7746931bcdfd55ac7584ecd4e5b0846ab164333f0794b121760f2 \ - --hash=sha256:b7b68139aa8a6339d2c320ca8b1dc42d13a7831a346b446cb9eb385f0c76310c - # via docker-compose -toml==0.10.2 \ - --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ - --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f - # via pre-commit tomli==2.0.1 \ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f # via - # black # build # coverage # mypy - # pep517 + # pip-tools + # pyproject-api + # pyproject-hooks # pytest # tox tomli-w==1.0.0 \ --hash=sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463 \ --hash=sha256:f463434305e0336248cac9c2dc8076b707d8a12d019dd349f5c1e382dd1ae1b9 # via flit -tox==3.28.0 \ - --hash=sha256:57b5ab7e8bb3074edc3c0c0b4b192a4f3799d3723b2c5b76f1fa9f2d40316eea \ - --hash=sha256:d0d28f3fe6d6d7195c27f8b054c3e99d5451952b54abdae673b71609a581f640 - # via - # tavern (pyproject.toml) - # tox-travis -tox-travis==0.12 \ - --hash=sha256:442c96b078333c94e272d0e90e4582e35e0529ea98bcd2f7f96053d690c4e7a4 \ - --hash=sha256:465cd8f71ad878962a3fce0e9e2e213994e0ae4e0c30f87fe6af1b04ea282dc4 +tox==4.6.3 \ + --hash=sha256:2946a0bb38924c3a9f9575c7fb4ca1f4c11a7c69c61592f176778892155cb50c \ + --hash=sha256:9e2c5091a117d03b583c57c4c40aecd068099c17d40520e7b165e85c19334534 # via tavern (pyproject.toml) twine==4.0.2 \ --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \ --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8 # via tavern (pyproject.toml) -types-pyyaml==6.0.12.2 \ - --hash=sha256:1e94e80aafee07a7e798addb2a320e32956a373f376655128ae20637adb2655b \ - --hash=sha256:6840819871c92deebe6a2067fb800c11b8a063632eb4e3e755914e7ab3604e83 +types-pyyaml==6.0.12.11 \ + --hash=sha256:7d340b19ca28cddfdba438ee638cd4084bde213e501a3978738543e27094775b \ + --hash=sha256:a461508f3096d1d5810ec5ab95d7eeecb651f3a15b71959999988942063bf01d # via tavern (pyproject.toml) -typing-extensions==4.4.0 \ - --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ - --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e - # via - # black - # mypy +types-requests==2.31.0.2 \ + --hash=sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a \ + --hash=sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40 + # via tavern (pyproject.toml) +types-setuptools==68.2.0.0 \ + --hash=sha256:77edcc843e53f8fc83bb1a840684841f3dc804ec94562623bfa2ea70d5a2ba1b \ + --hash=sha256:a4216f1e2ef29d089877b3af3ab2acf489eb869ccaf905125c69d2dc3932fd85 + # via tavern (pyproject.toml) +types-urllib3==1.26.25.14 \ + --hash=sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f \ + --hash=sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e + # via types-requests +typing-extensions==4.8.0 \ + --hash=sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0 \ + --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef + # via mypy uritemplate==4.1.1 \ --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e # via google-api-python-client -urllib3==1.26.13 \ - --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \ - --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8 +urllib3==2.0.7 \ + --hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \ + --hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e # via - # docker # requests # twine -virtualenv==20.17.1 \ - --hash=sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4 \ - --hash=sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058 +virtualenv==20.24.5 \ + --hash=sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b \ + --hash=sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752 # via # pre-commit # tox -webencodings==0.5.1 \ - --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \ - --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923 - # via bleach -websocket-client==0.59.0 \ - --hash=sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32 \ - --hash=sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c - # via - # docker - # docker-compose -werkzeug==2.2.3 \ - --hash=sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe \ - --hash=sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612 +werkzeug==2.3.7 \ + --hash=sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8 \ + --hash=sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528 # via flask -wheel==0.38.4 \ - --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \ - --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8 +wheel==0.41.2 \ + --hash=sha256:0c5ac5ff2afb79ac23ab82bab027a0be7b5dbcf2e54dc50efe4bf507de1f7985 \ + --hash=sha256:75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8 # via # pip-tools # tavern (pyproject.toml) -zipp==3.11.0 \ - --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \ - --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766 +zipp==3.16.2 \ + --hash=sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0 \ + --hash=sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147 # via importlib-metadata # WARNING: The following packages were not pinned, but pip requires them to be -# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag. +# pinned when the requirements file includes hashes and the requirement is not +# satisfied by a package already installed. Consider using the --allow-unsafe flag. # pip # setuptools diff --git a/scripts/smoke.bash b/scripts/smoke.bash index cc581927d..ea4be2e46 100755 --- a/scripts/smoke.bash +++ b/scripts/smoke.bash @@ -2,7 +2,8 @@ set -ex -ruff tavern +pre-commit run ruff --all-files +pre-commit run black --all-files # Separate as isort can interfere with other testenvs tox --parallel -c tox.ini \ diff --git a/tavern/__init__.py b/tavern/__init__.py index 536d021df..a39782e8b 100644 --- a/tavern/__init__.py +++ b/tavern/__init__.py @@ -1,2 +1,2 @@ """Stop pytest warning about module already imported: PYTEST_DONT_REWRITE""" -__version__ = "2.0.7" +__version__ = "2.5.0" diff --git a/tavern/_core/dict_util.py b/tavern/_core/dict_util.py index 277dc252e..fd0ab1c7a 100644 --- a/tavern/_core/dict_util.py +++ b/tavern/_core/dict_util.py @@ -1,4 +1,5 @@ import contextlib +import functools import logging import os import re @@ -91,21 +92,34 @@ def _attempt_find_include(to_format: str, box_vars: box.Box): return formatter.convert_field(would_replace, conversion) # type: ignore -def format_keys(val, variables: Mapping, no_double_format: bool = True): +def format_keys( + val, + variables: Mapping, + *, + no_double_format: bool = True, + dangerously_ignore_string_format_errors: bool = False, +): """recursively format a dictionary with the given values Args: - val (object): Input dictionary to format - variables (dict): Dictionary of keys to format it with - no_double_format (bool): Whether to use the 'inner formatted string' class to avoid double formatting + val: Input dictionary to format + variables: Dictionary of keys to format it with + no_double_format: Whether to use the 'inner formatted string' class to avoid double formatting This is required if passing something via pytest-xdist, such as markers: https://github.com/taverntesting/tavern/issues/431 + dangerously_ignore_string_format_errors: whether to ignore any string formatting errors. This will result + in broken output, only use for debugging purposes. Returns: - str,int,list,dict: recursively formatted values + recursively formatted values """ formatted = val + format_keys_ = functools.partial( + format_keys, + dangerously_ignore_string_format_errors=dangerously_ignore_string_format_errors, + ) + if not isinstance(variables, Box): box_vars = Box(variables) else: @@ -115,22 +129,26 @@ def format_keys(val, variables: Mapping, no_double_format: bool = True): formatted = {} # formatted = {key: format_keys(val[key], box_vars) for key in val} for key in val: - formatted[key] = format_keys(val[key], box_vars) + formatted[key] = format_keys_(val[key], box_vars) elif isinstance(val, (list, tuple)): - formatted = [format_keys(item, box_vars) for item in val] + formatted = [format_keys_(item, box_vars) for item in val] # type: ignore elif isinstance(formatted, FormattedString): logger.debug("Already formatted %s, not double-formatting", formatted) elif isinstance(val, str): - formatted = _check_and_format_values(val, box_vars) + try: + formatted = _check_and_format_values(val, box_vars) + except exceptions.MissingFormatError: + if not dangerously_ignore_string_format_errors: + raise if no_double_format: - formatted = FormattedString(formatted) + formatted = FormattedString(formatted) # type: ignore elif isinstance(val, TypeConvertToken): logger.debug("Got type convert token '%s'", val) if isinstance(val, ForceIncludeToken): formatted = _attempt_find_include(val.value, box_vars) else: - value = format_keys(val.value, box_vars) + value = format_keys_(val.value, box_vars) formatted = val.constructor(value) else: logger.debug("Not formatting something of type '%s'", type(formatted)) diff --git a/tavern/_core/exceptions.py b/tavern/_core/exceptions.py index ba4fd1f82..dcdc7fbc9 100644 --- a/tavern/_core/exceptions.py +++ b/tavern/_core/exceptions.py @@ -1,5 +1,18 @@ +from typing import TYPE_CHECKING, Dict, Optional + +if TYPE_CHECKING: + from tavern._core.pytest.config import TestConfig + + class TavernException(Exception): - """Base exception""" + """Base exception + + Fields are internal and might change in future + """ + + stage: Optional[Dict] + test_block_config: Optional["TestConfig"] + is_final: bool = False class BadSchemaError(TavernException): diff --git a/tavern/_core/loader.py b/tavern/_core/loader.py index 1c33cef36..7104fbe56 100644 --- a/tavern/_core/loader.py +++ b/tavern/_core/loader.py @@ -445,7 +445,7 @@ def load_single_document_yaml(filename: os.PathLike) -> dict: with open(filename, "r", encoding="utf-8") as fileobj: try: - contents = yaml.load(fileobj, Loader=IncludeLoader) # noqa + contents = yaml.load(fileobj, Loader=IncludeLoader) # type:ignore # noqa except yaml.composer.ComposerError as e: msg = "Expected only one document in this file but found multiple" raise exceptions.UnexpectedDocumentsError(msg) from e diff --git a/tavern/_core/pytest/error.py b/tavern/_core/pytest/error.py index e63677f21..6b5f12ad3 100644 --- a/tavern/_core/pytest/error.py +++ b/tavern/_core/pytest/error.py @@ -152,7 +152,9 @@ def _print_formatted_stage(self, tw: TerminalWriter, stage: Mapping) -> None: keys = self._get_available_format_keys() # Format stage variables recursively - formatted_stage = format_keys(stage, keys) + formatted_stage = format_keys( + stage, keys, dangerously_ignore_string_format_errors=True + ) # Replace formatted strings with strings for dumping formatted_stage = prepare_yaml(formatted_stage) diff --git a/tavern/_core/pytest/file.py b/tavern/_core/pytest/file.py index 0733cb01e..b014a58fd 100644 --- a/tavern/_core/pytest/file.py +++ b/tavern/_core/pytest/file.py @@ -347,7 +347,10 @@ def collect(self) -> Iterator[YamlItem]: try: # Convert to a list so we can catch parser exceptions all_tests = list( - yaml.load_all(self.path.open(encoding="utf-8"), Loader=IncludeLoader) + yaml.load_all( + self.path.open(encoding="utf-8"), + Loader=IncludeLoader, # type:ignore + ) ) except yaml.parser.ParserError as e: raise exceptions.BadSchemaError from e diff --git a/tavern/_core/pytest/hooks.py b/tavern/_core/pytest/hooks.py index 2ee77b3be..9a83ee9e9 100644 --- a/tavern/_core/pytest/hooks.py +++ b/tavern/_core/pytest/hooks.py @@ -43,14 +43,14 @@ def pytest_collect_file(parent, path: os.PathLike): format: "%(asctime)s [%(levelname)s]: (%(name)s:%(lineno)d) %(message)s" style: "%" datefmt: "%X" - + handlers: stderr: class : logging.StreamHandler level : DEBUG formatter: default stream : ext://sys.stderr - + loggers: tavern: handlers: diff --git a/tavern/_core/pytest/item.py b/tavern/_core/pytest/item.py index c1013dab0..e48f5c605 100644 --- a/tavern/_core/pytest/item.py +++ b/tavern/_core/pytest/item.py @@ -223,25 +223,23 @@ def runtest(self) -> None: if msg := xfail.get("run"): if msg not in str(e): raise Exception( - f"error message did not match: expected '{msg}', got '{str(e)}'" + f"error message did not match: expected '{msg}', got '{e!s}'" ) from e logger.info("xfailing test when running") self.add_marker(pytest.mark.xfail, True) else: logger.warning("internal error checking 'xfail'") - elif xfail == "run": + elif xfail == "run" and not e.is_final: logger.info("xfailing test when running") self.add_marker(pytest.mark.xfail, True) + elif xfail == "finally" and e.is_final: + logger.info("xfailing test when finalising") + self.add_marker(pytest.mark.xfail, True) + raise else: if xfail: raise Exception("internal: xfail test did not fail '{}'".format(xfail)) - # else: - # if xfail: - # logger.error("Expected test to fail") - # raise exceptions.TestFailError( - # "Expected test to fail at {} stage".format(xfail) - # ) finally: call_hook( self.global_cfg, diff --git a/tavern/_core/run.py b/tavern/_core/run.py index b950b1d53..50a353551 100644 --- a/tavern/_core/run.py +++ b/tavern/_core/run.py @@ -1,15 +1,17 @@ import copy +import dataclasses import functools import logging import pathlib from contextlib import ExitStack from copy import deepcopy -from typing import List, Mapping, MutableMapping +from typing import Dict, List, Mapping, MutableMapping import box from tavern._core import exceptions from tavern._core.plugins import ( + PluginHelperBase, get_expected, get_extra_sessions, get_request_type, @@ -23,6 +25,7 @@ from .report import attach_stage_content, wrap_step from .strtobool import strtobool from .testhelpers import delay, retry +from .tincture import Tinctures, get_stage_tinctures logger = logging.getLogger(__name__) @@ -134,7 +137,7 @@ def run_test( # Initialise test config for this test with the global configuration before # starting test_block_config = global_cfg.copy() - default_global_stricness = global_cfg.strict + default_global_strictness = global_cfg.strict tavern_box = get_tavern_box() @@ -174,41 +177,34 @@ def getonly(stage): has_only = any(getonly(stage) for stage in test_spec["stages"]) - # Run tests in a path in order - for idx, stage in enumerate(test_spec["stages"]): - if stage.get("skip"): - continue - if has_only and not getonly(stage): - continue - - test_block_config = test_block_config.with_strictness( - default_global_stricness - ) - test_block_config = test_block_config.with_strictness( - _calculate_stage_strictness(stage, test_block_config, test_spec) - ) - - # Wrap run_stage with retry helper - run_stage_with_retries = retry(stage, test_block_config)(run_stage) - - partial = functools.partial( - run_stage_with_retries, sessions, stage, test_block_config - ) - - allure_name = "Stage {}: {}".format( - idx, format_keys(stage["name"], test_block_config.variables) - ) - step = wrap_step(allure_name, partial) - - try: - step() - except exceptions.TavernException as e: - e.stage = stage # type: ignore - e.test_block_config = test_block_config # type: ignore - raise + runner = _TestRunner( + default_global_strictness, sessions, test_block_config, test_spec + ) - if getonly(stage): - break + try: + # Run tests in a path in order + for idx, stage in enumerate(test_spec["stages"]): + if stage.get("skip"): + continue + if has_only and not getonly(stage): + continue + + runner.run_stage(idx, stage) + + if getonly(stage): + break + finally: + finally_stages = test_spec.get("finally", []) + if not isinstance(finally_stages, list): + raise exceptions.BadSchemaError( + f"finally block should be a list of dicts but was {type(finally_stages)}" + ) + for idx, stage in enumerate(finally_stages): + if not isinstance(stage, dict): + raise exceptions.BadSchemaError( + f"finally block should be a dict but was {type(stage)}" + ) + runner.run_stage(idx, stage, is_final=True) def _calculate_stage_strictness( @@ -273,49 +269,85 @@ def update_stage_options(new_option): return new_strict -def run_stage( - sessions: Mapping, - stage: Mapping, - test_block_config: TestConfig, -) -> None: - """Run one stage from the test +@dataclasses.dataclass(frozen=True) +class _TestRunner: + default_global_strictness: StrictLevel + sessions: Dict[str, PluginHelperBase] + test_block_config: TestConfig + test_spec: Mapping - Args: - sessions: Dictionary of relevant 'session' objects used for this test - stage: specification of stage to be run - test_block_config: available variables for test - """ - stage = copy.deepcopy(stage) - name = stage["name"] + def run_stage(self, idx: int, stage, *, is_final: bool = False): + tinctures = get_stage_tinctures(stage, self.test_spec) - attach_stage_content(stage) + stage_config = self.test_block_config.with_strictness( + self.default_global_strictness + ) + stage_config = stage_config.with_strictness( + _calculate_stage_strictness(stage, stage_config, self.test_spec) + ) + # Wrap run_stage with retry helper + run_stage_with_retries = retry(stage, stage_config)(self.wrapped_run_stage) + partial = functools.partial( + run_stage_with_retries, stage, stage_config, tinctures + ) + allure_name = "Stage {}: {}".format( + idx, format_keys(stage["name"], stage_config.variables) + ) + step = wrap_step(allure_name, partial) - r = get_request_type(stage, test_block_config, sessions) + try: + step() + except exceptions.TavernException as e: + e.stage = stage + e.test_block_config = stage_config + e.is_final = is_final + raise - tavern_box = test_block_config.variables["tavern"] - tavern_box.update(request_vars=r.request_vars) + def wrapped_run_stage( + self, stage: dict, stage_config: TestConfig, tinctures: Tinctures + ): + """Run one stage from the test - expected = get_expected(stage, test_block_config, sessions) + Args: + stage: specification of stage to be run + stage_config: available variables for test + tinctures: tinctures for this stage/test + """ + stage = copy.deepcopy(stage) + name = stage["name"] - delay(stage, "before", test_block_config.variables) + attach_stage_content(stage) - logger.info("Running stage : %s", name) + r = get_request_type(stage, stage_config, self.sessions) - call_hook( - test_block_config, - "pytest_tavern_beta_before_every_request", - request_args=r.request_vars, - ) + tavern_box = stage_config.variables["tavern"] + tavern_box.update(request_vars=r.request_vars) + + expected = get_expected(stage, stage_config, self.sessions) + + delay(stage, "before", stage_config.variables) + + logger.info("Running stage : %s", name) + + call_hook( + stage_config, + "pytest_tavern_beta_before_every_request", + request_args=r.request_vars, + ) + + verifiers = get_verifiers(stage, stage_config, self.sessions, expected) + + tinctures.start_tinctures(stage) - verifiers = get_verifiers(stage, test_block_config, sessions, expected) + response = r.run() - response = r.run() + tinctures.end_tinctures(expected, response) - for response_type, response_verifiers in verifiers.items(): - logger.debug("Running verifiers for %s", response_type) - for v in response_verifiers: - saved = v.verify(response) - test_block_config.variables.update(saved) + for response_type, response_verifiers in verifiers.items(): + logger.debug("Running verifiers for %s", response_type) + for v in response_verifiers: + saved = v.verify(response) + stage_config.variables.update(saved) - tavern_box.pop("request_vars") - delay(stage, "after", test_block_config.variables) + tavern_box.pop("request_vars") + delay(stage, "after", stage_config.variables) diff --git a/tavern/_core/schema/extensions.py b/tavern/_core/schema/extensions.py index 2fdec69a4..7bc2756bf 100644 --- a/tavern/_core/schema/extensions.py +++ b/tavern/_core/schema/extensions.py @@ -392,6 +392,8 @@ def validate_cert_tuple_or_str(value, rule_obj, path) -> bool: def validate_file_spec(value, rule_obj, path) -> bool: """Validate file upload arguments""" + logger = get_pykwalify_logger("tavern.schema.extensions") + if not isinstance(value, dict): raise BadSchemaError( "File specification must be a mapping of file names to file specs, got {}".format( @@ -399,11 +401,16 @@ def validate_file_spec(value, rule_obj, path) -> bool: ) ) + if value.get("file_path"): + # If the file spec was a list, this function will be called for each item. Just call this + # function recursively to check each item. + return validate_file_spec({"file": value}, rule_obj, path) + for _, filespec in value.items(): if isinstance(filespec, str): file_path = filespec elif isinstance(filespec, dict): - valid = {"file_path", "content_type", "content_encoding"} + valid = {"file_path", "content_type", "content_encoding", "form_field_name"} extra = set(filespec.keys()) - valid if extra: raise BadSchemaError( @@ -423,7 +430,7 @@ def validate_file_spec(value, rule_obj, path) -> bool: if not os.path.exists(file_path): if re.search(".*{.+}.*", file_path): - get_pykwalify_logger("tavern.schemas.extensions").debug( + logger.debug( "Could not find file path, but it might be a format variable, so continuing" ) else: diff --git a/tavern/_core/schema/jsonschema.py b/tavern/_core/schema/jsonschema.py index cc6063908..595178436 100644 --- a/tavern/_core/schema/jsonschema.py +++ b/tavern/_core/schema/jsonschema.py @@ -164,6 +164,8 @@ def verify_jsonschema(to_verify, schema) -> None: """ ) + logger.debug("original exception from jsonschema: %s", e) + msg = "\n---\n" + "\n---\n".join([str(i) for i in real_context]) raise BadSchemaError(msg) from e diff --git a/tavern/_core/schema/tests.jsonschema.yaml b/tavern/_core/schema/tests.jsonschema.yaml index 2053cb4a9..428f62d46 100644 --- a/tavern/_core/schema/tests.jsonschema.yaml +++ b/tavern/_core/schema/tests.jsonschema.yaml @@ -77,8 +77,13 @@ definitions: properties: url: - type: string description: URL to make request to + oneOf: + - type: string + - type: object + properties: + "$ext": + $ref: "#/definitions/verify_block" cert: description: Certificate to use - either a path to a certificate and key in one file, or a two item list containing the certificate and key separately @@ -162,7 +167,9 @@ definitions: description: Path to a file to upload as the request body files: - type: object + oneOf: + - type: object + - type: array description: Files to send as part of the request clear_session_cookies: @@ -359,6 +366,12 @@ definitions: - name properties: + tinctures: + type: array + description: Tinctures for stage + items: + $ref: "#/definitions/verify_block" + id: type: string description: ID of stage for use in stage references @@ -431,6 +444,7 @@ properties: enum: - verify - run + - finally - type: object properties: verify: @@ -464,6 +478,12 @@ properties: - key - vals + tinctures: + type: array + description: Tinctures for whole test + items: + $ref: "#/definitions/verify_block" + strict: $ref: "#/definitions/strict_block" @@ -482,3 +502,10 @@ properties: oneOf: - $ref: "#/definitions/stage" - $ref: "#/definitions/stage_ref" + + finally: + type: array + description: Stages to run after test finishes + + items: + $ref: "#/definitions/stage" diff --git a/tavern/_core/schema/tests.schema.yaml b/tavern/_core/schema/tests.schema.yaml index ea1494aac..c2388b31f 100644 --- a/tavern/_core/schema/tests.schema.yaml +++ b/tavern/_core/schema/tests.schema.yaml @@ -28,6 +28,10 @@ schema;stage: required: true func: verify_oneof_id_name mapping: + tinctures: + func: validate_extensions + type: any + id: type: str required: false @@ -356,6 +360,7 @@ mapping: enum: - verify - run + - finally strict: func: check_strict_key diff --git a/tavern/_core/testhelpers.py b/tavern/_core/testhelpers.py index 095c34782..42247f6c0 100644 --- a/tavern/_core/testhelpers.py +++ b/tavern/_core/testhelpers.py @@ -10,13 +10,13 @@ logger = logging.getLogger(__name__) -def delay(stage, when, variables) -> None: +def delay(stage: Mapping, when: str, variables: Mapping) -> None: """Look for delay_before/delay_after and sleep Args: - stage (dict): test stage - when (str): 'before' or 'after' - variables (dict): Variables to format with + stage: test stage + when: 'before' or 'after' + variables: Variables to format with """ try: diff --git a/tavern/_core/tincture.py b/tavern/_core/tincture.py new file mode 100644 index 000000000..ae33dbf90 --- /dev/null +++ b/tavern/_core/tincture.py @@ -0,0 +1,81 @@ +import collections.abc +import inspect +import logging +from typing import Any, List + +from tavern._core import exceptions +from tavern._core.extfunctions import get_wrapped_response_function + +logger = logging.getLogger(__name__) + + +class Tinctures: + def __init__(self, tinctures: List[Any]): + self._tinctures = tinctures + self._needs_response: List[Any] = [] + + def start_tinctures(self, stage: collections.abc.Mapping): + results = [t(stage) for t in self._tinctures] + self._needs_response = [] + + for r in results: + if inspect.isgenerator(r): + # Store generator and start it + self._needs_response.append(r) + next(r) + + def end_tinctures(self, expected: collections.abc.Mapping, response) -> None: + """ + Send the response object to any tinctures that want it + + Args: + response: The response from 'run' for the stage + """ + if self._needs_response is None: + raise RuntimeError( + "should not be called before accumulating tinctures which need a response" + ) + + for n in self._needs_response: + try: + n.send((expected, response)) + except StopIteration: + pass + else: + raise RuntimeError("Tincture had more than one yield") + + +def get_stage_tinctures( + stage: collections.abc.Mapping, test_spec: collections.abc.Mapping +) -> Tinctures: + """Get tinctures for stage + + Args: + stage: Stage + test_spec: Whole test spec + """ + stage_tinctures = [] + + def add_tinctures_from_block(maybe_tinctures, blockname: str): + logger.debug("Trying to add tinctures from %s", blockname) + + def inner_yield(): + if maybe_tinctures is not None: + if isinstance(maybe_tinctures, list): + for vf in maybe_tinctures: + yield get_wrapped_response_function(vf) + elif isinstance(maybe_tinctures, dict): + yield get_wrapped_response_function(maybe_tinctures) + elif maybe_tinctures is not None: + raise exceptions.BadSchemaError( + "Badly formatted 'tinctures' block in {}".format(blockname) + ) + + stage_tinctures.extend(inner_yield()) + + add_tinctures_from_block(test_spec.get("tinctures"), "test") + add_tinctures_from_block(stage.get("tinctures"), "stage") + + logger.debug("%d tinctures for stage %s", len(stage_tinctures), stage["name"]) + + return Tinctures(stage_tinctures) diff --git a/tavern/_plugins/grpc/client.py b/tavern/_plugins/grpc/client.py index 429110633..1ca6f8401 100644 --- a/tavern/_plugins/grpc/client.py +++ b/tavern/_plugins/grpc/client.py @@ -24,7 +24,7 @@ with warnings.catch_warnings(): warnings.simplefilter("ignore") - warnings.warn("deprecated", DeprecationWarning) + warnings.warn("deprecated", DeprecationWarning) # noqa: B028 @functools.lru_cache @@ -72,7 +72,7 @@ def _generate_proto_import(source: str, output: str): protoc_command = [protoc, "-I" + source, "--python_out=" + output] protoc_command.extend(protos) - call = subprocess.run(protoc_command, capture_output=True) + call = subprocess.run(protoc_command, capture_output=True) # noqa: S603 if call.returncode != 0: logger.error(f"Error calling '{protoc_command}'") raise exceptions.ProtoCompilerException(call.stderr.decode("utf8")) diff --git a/tavern/_plugins/mqtt/client.py b/tavern/_plugins/mqtt/client.py index 49c73c565..84afaceca 100644 --- a/tavern/_plugins/mqtt/client.py +++ b/tavern/_plugins/mqtt/client.py @@ -36,10 +36,6 @@ logger = logging.getLogger(__name__) -def root_topic(topic): - return topic.split("+")[0].split("#")[0] - - @dataclasses.dataclass class _Subscription: topic: str @@ -304,16 +300,15 @@ def _on_message(client, userdata, message) -> None: logger.info("Received mqtt message on %s", message.topic) - sanitised = root_topic(message.topic) - try: - userdata["_subscribed"][ - userdata["_subscription_mappings"][sanitised] - ].queue.put(message) - except KeyError as e: - raise exceptions.MQTTTopicException( - "Message received on unregistered topic: {}".format(message.topic) - ) from e + for sub_topic, sub_id in userdata["_subscription_mappings"].items(): + if paho.topic_matches_sub(sub_topic, message.topic): + userdata["_subscribed"][sub_id].queue.put(message) + break + else: + raise exceptions.MQTTTopicException( + "Message received on unregistered topic: {}".format(message.topic) + ) except Full: logger.exception("message queue full") @@ -369,11 +364,9 @@ def message_received(self, topic: str, timeout: int = 1): Allow regexes for topic names? Better validation for mqtt payloads """ - sanitised = root_topic(topic) - try: with self._subscribe_lock: - queue = self._subscribed[self._subscription_mappings[sanitised]].queue + queue = self._subscribed[self._subscription_mappings[topic]].queue except KeyError as e: raise exceptions.MQTTTopicException( "Unregistered topic: {}".format(topic) @@ -457,9 +450,8 @@ def subscribe(self, topic: str, *args, **kwargs) -> None: (status, mid) = self._client.subscribe(topic, *args, **kwargs) if status == 0: - sanitised = root_topic(topic) with self._subscribe_lock: - self._subscription_mappings[sanitised] = mid + self._subscription_mappings[topic] = mid self._subscribed[mid] = _Subscription(topic) else: raise exceptions.MQTTError( diff --git a/tavern/_plugins/rest/files.py b/tavern/_plugins/rest/files.py new file mode 100644 index 000000000..3f72db8a8 --- /dev/null +++ b/tavern/_plugins/rest/files.py @@ -0,0 +1,189 @@ +import dataclasses +import logging +import mimetypes +import os +from contextlib import ExitStack +from typing import Any, List, Optional, Tuple, Union + +from tavern._core import exceptions +from tavern._core.dict_util import format_keys +from tavern._core.pytest.config import TestConfig + +logger = logging.getLogger(__name__) + + +@dataclasses.dataclass +class _Filespec: + """A description of a file for a file upload, possibly as part of a multi part upload""" + + path: str + content_type: Optional[str] = None + content_encoding: Optional[str] = None + form_field_name: Optional[str] = None + + +def _parse_filespec(filespec: Union[str, dict]) -> _Filespec: + """ + Get configuration for uploading file + + Args: + filespec: Can either be one of + - A path to a file + - A dict containing 'long' format, possibly including content type/encoding and the + multipart 'name' + + Returns: + The parsed file spec + + Raises: + exceptions.BadSchemaError: If the file spec was invalid + """ + if isinstance(filespec, str): + return _Filespec(filespec) + elif isinstance(filespec, dict): + try: + # The one required key + path = filespec["file_path"] + except KeyError as e: + raise exceptions.BadSchemaError( + "File spec dict did not contain the required 'file_path' key" + ) from e + + return _Filespec( + path, + filespec.get("content_type"), + filespec.get("content_encoding"), + filespec.get("form_field_name"), + ) + else: + # Could remove, also done in schema check + raise exceptions.BadSchemaError( + "File specification must be a path or a dictionary" + ) + + +def guess_filespec( + filespec: Union[str, dict], stack: ExitStack, test_block_config: TestConfig +) -> Tuple[List, Optional[str]]: + """tries to guess the content type and encoding from a file. + + Args: + test_block_config: config for test/stage + stack: exit stack to add open files context to + filespec: a string path to a file or a dictionary of the file path, content type, and encoding. + + Returns: + A tuple of either length 2 (filename and file object), 3 (as before, with content type), + or 4 (as before, with with content encoding). If a group name for the multipart upload + was specified, this is also returned. + + Notes: + If a 4-tuple is returned, the last element is a dictionary of headers to send to requests, + _not_ the raw encoding value. + """ + if not mimetypes.inited: + mimetypes.init() + + parsed = _parse_filespec(filespec) + + filepath = format_keys(parsed.path, test_block_config.variables) + filename = os.path.basename(filepath) + + # a 2-tuple ('filename', fileobj) + file_spec = [ + filename, + stack.enter_context(open(filepath, "rb")), + ] + + # Try to guess as well, but don't override what the user specified + guessed_content_type, guessed_encoding = mimetypes.guess_type(filepath) + content_type = parsed.content_type or guessed_content_type + encoding = parsed.content_encoding or guessed_encoding + + # If it doesn't have a mimetype, or can't guess it, don't + # send the content type for the file + if content_type: + # a 3-tuple ('filename', fileobj, 'content_type') + logger.debug("content_type for '%s' = '%s'", filename, content_type) + file_spec.append(content_type) + if encoding: + # or a 4-tuple ('filename', fileobj, 'content_type', custom_headers) + logger.debug("encoding for '%s' = '%s'", filename, encoding) + # encoding is None for no encoding or the name of the + # program used to encode (e.g. compress or gzip). The + # encoding is suitable for use as a Content-Encoding header. + file_spec.append({"Content-Encoding": encoding}) + + return file_spec, parsed.form_field_name + + +def _parse_file_mapping(file_args, stack, test_block_config) -> dict: + """Parses a simple mapping of uploads where each key is mapped to one form field name which has one file""" + files_to_send = {} + for key, filespec in file_args.items(): + file_spec, form_field_name = guess_filespec(filespec, stack, test_block_config) + + # If it's a dict then the key is used as the name, at least to maintain backwards compatability + if form_field_name: + logger.warning( + f"Specified 'form_field_name' as '{form_field_name}' in file spec, but the file name was inferred to be '{key}' from the mapping - the form_field_name will be ignored" + ) + + files_to_send[key] = tuple(file_spec) + return files_to_send + + +def _parse_file_list(file_args, stack, test_block_config) -> List: + """Parses a case where there may be multiple files uploaded as part of one form field""" + files_to_send: List[Any] = [] + for filespec in file_args: + file_spec, form_field_name = guess_filespec(filespec, stack, test_block_config) + + if not form_field_name: + raise exceptions.BadSchemaError( + "If specifying a list of files to upload for a multi part upload, the 'form_field_name' key must also be specified for each file to upload" + ) + + files_to_send.append( + ( + form_field_name, + tuple(file_spec), + ) + ) + + return files_to_send + + +def get_file_arguments( + request_args: dict, stack: ExitStack, test_block_config: TestConfig +) -> dict: + """Get correct arguments for anything that should be passed as a file to + requests + + Args: + request_args: args passed to requests + test_block_config: config for test + stack: context stack to add file objects to so they're + closed correctly after use + + Returns: + mapping of 'files' block to pass directly to requests + """ + + files_to_send: Optional[Union[dict, List]] = None + + file_args = request_args.get("files") + + if isinstance(file_args, dict): + files_to_send = _parse_file_mapping(file_args, stack, test_block_config) + elif isinstance(file_args, list): + files_to_send = _parse_file_list(file_args, stack, test_block_config) + elif file_args is not None: + raise exceptions.BadSchemaError( + f"'files' key in a HTTP request can only be a dict or a list but was {type(file_args)}" + ) + + if files_to_send: + return {"files": files_to_send} + else: + return {} diff --git a/tavern/_plugins/rest/request.py b/tavern/_plugins/rest/request.py index 1639b9905..8d81d7736 100644 --- a/tavern/_plugins/rest/request.py +++ b/tavern/_plugins/rest/request.py @@ -1,12 +1,10 @@ import contextlib import json import logging -import mimetypes -import os import warnings from contextlib import ExitStack from itertools import filterfalse, tee -from typing import Mapping, MutableMapping, Optional, Union +from typing import ClassVar, List, Mapping, MutableMapping, Optional from urllib.parse import quote_plus import requests @@ -20,6 +18,7 @@ from tavern._core.general import valid_http_methods from tavern._core.pytest.config import TestConfig from tavern._core.report import attach_yaml +from tavern._plugins.rest.files import get_file_arguments, guess_filespec from tavern.request import BaseRequest logger = logging.getLogger(__name__) @@ -94,7 +93,14 @@ def get_header(name): filename = fspec.get("file_body") if filename: with ExitStack() as stack: - file_spec = guess_filespec(filename, stack, test_block_config) + file_spec, group_name = guess_filespec(filename, stack, test_block_config) + + # Group name doesn't matter here as it's a single file + if group_name: + logger.warning( + f"'group_name' for the 'file_body' key was specified as '{group_name}' but this will be ignored " + ) + fspec["file_body"] = filename if len(file_spec) == 2: logger.debug( @@ -190,7 +196,7 @@ def add_request_args(keys, optional): # https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods if request_args["method"] in ["GET", "HEAD", "OPTIONS"]: if any(i in request_args for i in ["json", "data"]): - warnings.warn( + warnings.warn( # noqa "You are trying to send a body with a HTTP verb that has no semantic use for it", RuntimeWarning, ) @@ -326,116 +332,8 @@ def partition(pred, iterable): return deep_dict_merge(from_cookiejar, from_extra) -def _read_filespec(filespec: Union[str, dict]): - """ - Get configuration for uploading file - - Can either be just a path to a file or a 'long' format including content type/encoding - - Args: - filespec: Either a string with the path to a file or a dictionary with file_path and possible content_type and/or content_encoding - - Returns: - (file path, content type, content encoding) - """ - if isinstance(filespec, str): - return filespec, None, None - elif isinstance(filespec, dict): - return ( - filespec.get("file_path"), - filespec.get("content_type"), - filespec.get("content_encoding"), - ) - else: - # Could remove, also done in schema check - raise exceptions.BadSchemaError( - "File specification must be a path or a dictionary" - ) - - -def _get_file_arguments( - request_args: dict, stack: ExitStack, test_block_config: TestConfig -) -> dict: - """Get corect arguments for anything that should be passed as a file to - requests - - Args: - request_args: args passed to requests - test_block_config: config for test - stack: context stack to add file objects to so they're - closed correctly after use - - Returns: - mapping of 'files' block to pass directly to requests - """ - - files_to_send = {} - - for key, filespec in request_args.get("files", {}).items(): - file_spec = guess_filespec(filespec, stack, test_block_config) - - files_to_send[key] = tuple(file_spec) - - if files_to_send: - return {"files": files_to_send} - else: - return {} - - -def guess_filespec( - filespec: Union[str, dict], stack: ExitStack, test_block_config: TestConfig -): - """tries to guess the content type and encoding from a file. - - Args: - test_block_config: config for test/stage - stack: exit stack to add open files context to - filespec: a string path to a file or a dictionary of the file path, content type, and encoding. - - Returns: - tuple: A tuple of either length 2 (filename and file object), 3 (as before, with ceontent type), or 4 (as before, with with content encoding) - - Notes: - If a 4-tuple is returned, the last element is a dictionary of headers to send to requests, _not_ the raw encoding value. - """ - if not mimetypes.inited: - mimetypes.init() - - filepath, content_type, encoding = _read_filespec(filespec) - - filepath = format_keys(filepath, test_block_config.variables) - filename = os.path.basename(filepath) - - # a 2-tuple ('filename', fileobj) - file_spec = [ - filename, - stack.enter_context(open(filepath, "rb")), - ] - - # Try to guess as well, but don't override what the user specified - guessed_content_type, guessed_encoding = mimetypes.guess_type(filepath) - content_type = content_type or guessed_content_type - encoding = encoding or guessed_encoding - - # If it doesn't have a mimetype, or can't guess it, don't - # send the content type for the file - if content_type: - # a 3-tuple ('filename', fileobj, 'content_type') - logger.debug("content_type for '%s' = '%s'", filename, content_type) - file_spec.append(content_type) - if encoding: - # or a 4-tuple ('filename', fileobj, 'content_type', custom_headers) - logger.debug("encoding for '%s' = '%s'", filename, encoding) - # encoding is None for no encoding or the name of the - # program used to encode (e.g. compress or gzip). The - # encoding is suitable for use as a Content-Encoding header. - file_spec.append({"Content-Encoding": encoding}) - - return file_spec - - class RestRequest(BaseRequest): - optional_in_file = [ + optional_in_file: ClassVar[List[str]] = [ "json", "data", "params", @@ -492,7 +390,7 @@ def __init__( request_args = get_request_args(rspec, test_block_config) update_from_ext( request_args, - RestRequest.optional_in_file, + RestRequest.optional_in_file + ["url"], ) # Used further down, but pop it asap to avoid unwanted side effects @@ -530,9 +428,10 @@ def prepared_request(): file = stack.enter_context(open(file_body, "rb")) request_args.update(data=file) else: - self._request_args.update( - _get_file_arguments(request_args, stack, test_block_config) - ) + files = get_file_arguments(request_args, stack, test_block_config) + if files: + logger.debug("Sending %d files in request", len(files["files"])) + self._request_args.update(files) headers = self._request_args.get("headers", {}) for k, v in headers.items(): diff --git a/tavern/_plugins/rest/response.py b/tavern/_plugins/rest/response.py index ca54b116f..d14c1a308 100644 --- a/tavern/_plugins/rest/response.py +++ b/tavern/_plugins/rest/response.py @@ -105,21 +105,18 @@ def _check_status_code(self, status_code, body) -> None: "Status code '%s' matched expected '%s'", status_code, expected_code ) return + elif 400 <= status_code < 500: + # special case if there was a bad request. This assumes that the + # response would contain some kind of information as to why this + # request was rejected. + self._adderr( + "Status code was %s, expected %s:\n%s", + status_code, + expected_code, + indent_err_text(json.dumps(body)), + ) else: - if 400 <= status_code < 500: - # special case if there was a bad request. This assumes that the - # response would contain some kind of information as to why this - # request was rejected. - self._adderr( - "Status code was %s, expected %s:\n%s", - status_code, - expected_code, - indent_err_text(json.dumps(body)), - ) - else: - self._adderr( - "Status code was %s, expected %s", status_code, expected_code - ) + self._adderr("Status code was %s, expected %s", status_code, expected_code) def verify(self, response: requests.Response) -> dict: """Verify response against expected values and returns any values that diff --git a/tests/integration/881_1.json b/tests/integration/881_1.json new file mode 100644 index 000000000..64ffa49a5 --- /dev/null +++ b/tests/integration/881_1.json @@ -0,0 +1,3 @@ +{ + "{enclosed-in-key}": "sample" +} \ No newline at end of file diff --git a/tests/integration/881_2.yaml b/tests/integration/881_2.yaml new file mode 100644 index 000000000..b755e68c9 --- /dev/null +++ b/tests/integration/881_2.yaml @@ -0,0 +1 @@ +{ "sample": !raw "{enclosed-in-value}" } diff --git a/tests/integration/Dockerfile b/tests/integration/Dockerfile index 68b46a7f7..edd2733d2 100644 --- a/tests/integration/Dockerfile +++ b/tests/integration/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.10-alpine -RUN pip3 install pyjwt~=2.4.0 flask~=2.0.3 +RUN pip3 install 'pyjwt>=2.4.0,<3' 'flask>=2.2.3' ENV FLASK_DEBUG=1 ENV PYTHONUNBUFFERED=0 diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index a7461c1d4..307791662 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -23,6 +23,11 @@ def autouse_thing(): return "abc" +@pytest.fixture(scope="session", autouse=True) +def fixture_echo_url(): + return "http://localhost:5003/echo" + + @pytest.fixture(scope="session", autouse=True, name="autouse_thing_named") def second(autouse_thing): return autouse_thing diff --git a/tests/integration/ext_functions.py b/tests/integration/ext_functions.py index 5c28e5fb7..cf33102bb 100644 --- a/tests/integration/ext_functions.py +++ b/tests/integration/ext_functions.py @@ -1,3 +1,6 @@ +import time + + def return_hello(): return {"hello": "there"} @@ -8,3 +11,17 @@ def return_goodbye_string(): def return_list_vals(): return [{"a_value": "b_value"}, 2] + + +def gen_echo_url(host): + return "{0}/echo".format(host) + + +def time_request(_): + time.time() + yield + time.time() + + +def print_response(_, extra_print="affa"): + (_, r) = yield diff --git a/tests/integration/server.py b/tests/integration/server.py index d409b89fb..7d1780154 100644 --- a/tests/integration/server.py +++ b/tests/integration/server.py @@ -87,8 +87,6 @@ def upload_fake_file(): def _handle_files(): - if not mimetypes.inited: - mimetypes.init() for item in request.files.values(): if item.filename: filetype = ".{}".format(item.filename.split(".")[-1]) @@ -104,21 +102,72 @@ def _handle_files(): return "", 200 +class BadFileUploadException(Exception): + """Something wrong when uploading files""" + + +def _verify_is_file_multipart(): + if not mimetypes.inited: + mimetypes.init() + + if not request.content_type.startswith("multipart/form-data"): + raise BadFileUploadException("Was not a multipart form upload") + + if not request.files: + raise BadFileUploadException("No files in request") + + @app.route("/fake_upload_file_data", methods=["POST"]) def upload_fake_file_and_data(): - if not request.files: - return "", 401 + try: + _verify_is_file_multipart() + except BadFileUploadException as e: + return jsonify({"error": str(e)}), 400 if not request.form.to_dict(): - return "", 402 - - # Verify that the content type is `multipart` - if not request.content_type.startswith("multipart/form-data"): - return "", 403 + return "", 400 return _handle_files() +@app.route("/files_expect_in_order", methods=["POST"]) +def upload_specific_files_in_order(): + """Expects a multipart form upload with files in the correct order + + See test_files.tavern.yaml for expected list of files here + """ + + try: + _verify_is_file_multipart() + except BadFileUploadException as e: + return jsonify({"error": str(e)}), 400 + + try: + group_1 = request.files.getlist("group_1") + if len(group_1) != 2: + raise Exception(f"expected 2 files in group 1, got {len(group_1)}") + if group_1[0].filename != "OK.txt": + raise Exception( + f"First file in group 1 should be OK.txt, was {group_1[0].filename}" + ) + if group_1[1].filename != "OK.json.gz": + raise Exception( + f"Second file in group 1 should be OK.json.gz, was {group_1[1].filename}" + ) + + group_2 = request.files.getlist("group_2") + if len(group_2) != 1: + raise Exception(f"expected 1 files in group 2, got {len(group_2)}") + if group_2[0].filename != "OK.txt": + raise Exception( + f"First file in group 2 should be OK.txt, was {group_2[0].filename}" + ) + except Exception as e: + return jsonify({"error": str(e)}), 400 + + return "", 200 + + @app.route("/nested/again", methods=["GET"]) def multiple_path_items_response(): response = {"status": "OK"} diff --git a/tests/integration/test_control_flow.tavern.yaml b/tests/integration/test_control_flow.tavern.yaml new file mode 100644 index 000000000..dbcdfcf14 --- /dev/null +++ b/tests/integration/test_control_flow.tavern.yaml @@ -0,0 +1,51 @@ +--- +test_name: Test finally block doing nothing + +stages: + - name: Simple echo + request: + url: "{global_host}/echo" + method: POST + json: + value: "123" + response: + status_code: 200 + json: + value: "123" + +finally: + - name: nothing + request: + url: "{global_host}/echo" + method: POST + json: + value: "123" + +--- +test_name: Test finally block fail + +_xfail: finally + +stages: + - name: Simple echo + request: + url: "{global_host}/echo" + method: POST + json: + value: "123" + response: + status_code: 200 + json: + value: "123" + +finally: + - name: nothing + request: + url: "{global_host}/echo" + method: DELETE + json: + value: "123" + response: + status_code: 200 + json: + value: "123" diff --git a/tests/integration/test_external_functions.tavern.yaml b/tests/integration/test_external_functions.tavern.yaml index bd4da1690..08ea91ba6 100644 --- a/tests/integration/test_external_functions.tavern.yaml +++ b/tests/integration/test_external_functions.tavern.yaml @@ -154,3 +154,28 @@ stages: json: top_level: nested: value + +--- +test_name: Test external function url + +includes: + - !include common.yaml + +stages: + - name: external function url + request: + url: + $ext: + function: ext_functions:gen_echo_url + extra_kwargs: + host: "{host}" + method: POST + json: + value1: "hi" + $ext: + function: ext_functions:return_hello + response: + status_code: 200 + json: + value1: "hi" + hello: "there" diff --git a/tests/integration/test_files.tavern.yaml b/tests/integration/test_files.tavern.yaml index 764549226..f7f492475 100644 --- a/tests/integration/test_files.tavern.yaml +++ b/tests/integration/test_files.tavern.yaml @@ -224,3 +224,59 @@ stages: test_files: "test_files.tavern.yaml" response: status_code: 200 + +--- +test_name: Test uploading multi part files in a common group + +stages: + - name: Upload files + request: + url: "{global_host}/files_expect_in_order" + method: POST + files: + - form_field_name: group_2 + file_path: OK.txt + - form_field_name: group_1 + file_path: OK.txt + - form_field_name: group_1 + file_path: OK.json.gz + response: + status_code: 200 + +--- +test_name: Test uploading multi part files in a common group in a different order + +stages: + - name: Upload files + request: + url: "{global_host}/files_expect_in_order" + method: POST + files: + - form_field_name: group_1 + file_path: OK.txt + - form_field_name: group_2 + file_path: OK.txt + - form_field_name: group_1 + file_path: OK.json.gz + response: + status_code: 200 + +--- +test_name: Test uploading multi part files with wrong groups + +stages: + - name: Upload files + request: + url: "{global_host}/files_expect_in_order" + method: POST + files: + - form_field_name: group_1 + file_path: OK.txt + - form_field_name: group_2 + file_path: OK.txt + - form_field_name: group_3 + file_path: OK.json.gz + response: + status_code: 400 + json: + error: expected 2 files in group 1, got 1 diff --git a/tests/integration/test_fixtures.tavern.yaml b/tests/integration/test_fixtures.tavern.yaml index db4422f4e..6d6f8bd11 100644 --- a/tests/integration/test_fixtures.tavern.yaml +++ b/tests/integration/test_fixtures.tavern.yaml @@ -116,6 +116,7 @@ stages: status_code: 200 json: value: "{yield_str_fixture}" + --- test_name: Test autouse fixture @@ -133,3 +134,21 @@ stages: status_code: 200 json: value: "{autouse_thing_named}" + +--- +test_name: Test autouse fixture host + +includes: + - !include common.yaml + +stages: + - name: post json and expect it to be echoed + request: + url: "{fixture_echo_url}" + method: POST + json: + value: "hello" + response: + status_code: 200 + json: + value: "hello" diff --git a/tests/integration/test_include.tavern.yaml b/tests/integration/test_include.tavern.yaml new file mode 100644 index 000000000..4e2d45c79 --- /dev/null +++ b/tests/integration/test_include.tavern.yaml @@ -0,0 +1,65 @@ +--- +test_name: Test including json + +includes: + - !include common.yaml + +stages: + - name: Send included json + request: + url: "{host}/echo" + method: POST + json: !include 881_1.json + response: + status_code: 200 + json: !include 881_1.json + +--- +test_name: Test including json with key + +includes: + - !include common.yaml + +stages: + - name: Send included json + request: + url: "{host}/echo" + method: POST + json: !include 881_2.yaml + response: + status_code: 200 + json: !include 881_2.yaml + +--- +test_name: Test including json with error + +includes: + - !include common.yaml + +stages: + - name: Send included json + request: + url: "{host}/echo" + method: POST + json: !include 881_1.json + response: + status_code: 201 + +_xfail: run + +--- +test_name: Test including json with error and key + +includes: + - !include common.yaml + +stages: + - name: Send included json + request: + url: "{host}/echo" + method: POST + json: !include 881_2.yaml + response: + status_code: 201 + +_xfail: run diff --git a/tests/integration/test_minimal.tavern.yaml b/tests/integration/test_minimal.tavern.yaml deleted file mode 100644 index f66d3314b..000000000 --- a/tests/integration/test_minimal.tavern.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- -# Every test file has one or more tests... -test_name: Get some fake data from the JSON placeholder API - -# ...and each test has one or more stages (e.g. an HTTP request) -stages: - - name: Make sure we have the right ID - - # Define the request to be made... - request: - url: https://jsonplaceholder.typicode.com/posts/1 - method: GET - - # ...and the expected response code and body - response: - status_code: 200 - json: - id: 1 - userId: 1 - title: "sunt aut facere repellat provident occaecati excepturi optio reprehenderit" - body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" diff --git a/tests/integration/test_tincture.tavern.yaml b/tests/integration/test_tincture.tavern.yaml new file mode 100644 index 000000000..06bd17858 --- /dev/null +++ b/tests/integration/test_tincture.tavern.yaml @@ -0,0 +1,60 @@ +--- +test_name: Test tincture + fixtures + +includes: + - !include common.yaml + +tinctures: + - function: ext_functions:time_request + - function: ext_functions:print_response + extra_kwargs: + extra_print: "blooble" + +marks: + - usefixtures: + - yielder + - str_fixture + - yield_str_fixture + +stages: + - name: do something + tinctures: + - function: ext_functions:time_request + - function: ext_functions:print_response + extra_kwargs: + extra_print: "blooble" + request: + url: "{host}/echo" + method: POST + json: + value: "{str_fixture}" + response: + status_code: 200 + json: + value: "{yield_str_fixture}" + +--- +test_name: Test tincture extra kwargs fails + +includes: + - !include common.yaml + +tinctures: + - function: ext_functions:print_response + extra_kwargs: + extra_print: "blooble" + something: else + +_xfail: run + +stages: + - name: do something + request: + url: "{host}/echo" + method: POST + json: + value: "{str_fixture}" + response: + status_code: 200 + json: + value: "{yield_str_fixture}" diff --git a/tests/unit/test_core.py b/tests/unit/test_core.py index 32d2b1985..e930d813e 100644 --- a/tests/unit/test_core.py +++ b/tests/unit/test_core.py @@ -1,3 +1,4 @@ +import copy import dataclasses import json import os @@ -37,7 +38,7 @@ def fix_example_test(): @pytest.fixture(name="mockargs") def fix_mock_response_args(fulltest): - response = fulltest["stages"][0]["response"] + response = copy.deepcopy(fulltest["stages"][0]["response"]) content = response["json"] args = { @@ -500,6 +501,138 @@ def test_format_request_var_value(self, fulltest, includes): assert pmock.called +class TestFinally: + @staticmethod + def run_test(fulltest, mockargs, includes): + mock_response = Mock(**mockargs) + + with patch( + "tavern._plugins.rest.request.requests.Session.request", + return_value=mock_response, + ) as pmock: + run_test("heif", fulltest, includes) + + assert pmock.called + + return pmock + + @pytest.mark.parametrize("finally_block", ([],)) + def test_nop(self, fulltest, mockargs, includes, finally_block): + """ignore empty finally blocks""" + fulltest["finally"] = finally_block + + self.run_test(fulltest, mockargs, includes) + + @pytest.mark.parametrize( + "finally_block", + ( + {}, + "hi", + 3, + ), + ) + def test_wrong_type(self, fulltest, mockargs, includes, finally_block): + """final stages need to be dicts too""" + fulltest["finally"] = finally_block + + with pytest.raises(exceptions.BadSchemaError): + self.run_test(fulltest, mockargs, includes) + + @pytest.fixture + def finally_request(self): + return { + "name": "step 1", + "request": {"url": "http://www.myfinal.com", "method": "POST"}, + "response": { + "status_code": 200, + "json": {"key": "value"}, + "headers": {"content-type": "application/json"}, + }, + } + + def test_finally_run(self, fulltest, mockargs, includes, finally_request): + fulltest["finally"] = [finally_request] + + pmock = self.run_test(fulltest, mockargs, includes) + + assert pmock.call_count == 2 + assert pmock.mock_calls[1].kwargs.items() >= finally_request["request"].items() + + def test_finally_run_twice(self, fulltest, mockargs, includes, finally_request): + fulltest["finally"] = [finally_request, finally_request] + + pmock = self.run_test(fulltest, mockargs, includes) + + assert pmock.call_count == 3 + assert pmock.mock_calls[1].kwargs.items() >= finally_request["request"].items() + assert pmock.mock_calls[2].kwargs.items() >= finally_request["request"].items() + + def test_finally_run_on_main_failure( + self, fulltest, mockargs, includes, finally_request + ): + fulltest["finally"] = [finally_request] + + mockargs["status_code"] = 503 + + mock_response = Mock(**mockargs) + + with patch( + "tavern._plugins.rest.request.requests.Session.request", + return_value=mock_response, + ) as pmock: + with pytest.raises(exceptions.TestFailError): + run_test("heif", fulltest, includes) + + assert pmock.call_count == 2 + assert pmock.mock_calls[1].kwargs.items() >= finally_request["request"].items() + + +class TestTinctures: + @pytest.mark.parametrize( + "tinctures", + ( + {"function": "abc"}, + [{"function": "abc"}], + [{"function": "abc"}, {"function": "def"}], + ), + ) + @pytest.mark.parametrize( + "at_stage_level", + ( + True, + False, + ), + ) + def test_tinctures( + self, + fulltest, + mockargs, + includes, + tinctures, + at_stage_level, + ): + if at_stage_level: + fulltest["tinctures"] = tinctures + else: + fulltest["stages"][0]["tinctures"] = tinctures + + mock_response = Mock(**mockargs) + + tincture_func_mock = Mock() + + with patch( + "tavern._plugins.rest.request.requests.Session.request", + return_value=mock_response, + ) as pmock, patch( + "tavern._core.tincture.get_wrapped_response_function", + return_value=tincture_func_mock, + ): + run_test("heif", fulltest, includes) + + assert pmock.call_count == 1 + assert tincture_func_mock.call_count == len(tinctures) + + def test_copy_config(pytestconfig): cfg_1 = load_global_cfg(pytestconfig) diff --git a/tests/unit/test_request.py b/tests/unit/test_request.py index 2efdbe727..43aa6309c 100644 --- a/tests/unit/test_request.py +++ b/tests/unit/test_request.py @@ -2,18 +2,20 @@ import os import tempfile from contextlib import ExitStack +from textwrap import dedent from unittest.mock import Mock import pytest import requests +import yaml from requests.cookies import RequestsCookieJar from tavern._core import exceptions from tavern._core.extfunctions import update_from_ext +from tavern._plugins.rest.files import get_file_arguments from tavern._plugins.rest.request import ( RestRequest, _check_allow_redirects, - _get_file_arguments, _read_expected_cookies, get_request_args, ) @@ -400,14 +402,14 @@ def test_get_no_files(self, mock_stack, includes): request_args = {} - assert _get_file_arguments(request_args, mock_stack, includes) == {} + assert get_file_arguments(request_args, mock_stack, includes) == {} def test_get_empty_files_list(self, mock_stack, includes): """No specific files specified -> no files""" request_args = {"files": {}} - assert _get_file_arguments(request_args, mock_stack, includes) == {} + assert get_file_arguments(request_args, mock_stack, includes) == {} def test_a_file(self, mock_stack, includes): """Json file should have the correct mimetype etc.""" @@ -415,7 +417,7 @@ def test_a_file(self, mock_stack, includes): with tempfile.NamedTemporaryFile(suffix=".json") as tfile: request_args = {"files": {"file1": tfile.name}} - file_spec = _get_file_arguments(request_args, mock_stack, includes) + file_spec = get_file_arguments(request_args, mock_stack, includes) file = file_spec["files"]["file1"] assert file[0] == os.path.basename(tfile.name) @@ -435,7 +437,7 @@ def test_use_long_form_content_type(self, mock_stack, includes): } } - file_spec = _get_file_arguments(request_args, mock_stack, includes) + file_spec = get_file_arguments(request_args, mock_stack, includes) file = file_spec["files"]["file1"] assert file[0] == os.path.basename(tfile.name) @@ -462,7 +464,53 @@ def test_format_filename(self, mock_stack, includes, file_args): includes.variables["tmpname"] = tfile.name request_args = {"files": {"file1": tfile.name}} - file_spec = _get_file_arguments(request_args, mock_stack, includes) + file_spec = get_file_arguments(request_args, mock_stack, includes) file = file_spec["files"]["file1"] assert file[0] == os.path.basename(tfile.name) + + def test_grouped_file_names(self, mock_stack, includes): + """Parse grouped names appropriately""" + with tempfile.NamedTemporaryFile() as tfile: + raw_yaml_args = """ + # Send file_1.txt and file_2.txt, both with name="input_files", in the multipart data. + - form_field_name: "input_files" + file_path: "%FILENAME%" + content_type: "application/customtype" + content_encoding: "UTF16" + - form_field_name: "input_files" + file_path: "%FILENAME%" + content_type: "application/json" + """ + + raw_yaml_args = raw_yaml_args.replace("%FILENAME%", tfile.name) + + file_args = yaml.safe_load(dedent(raw_yaml_args)) + + request_args = {"files": file_args} + + parsed = get_file_arguments(request_args, mock_stack, includes) + + parsed_into = { + "files": [ + ( + "input_files", + ( + os.path.basename(tfile.name), + mock_stack.enter_context.return_value, + "application/customtype", + {"Content-Encoding": "UTF16"}, + ), + ), + ( + "input_files", + ( + os.path.basename(tfile.name), + mock_stack.enter_context.return_value, + "application/json", + ), + ), + ], + } + + assert parsed_into == parsed diff --git a/tests/unit/test_tinctures.py b/tests/unit/test_tinctures.py new file mode 100644 index 000000000..98768a7d1 --- /dev/null +++ b/tests/unit/test_tinctures.py @@ -0,0 +1,113 @@ +from unittest.mock import patch + +import pytest + +from tavern._core.tincture import Tinctures, get_stage_tinctures + + +@pytest.fixture(name="example") +def example(): + spec = { + "test_name": "A test with a single stage", + "stages": [ + { + "name": "step 1", + "request": {"url": "http://www.google.com", "method": "GET"}, + "response": { + "status_code": 200, + "json": {"key": "value"}, + "headers": {"content-type": "application/json"}, + }, + } + ], + } + + return spec + + +def test_empty(): + t = Tinctures([]) + + t.start_tinctures({}) + t.end_tinctures({}, None) + + +@pytest.mark.parametrize( + "tinctures", + ( + {"function": "abc"}, + [{"function": "abc"}], + [{"function": "abc"}, {"function": "def"}], + ), +) +class TestTinctures: + class TestTinctureBasic: + def test_stage_tinctures_normal(self, example, tinctures): + stage = example["stages"][0] + stage["tinctures"] = tinctures + + with patch( + "tavern._core.tincture.get_wrapped_response_function", + return_value=lambda _: None, + ) as call_mock: + t = get_stage_tinctures(stage, example) + + t.start_tinctures(stage) + t.end_tinctures(stage, None) + + assert call_mock.call_count == len(tinctures) + + def test_test_tinctures_normal(self, example, tinctures): + stage = example["stages"][0] + example["tinctures"] = tinctures + + with patch( + "tavern._core.tincture.get_wrapped_response_function", + return_value=lambda _: None, + ) as call_mock: + t = get_stage_tinctures(stage, example) + + t.start_tinctures(stage) + t.end_tinctures(stage, None) + + assert call_mock.call_count == len(tinctures) + + class TestTinctureYields: + @staticmethod + def does_yield(stage): + assert stage["name"] == "step 1" + + (expected, response) = yield + + assert expected == stage["response"] + assert response is None + + def test_stage_tinctures_normal(self, example, tinctures): + stage = example["stages"][0] + stage["tinctures"] = tinctures + + with patch( + "tavern._core.tincture.get_wrapped_response_function", + return_value=self.does_yield, + ) as call_mock: + t = get_stage_tinctures(stage, example) + + t.start_tinctures(stage) + t.end_tinctures(stage["response"], None) + + assert call_mock.call_count == len(tinctures) + + def test_test_tinctures_normal(self, example, tinctures): + stage = example["stages"][0] + example["tinctures"] = tinctures + + with patch( + "tavern._core.tincture.get_wrapped_response_function", + return_value=self.does_yield, + ) as call_mock: + t = get_stage_tinctures(stage, example) + + t.start_tinctures(stage) + t.end_tinctures(stage["response"], None) + + assert call_mock.call_count == len(tinctures) diff --git a/tox-integration.ini b/tox-integration.ini index 12de6ec93..eeb490fdd 100644 --- a/tox-integration.ini +++ b/tox-integration.ini @@ -4,8 +4,10 @@ skip_missing_interpreters = true isolated_build = True [testenv] +allowlist_externals = + docker basepython = python3.10 -passenv = DOCKER_TLS_VERIFY DOCKER_HOST DOCKER_CERT_PATH DOCKER_BUILDKIT +passenv = DOCKER_TLS_VERIFY,DOCKER_HOST,DOCKER_CERT_PATH,DOCKER_BUILDKIT setenv = TEST_HOST = http://localhost:5003 SECOND_URL_PART = again @@ -20,7 +22,6 @@ changedir = generic: tests/integration noextra: tests/integration deps = - docker-compose flask allure-pytest pyjwt @@ -29,31 +30,31 @@ deps = colorlog mqtt: fluent-logger commands = -; docker-compose stop -; docker-compose build - docker-compose up --build -d +; docker compose stop +; docker compose build + docker compose up --build -d python -m pytest --collect-only - python -m pytest --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml --cov tavern + python -m pytest --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml --cov tavern {posargs} generic: py.test --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml -n 3 generic: tavern-ci --stdout . --tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml - generic: python -c "from tavern.core import run; exit(run('.', '{toxinidir}/tests/integration/global_cfg.yaml', pytest_args=[]))" + generic: python -c "from tavern.core import run; exit(run('.', '{toxinidir}/tests/integration/global_cfg.yaml', pytest_args=[ ]))" generic: python -c "from tavern.core import run; exit(run('.', pytest_args=['--tavern-global-cfg={toxinidir}/tests/integration/global_cfg.yaml']))" cookies: tavern-ci --stdout test_server.tavern.yaml - cookies: python -c "from tavern.core import run; exit(run('test_server.tavern.yaml', pytest_args=[]))" + cookies: python -c "from tavern.core import run; exit(run('test_server.tavern.yaml', pytest_args=[ ]))" advanced: tavern-ci --stdout test_server.tavern.yaml - advanced: python -c "from tavern.core import run; exit(run('test_server.tavern.yaml', pytest_args=[]))" + advanced: python -c "from tavern.core import run; exit(run('test_server.tavern.yaml', pytest_args=[ ]))" components: tavern-ci --stdout test_ping.tavern.yaml components: tavern-ci --stdout test_hello.tavern.yaml - components: python -c "from tavern.core import run; exit(run('test_ping.tavern.yaml', pytest_args=[]))" - components: python -c "from tavern.core import run; exit(run('test_hello.tavern.yaml', pytest_args=[]))" + components: python -c "from tavern.core import run; exit(run('test_ping.tavern.yaml', pytest_args=[ ]))" + components: python -c "from tavern.core import run; exit(run('test_hello.tavern.yaml', pytest_args=[ ]))" mqtt: tavern-ci --stdout test_mqtt.tavern.yaml --cov tavern mqtt: python -c "from tavern.core import run; exit(run('test_mqtt.tavern.yaml', pytest_args=['--cov-append']))" mqtt: tavern-ci --stdout test_mqtt_failures.tavern.yaml - mqtt: python -c "from tavern.core import run; exit(run('test_mqtt_failures.tavern.yaml', pytest_args=[]))" + mqtt: python -c "from tavern.core import run; exit(run('test_mqtt_failures.tavern.yaml', pytest_args=[ ]))" - docker-compose stop + docker compose stop diff --git a/tox.ini b/tox.ini index 70494974e..91a975e69 100644 --- a/tox.ini +++ b/tox.ini @@ -6,13 +6,13 @@ isolated_build = True [testenv] passenv = XDG_CACHE_HOME basepython = python3.10 -whitelist_externals = +allowlist_externals = mypy install_command = python -m pip install {opts} {packages} -c constraints.txt extras = dev commands = - {envbindir}/python -m pytest --cov-report term-missing --cov tavern + {envbindir}/python -m pytest --cov-report term-missing --cov tavern {posargs} [testenv:py3check] commands =