From c5f3cc9ab90f787247e424cbf68a643edfb063a7 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 20 Jan 2022 05:41:10 +0000 Subject: [PATCH 01/36] feat(Dependencies): Update python Docker tag to v3.10.2 | datasource | package | from | to | | ---------- | ------- | ------ | ------ | | docker | python | 3.10.1 | 3.10.2 | | docker | python | 3.10.1 | 3.10.2 | | docker | python | 3.10.1 | 3.10.2 | --- Dockerfile | 4 ++-- Dockerfile.dev | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5b19f50..8fcc1e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.1-alpine as builder +FROM python:3.10.2-alpine as builder ADD src /code/src ADD pyproject.toml poetry.lock /code/ @@ -8,7 +8,7 @@ RUN pip install poetry && poetry build -f wheel # by using a build container we prevent us from carrying around poetry # alongside its dependencies -FROM python:3.10.1-alpine +FROM python:3.10.2-alpine ARG OS_CREDITS_VERSION ARG WHEEL_NAME=os_credits-1.1.0-py3-none-any.whl EXPOSE 80 diff --git a/Dockerfile.dev b/Dockerfile.dev index 3219248..ac7fe75 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,6 @@ # Expected to be called with `--volume $(PWD)/src:/code/src:ro` # Uses `adev` from `aiohttp-devtools` to restart the application on any code change -FROM python:3.10.1-alpine +FROM python:3.10.2-alpine ADD src /code/src ADD pyproject.toml poetry.lock /code/ From dcf60d30c8f36a63696f241108f6f4f54cd48a0c Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 20 Jan 2022 09:25:21 +0000 Subject: [PATCH 02/36] feat(Dependencies): Update dependency sphinx to v4.4.0 | datasource | package | from | to | | ---------- | ------- | ----- | ----- | | pypi | sphinx | 4.3.2 | 4.4.0 | --- poetry.lock | 59 ++++++++++++++++++++++++++++++++++++++++---------- pyproject.toml | 2 +- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index dace2bc..12ef828 100644 --- a/poetry.lock +++ b/poetry.lock @@ -453,6 +453,22 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "importlib-metadata" +version = "4.10.1" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] + [[package]] name = "iniconfig" version = "1.1.1" @@ -948,7 +964,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "4.3.2" +version = "4.4.0" description = "Python documentation generator" category = "dev" optional = false @@ -960,6 +976,7 @@ babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.14,<0.18" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -974,22 +991,22 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.11.1" +version = "1.15.2" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" category = "dev" optional = false -python-versions = ">=3.5.2" +python-versions = ">=3.7" [package.dependencies] -Sphinx = ">=3.0" +Sphinx = ">=4" [package.extras] -test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "Sphinx (>=3.2.0)", "dataclasses"] +testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] type_comments = ["typed-ast (>=1.4.0)"] [[package]] @@ -1207,10 +1224,22 @@ python-versions = ">=3.6" idna = ">=2.0" multidict = ">=4.0" +[[package]] +name = "zipp" +version = "3.7.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "76c197fbb84d1287892b8677eb75b213e0ec71f7063306e014b89f0d8b3315e0" +content-hash = "5530dd79cf9e5a286938ca8f8354afa39d49a19cd6e7b1825d6786b63b4620b9" [metadata.files] aiohttp = [ @@ -1615,6 +1644,10 @@ imagesize = [ {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] +importlib-metadata = [ + {file = "importlib_metadata-4.10.1-py3-none-any.whl", hash = "sha256:899e2a40a8c4a1aec681feef45733de8a6c58f3f6a0dbed2eb6574b4387a77b6"}, + {file = "importlib_metadata-4.10.1.tar.gz", hash = "sha256:951f0d8a5b7260e9db5e41d429285b5f451e928479f19d80818878527d36e95e"}, +] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -2056,12 +2089,12 @@ snowballstemmer = [ {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, ] sphinx = [ - {file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"}, - {file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"}, + {file = "Sphinx-4.4.0-py3-none-any.whl", hash = "sha256:5da895959511473857b6d0200f56865ed62c31e8f82dd338063b84ec022701fe"}, + {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"}, - {file = "sphinx_autodoc_typehints-1.11.1-py3-none-any.whl", hash = "sha256:da049791d719f4c9813642496ee4764203e317f0697eb75446183fa2a68e3f77"}, + {file = "sphinx_autodoc_typehints-1.15.2-py3-none-any.whl", hash = "sha256:bd6b70b93fc53d63a167738ba4fb40643ef7eb9b8d915a886c372949981b8eac"}, + {file = "sphinx_autodoc_typehints-1.15.2.tar.gz", hash = "sha256:1480d4af8ac465aa87b793e22bb0feb4578b9c93b36e01b35c983bd6f596326f"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2205,3 +2238,7 @@ yarl = [ {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] +zipp = [ + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, +] diff --git a/pyproject.toml b/pyproject.toml index d7bf450..0137179 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ pre-commit = "2.16.0" black = {version = "20.8b1",allow-prereleases = true} sphinx-autodoc-typehints = "1.15.2" mypy = "0.931" -sphinx = "4.3.2" +sphinx = "4.4.0" pytest-localserver = "0.5.1.post0" pytest-cov = "3.0.0" sphinxcontrib-trio = "1.1.2" From 2b077aff95ad9b2be5684f8323989dcdeabff5be Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 20 Jan 2022 13:46:07 +0000 Subject: [PATCH 03/36] feat(Dependencies): Update dependency sphinx-autodoc-typehints to v1.15.3 | datasource | package | from | to | | ---------- | ------------------------ | ------ | ------ | | pypi | sphinx-autodoc-typehints | 1.15.2 | 1.15.3 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 12ef828..955478a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -996,7 +996,7 @@ test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.15.2" +version = "1.15.3" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" category = "dev" optional = false @@ -1239,7 +1239,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "5530dd79cf9e5a286938ca8f8354afa39d49a19cd6e7b1825d6786b63b4620b9" +content-hash = "d8164649a9533f38a99033093089b78f159263d874249dc0929d1921cd194e3d" [metadata.files] aiohttp = [ @@ -2093,8 +2093,8 @@ sphinx = [ {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx_autodoc_typehints-1.15.2-py3-none-any.whl", hash = "sha256:bd6b70b93fc53d63a167738ba4fb40643ef7eb9b8d915a886c372949981b8eac"}, - {file = "sphinx_autodoc_typehints-1.15.2.tar.gz", hash = "sha256:1480d4af8ac465aa87b793e22bb0feb4578b9c93b36e01b35c983bd6f596326f"}, + {file = "sphinx_autodoc_typehints-1.15.3-py3-none-any.whl", hash = "sha256:5bf7ae28f98dd2cae8b93c4f723fb5f4761ae340194d02d2742a81c7694d2e0a"}, + {file = "sphinx_autodoc_typehints-1.15.3.tar.gz", hash = "sha256:91eb3c227ff0824bee39fabf92d92e21835546903d2ddde5467fd9b6fa10ad7f"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, diff --git a/pyproject.toml b/pyproject.toml index 0137179..1755be6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pytest = "6.2.5" pytest-aiohttp = "0.3.0" pre-commit = "2.16.0" black = {version = "20.8b1",allow-prereleases = true} -sphinx-autodoc-typehints = "1.15.2" +sphinx-autodoc-typehints = "1.15.3" mypy = "0.931" sphinx = "4.4.0" pytest-localserver = "0.5.1.post0" From 633910d3c363fad9e53bfe9b7545e8f42dca642a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 20 Jan 2022 13:59:09 +0000 Subject: [PATCH 04/36] feat(Dependencies): Update dependency pytest-aiohttp to v1 | datasource | package | from | to | | ---------- | -------------- | ----- | ----- | | pypi | pytest-aiohttp | 0.3.0 | 1.0.1 | --- poetry.lock | 38 ++++++++++++++++++++++++++++++-------- pyproject.toml | 2 +- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index 955478a..415dbde 100644 --- a/poetry.lock +++ b/poetry.lock @@ -784,15 +784,33 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-aiohttp" -version = "0.3.0" -description = "pytest plugin for aiohttp support" +version = "1.0.1" +description = "Pytest plugin for aiohttp support" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] -aiohttp = ">=2.3.5" -pytest = "*" +aiohttp = ">=3.8.1" +pytest = ">=6.1.0" +pytest-asyncio = ">=0.17.2" + +[package.extras] +testing = ["coverage (==6.2)", "mypy (==0.931)"] + +[[package]] +name = "pytest-asyncio" +version = "0.17.2" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=6.1.0" + +[package.extras] +testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)"] [[package]] name = "pytest-black" @@ -1239,7 +1257,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "d8164649a9533f38a99033093089b78f159263d874249dc0929d1921cd194e3d" +content-hash = "7d3596d4b4f68cd77f7b28bffc319209cf45e4ced253bd9cf161569ddf99effc" [metadata.files] aiohttp = [ @@ -1950,8 +1968,12 @@ pytest = [ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-aiohttp = [ - {file = "pytest-aiohttp-0.3.0.tar.gz", hash = "sha256:c929854339637977375838703b62fef63528598bc0a9d451639eba95f4aaa44f"}, - {file = "pytest_aiohttp-0.3.0-py3-none-any.whl", hash = "sha256:0b9b660b146a65e1313e2083d0d2e1f63047797354af9a28d6b7c9f0726fa33d"}, + {file = "pytest-aiohttp-1.0.1.tar.gz", hash = "sha256:56239a3e0abc393d9037995b2aa04a024c5e172e17046f2ef831a293fa13ace9"}, + {file = "pytest_aiohttp-1.0.1-py3-none-any.whl", hash = "sha256:c53e93f030a46d55b40dd601170da9afd66d2e4f36fc5a4b3b3787011e7b0c93"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.17.2.tar.gz", hash = "sha256:6d895b02432c028e6957d25fc936494e78c6305736e785d9fee408b1efbc7ff4"}, + {file = "pytest_asyncio-0.17.2-py3-none-any.whl", hash = "sha256:e0fe5dbea40516b661ef1bcfe0bd9461c2847c4ef4bb40012324f2454fb7d56d"}, ] pytest-black = [ {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"}, diff --git a/pyproject.toml b/pyproject.toml index 1755be6..25e22c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ mypy_extensions = "0.4.3" [tool.poetry.dev-dependencies] aiohttp-devtools = "1.0.post0" pytest = "6.2.5" -pytest-aiohttp = "0.3.0" +pytest-aiohttp = "1.0.1" pre-commit = "2.16.0" black = {version = "20.8b1",allow-prereleases = true} sphinx-autodoc-typehints = "1.15.3" From d09488c5c5516e22b2f8acecbe60764dd6c8422f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 20 Jan 2022 14:34:40 +0000 Subject: [PATCH 05/36] feat(Dependencies): Update dependency pre-commit to v2.17.0 | datasource | package | from | to | | ---------- | ---------- | ------ | ------ | | pypi | pre-commit | 2.16.0 | 2.17.0 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 415dbde..b9d415b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -643,7 +643,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "pre-commit" -version = "2.16.0" +version = "2.17.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -1257,7 +1257,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "7d3596d4b4f68cd77f7b28bffc319209cf45e4ced253bd9cf161569ddf99effc" +content-hash = "1dfe1af294bcf7a4c53a983dfb0f37ab554806b8c9240b519bca151d0c0c1c69" [metadata.files] aiohttp = [ @@ -1905,8 +1905,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ - {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, - {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, + {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, + {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, ] prometheus-async = [ {file = "prometheus_async-19.2.0-py2.py3-none-any.whl", hash = "sha256:227f516e5bf98a0dc602348381e182358f8b2ed24a8db05e8e34d9cf027bab83"}, diff --git a/pyproject.toml b/pyproject.toml index 25e22c4..33b2905 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ mypy_extensions = "0.4.3" aiohttp-devtools = "1.0.post0" pytest = "6.2.5" pytest-aiohttp = "1.0.1" -pre-commit = "2.16.0" +pre-commit = "2.17.0" black = {version = "20.8b1",allow-prereleases = true} sphinx-autodoc-typehints = "1.15.3" mypy = "0.931" From 3f56be76977ad389ac3c6883101549ceb98c23a2 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 20 Jan 2022 16:55:03 +0000 Subject: [PATCH 06/36] feat(Dependencies): Update dependency pytest-aiohttp to v1.0.2 | datasource | package | from | to | | ---------- | -------------- | ----- | ----- | | pypi | pytest-aiohttp | 1.0.1 | 1.0.2 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index b9d415b..e30005e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -784,7 +784,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-aiohttp" -version = "1.0.1" +version = "1.0.2" description = "Pytest plugin for aiohttp support" category = "dev" optional = false @@ -1257,7 +1257,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "1dfe1af294bcf7a4c53a983dfb0f37ab554806b8c9240b519bca151d0c0c1c69" +content-hash = "a575e4cfffe58f1ca865ebffc0206818c0dcbe6513fc1573ed1d65fb1d1fcbde" [metadata.files] aiohttp = [ @@ -1968,8 +1968,8 @@ pytest = [ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-aiohttp = [ - {file = "pytest-aiohttp-1.0.1.tar.gz", hash = "sha256:56239a3e0abc393d9037995b2aa04a024c5e172e17046f2ef831a293fa13ace9"}, - {file = "pytest_aiohttp-1.0.1-py3-none-any.whl", hash = "sha256:c53e93f030a46d55b40dd601170da9afd66d2e4f36fc5a4b3b3787011e7b0c93"}, + {file = "pytest-aiohttp-1.0.2.tar.gz", hash = "sha256:7b3c40a446058a3a58b1d25323985d8dd9117eea435f77689e6f7d2b36e0a720"}, + {file = "pytest_aiohttp-1.0.2-py3-none-any.whl", hash = "sha256:f775b523c3ad6da0e267fdfeb51f004b46f08c14ac809d25f1a15559969fec76"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.17.2.tar.gz", hash = "sha256:6d895b02432c028e6957d25fc936494e78c6305736e785d9fee408b1efbc7ff4"}, diff --git a/pyproject.toml b/pyproject.toml index 33b2905..f5fbd2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ mypy_extensions = "0.4.3" [tool.poetry.dev-dependencies] aiohttp-devtools = "1.0.post0" pytest = "6.2.5" -pytest-aiohttp = "1.0.1" +pytest-aiohttp = "1.0.2" pre-commit = "2.17.0" black = {version = "20.8b1",allow-prereleases = true} sphinx-autodoc-typehints = "1.15.3" From f2a293f27f7bab2d683214da8a049616d5707575 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 21 Jan 2022 11:11:56 +0000 Subject: [PATCH 07/36] feat(Dependencies): Update dependency pytest-aiohttp to v1.0.3 | datasource | package | from | to | | ---------- | -------------- | ----- | ----- | | pypi | pytest-aiohttp | 1.0.2 | 1.0.3 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index e30005e..b13d65c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -784,7 +784,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-aiohttp" -version = "1.0.2" +version = "1.0.3" description = "Pytest plugin for aiohttp support" category = "dev" optional = false @@ -1257,7 +1257,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "a575e4cfffe58f1ca865ebffc0206818c0dcbe6513fc1573ed1d65fb1d1fcbde" +content-hash = "3fc969c88037d6b47032ca99033d86c8240a1bad6fd0ffcdb01db6e849e84171" [metadata.files] aiohttp = [ @@ -1968,8 +1968,8 @@ pytest = [ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-aiohttp = [ - {file = "pytest-aiohttp-1.0.2.tar.gz", hash = "sha256:7b3c40a446058a3a58b1d25323985d8dd9117eea435f77689e6f7d2b36e0a720"}, - {file = "pytest_aiohttp-1.0.2-py3-none-any.whl", hash = "sha256:f775b523c3ad6da0e267fdfeb51f004b46f08c14ac809d25f1a15559969fec76"}, + {file = "pytest-aiohttp-1.0.3.tar.gz", hash = "sha256:0c8feb48dc8eb80870e2b153acaf62bbbcc207977ebcb74365ffcfe16adc9f15"}, + {file = "pytest_aiohttp-1.0.3-py3-none-any.whl", hash = "sha256:51d8e0ac8799ebedb76ed7f6cf28cd9159cd2b5757ebdc8195a5766eb7a2afe9"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.17.2.tar.gz", hash = "sha256:6d895b02432c028e6957d25fc936494e78c6305736e785d9fee408b1efbc7ff4"}, diff --git a/pyproject.toml b/pyproject.toml index f5fbd2f..2845074 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ mypy_extensions = "0.4.3" [tool.poetry.dev-dependencies] aiohttp-devtools = "1.0.post0" pytest = "6.2.5" -pytest-aiohttp = "1.0.2" +pytest-aiohttp = "1.0.3" pre-commit = "2.17.0" black = {version = "20.8b1",allow-prereleases = true} sphinx-autodoc-typehints = "1.15.3" From 09eb23cd70f3ca5d9716477e31e036696e716fb9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 25 Jan 2022 15:31:26 +0000 Subject: [PATCH 08/36] feat(Dependencies): Update dependency sphinx-autodoc-typehints to v1.16.0 | datasource | package | from | to | | ---------- | ------------------------ | ------ | ------ | | pypi | sphinx-autodoc-typehints | 1.15.3 | 1.16.0 | --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index b13d65c..9d14957 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1014,7 +1014,7 @@ test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.15.3" +version = "1.16.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" category = "dev" optional = false @@ -1024,7 +1024,7 @@ python-versions = ">=3.7" Sphinx = ">=4" [package.extras] -testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] +testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "nptyping (>=1)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] type_comments = ["typed-ast (>=1.4.0)"] [[package]] @@ -1257,7 +1257,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "3fc969c88037d6b47032ca99033d86c8240a1bad6fd0ffcdb01db6e849e84171" +content-hash = "c5c8216c8a856a094e1101a9571456202f51c5897d7c34e19017affca85c9a72" [metadata.files] aiohttp = [ @@ -2115,8 +2115,8 @@ sphinx = [ {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx_autodoc_typehints-1.15.3-py3-none-any.whl", hash = "sha256:5bf7ae28f98dd2cae8b93c4f723fb5f4761ae340194d02d2742a81c7694d2e0a"}, - {file = "sphinx_autodoc_typehints-1.15.3.tar.gz", hash = "sha256:91eb3c227ff0824bee39fabf92d92e21835546903d2ddde5467fd9b6fa10ad7f"}, + {file = "sphinx_autodoc_typehints-1.16.0-py3-none-any.whl", hash = "sha256:b5efe1fb5754349f849ca09b1f5c9b4bb37f1e360f00fbde003b12c60d67cc3a"}, + {file = "sphinx_autodoc_typehints-1.16.0.tar.gz", hash = "sha256:21df6ee692c2c8366f6df13b13e4d4ab8af25cc0dfb65e2d182351528b6eb704"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, diff --git a/pyproject.toml b/pyproject.toml index 2845074..7ecbe89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pytest = "6.2.5" pytest-aiohttp = "1.0.3" pre-commit = "2.17.0" black = {version = "20.8b1",allow-prereleases = true} -sphinx-autodoc-typehints = "1.15.3" +sphinx-autodoc-typehints = "1.16.0" mypy = "0.931" sphinx = "4.4.0" pytest-localserver = "0.5.1.post0" From a2ce35cfcb261ed26344315ccff7e1e1c07aa1a7 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 3 Feb 2022 12:17:38 +0000 Subject: [PATCH 09/36] feat(Dependencies): Update dependency black to v22 | datasource | package | from | to | | ---------- | ------- | ------ | ------ | | pypi | black | 20.8b1 | 22.1.0 | --- poetry.lock | 264 +++++++++++++++++++------------------------------ pyproject.toml | 2 +- 2 files changed, 105 insertions(+), 161 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9d14957..b16b213 100644 --- a/poetry.lock +++ b/poetry.lock @@ -177,25 +177,25 @@ typecheck = ["mypy"] [[package]] name = "black" -version = "20.8b1" +version = "22.1.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = ">=1.1.0" +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" @@ -253,11 +253,14 @@ python-versions = "*" [[package]] name = "click" -version = "7.1.2" +version = "8.0.3" description = "Composable command line interface toolkit" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" @@ -269,11 +272,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.2" +version = "6.3.1" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] tomli = {version = "*", optional = true, markers = "extra == \"toml\""} @@ -624,11 +627,23 @@ invoke = ["invoke (>=1.3)"] [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.4.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pluggy" @@ -938,14 +953,6 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -[[package]] -name = "regex" -version = "2021.3.17" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "requests" version = "2.25.1" @@ -1144,14 +1151,6 @@ category = "dev" optional = false python-versions = ">=3.7" -[[package]] -name = "typed-ast" -version = "1.4.2" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "typing-extensions" version = "4.0.1" @@ -1257,7 +1256,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "c5c8216c8a856a094e1101a9571456202f51c5897d7c34e19017affca85c9a72" +content-hash = "f96ea8f8cc2f4d60e337bf6c7488010785d47dc4be92d0b5636f26a2473f03ca" [metadata.files] aiohttp = [ @@ -1395,7 +1394,29 @@ bcrypt = [ {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] certifi = [ {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, @@ -1468,61 +1489,55 @@ ciso8601 = [ {file = "ciso8601-2.1.3.tar.gz", hash = "sha256:bdbb5b366058b1c87735603b23060962c439ac9be66f1ae91e8c7dbd7d59e262"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, - {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, - {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, - {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, - {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, - {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, - {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, - {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, - {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, - {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, - {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, - {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, - {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, - {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, - {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, - {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, - {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, - {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, - {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, - {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, + {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, + {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, + {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, + {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, + {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, + {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, + {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, + {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, + {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, + {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, + {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, ] cryptography = [ {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, @@ -1897,8 +1912,12 @@ paramiko = [ {file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, @@ -2055,49 +2074,6 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -regex = [ - {file = "regex-2021.3.17-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b97ec5d299c10d96617cc851b2e0f81ba5d9d6248413cd374ef7f3a8871ee4a6"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cb4ee827857a5ad9b8ae34d3c8cc51151cb4a3fe082c12ec20ec73e63cc7c6f0"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:633497504e2a485a70a3268d4fc403fe3063a50a50eed1039083e9471ad0101c"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:a59a2ee329b3de764b21495d78c92ab00b4ea79acef0f7ae8c1067f773570afa"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f85d6f41e34f6a2d1607e312820971872944f1661a73d33e1e82d35ea3305e14"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4651f839dbde0816798e698626af6a2469eee6d9964824bb5386091255a1694f"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:39c44532d0e4f1639a89e52355b949573e1e2c5116106a395642cbbae0ff9bcd"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3d9a7e215e02bd7646a91fb8bcba30bc55fd42a719d6b35cf80e5bae31d9134e"}, - {file = "regex-2021.3.17-cp36-cp36m-win32.whl", hash = "sha256:159fac1a4731409c830d32913f13f68346d6b8e39650ed5d704a9ce2f9ef9cb3"}, - {file = "regex-2021.3.17-cp36-cp36m-win_amd64.whl", hash = "sha256:13f50969028e81765ed2a1c5fcfdc246c245cf8d47986d5172e82ab1a0c42ee5"}, - {file = "regex-2021.3.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9d8d286c53fe0cbc6d20bf3d583cabcd1499d89034524e3b94c93a5ab85ca90"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:201e2619a77b21a7780580ab7b5ce43835e242d3e20fef50f66a8df0542e437f"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d47d359545b0ccad29d572ecd52c9da945de7cd6cf9c0cfcb0269f76d3555689"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ea2f41445852c660ba7c3ebf7d70b3779b20d9ca8ba54485a17740db49f46932"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:486a5f8e11e1f5bbfcad87f7c7745eb14796642323e7e1829a331f87a713daaa"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:18e25e0afe1cf0f62781a150c1454b2113785401ba285c745acf10c8ca8917df"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:a2ee026f4156789df8644d23ef423e6194fad0bc53575534101bb1de5d67e8ce"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:4c0788010a93ace8a174d73e7c6c9d3e6e3b7ad99a453c8ee8c975ddd9965643"}, - {file = "regex-2021.3.17-cp37-cp37m-win32.whl", hash = "sha256:575a832e09d237ae5fedb825a7a5bc6a116090dd57d6417d4f3b75121c73e3be"}, - {file = "regex-2021.3.17-cp37-cp37m-win_amd64.whl", hash = "sha256:8e65e3e4c6feadf6770e2ad89ad3deb524bcb03d8dc679f381d0568c024e0deb"}, - {file = "regex-2021.3.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a0df9a0ad2aad49ea3c7f65edd2ffb3d5c59589b85992a6006354f6fb109bb18"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b98bc9db003f1079caf07b610377ed1ac2e2c11acc2bea4892e28cc5b509d8d5"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:808404898e9a765e4058bf3d7607d0629000e0a14a6782ccbb089296b76fa8fe"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:5770a51180d85ea468234bc7987f5597803a4c3d7463e7323322fe4a1b181578"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:976a54d44fd043d958a69b18705a910a8376196c6b6ee5f2596ffc11bff4420d"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:63f3ca8451e5ff7133ffbec9eda641aeab2001be1a01878990f6c87e3c44b9d5"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bcd945175c29a672f13fce13a11893556cd440e37c1b643d6eeab1988c8b209c"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:3d9356add82cff75413bec360c1eca3e58db4a9f5dafa1f19650958a81e3249d"}, - {file = "regex-2021.3.17-cp38-cp38-win32.whl", hash = "sha256:f5d0c921c99297354cecc5a416ee4280bd3f20fd81b9fb671ca6be71499c3fdf"}, - {file = "regex-2021.3.17-cp38-cp38-win_amd64.whl", hash = "sha256:14de88eda0976020528efc92d0a1f8830e2fb0de2ae6005a6fc4e062553031fa"}, - {file = "regex-2021.3.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4c2e364491406b7888c2ad4428245fc56c327e34a5dfe58fd40df272b3c3dab3"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux1_i686.whl", hash = "sha256:8bd4f91f3fb1c9b1380d6894bd5b4a519409135bec14c0c80151e58394a4e88a"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:882f53afe31ef0425b405a3f601c0009b44206ea7f55ee1c606aad3cc213a52c"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:07ef35301b4484bce843831e7039a84e19d8d33b3f8b2f9aab86c376813d0139"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:360a01b5fa2ad35b3113ae0c07fb544ad180603fa3b1f074f52d98c1096fa15e"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:709f65bb2fa9825f09892617d01246002097f8f9b6dde8d1bb4083cf554701ba"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:c66221e947d7207457f8b6f42b12f613b09efa9669f65a587a2a71f6a0e4d106"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c782da0e45aff131f0bed6e66fbcfa589ff2862fc719b83a88640daa01a5aff7"}, - {file = "regex-2021.3.17-cp39-cp39-win32.whl", hash = "sha256:dc9963aacb7da5177e40874585d7407c0f93fb9d7518ec58b86e562f633f36cd"}, - {file = "regex-2021.3.17-cp39-cp39-win_amd64.whl", hash = "sha256:a0d04128e005142260de3733591ddf476e4902c0c23c1af237d9acf3c96e1b38"}, - {file = "regex-2021.3.17.tar.gz", hash = "sha256:4b8a1fb724904139149a43e172850f35aa6ea97fb0545244dc0b805e0154ed68"}, -] requests = [ {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, @@ -2162,38 +2138,6 @@ tomli = [ {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, ] -typed-ast = [ - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, - {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, - {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, - {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, - {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, - {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, - {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, - {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, - {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, - {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, -] typing-extensions = [ {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, diff --git a/pyproject.toml b/pyproject.toml index 7ecbe89..8e017af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ aiohttp-devtools = "1.0.post0" pytest = "6.2.5" pytest-aiohttp = "1.0.3" pre-commit = "2.17.0" -black = {version = "20.8b1",allow-prereleases = true} +black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.16.0" mypy = "0.931" sphinx = "4.4.0" From 6882efe28ed90e0fc8745869a56c8d8ae5a8191f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 3 Feb 2022 13:19:43 +0000 Subject: [PATCH 10/36] feat(Dependencies): Update dependency pytest-mypy to v0.9.0 | datasource | package | from | to | | ---------- | ----------- | ----- | ----- | | pypi | pytest-mypy | 0.8.1 | 0.9.0 | --- poetry.lock | 13 ++++++++----- pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index b16b213..4622801 100644 --- a/poetry.lock +++ b/poetry.lock @@ -906,7 +906,7 @@ werkzeug = ">=0.10" [[package]] name = "pytest-mypy" -version = "0.8.1" +version = "0.9.0" description = "Mypy static type checker plugin for Pytest" category = "dev" optional = false @@ -916,7 +916,10 @@ python-versions = ">=3.5" attrs = ">=19.0" filelock = ">=3.0" mypy = {version = ">=0.780", markers = "python_version >= \"3.9\""} -pytest = ">=3.5" +pytest = [ + {version = ">=6.2", markers = "python_version >= \"3.10\""}, + {version = ">=4.6", markers = "python_version >= \"3.5\" and python_version < \"3.10\""}, +] [[package]] name = "python-dotenv" @@ -1256,7 +1259,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "f96ea8f8cc2f4d60e337bf6c7488010785d47dc4be92d0b5636f26a2473f03ca" +content-hash = "14f3e749d7c1bf4ccaca0b86149d63865438e602b7ba14c3c70dda68611709f3" [metadata.files] aiohttp = [ @@ -2018,8 +2021,8 @@ pytest-localserver = [ {file = "pytest_localserver-0.5.1.post0-py3-none-any.whl", hash = "sha256:b3ff1b8bade571d54701bad3efd68ca1bb463ad88daa75da15cc8842809659ea"}, ] pytest-mypy = [ - {file = "pytest-mypy-0.8.1.tar.gz", hash = "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11"}, - {file = "pytest_mypy-0.8.1-py3-none-any.whl", hash = "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b"}, + {file = "pytest-mypy-0.9.0.tar.gz", hash = "sha256:4f2a08d4ff34737eb6ad6402df1e424f43c9213181b32d675e4e3cafbda67bbd"}, + {file = "pytest_mypy-0.9.0-py3-none-any.whl", hash = "sha256:f8b5c6be405c8605959d1c79f20dcdeb2b3667fce8f264ff32bc854e5249f161"}, ] python-dotenv = [ {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, diff --git a/pyproject.toml b/pyproject.toml index 8e017af..a8a909b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ pytest-dotenv = "0.5.2" sphinxcontrib-programoutput = "0.17" docker-compose = "1.29.2" pytest-black = "0.3.12" -pytest-mypy = "0.8.1" +pytest-mypy = "0.9.0" pytest-flake8 = "1.0.7" pytest-isort = "2.0.0" From 4b9e5eaffb7c10e8533e468062dbe80c8081e57e Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 7 Feb 2022 18:23:06 +0000 Subject: [PATCH 11/36] feat(Dependencies): Update dependency pytest-mypy to v0.9.1 | datasource | package | from | to | | ---------- | ----------- | ----- | ----- | | pypi | pytest-mypy | 0.9.0 | 0.9.1 | --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4622801..2feb957 100644 --- a/poetry.lock +++ b/poetry.lock @@ -906,7 +906,7 @@ werkzeug = ">=0.10" [[package]] name = "pytest-mypy" -version = "0.9.0" +version = "0.9.1" description = "Mypy static type checker plugin for Pytest" category = "dev" optional = false @@ -918,7 +918,7 @@ filelock = ">=3.0" mypy = {version = ">=0.780", markers = "python_version >= \"3.9\""} pytest = [ {version = ">=6.2", markers = "python_version >= \"3.10\""}, - {version = ">=4.6", markers = "python_version >= \"3.5\" and python_version < \"3.10\""}, + {version = ">=4.6", markers = "python_version >= \"3.6\" and python_version < \"3.10\""}, ] [[package]] @@ -1259,7 +1259,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "14f3e749d7c1bf4ccaca0b86149d63865438e602b7ba14c3c70dda68611709f3" +content-hash = "87136df0b74d9aa98133aa8c62fbb6c15052b86623956f3a52a9acf067af5ccc" [metadata.files] aiohttp = [ @@ -2021,8 +2021,8 @@ pytest-localserver = [ {file = "pytest_localserver-0.5.1.post0-py3-none-any.whl", hash = "sha256:b3ff1b8bade571d54701bad3efd68ca1bb463ad88daa75da15cc8842809659ea"}, ] pytest-mypy = [ - {file = "pytest-mypy-0.9.0.tar.gz", hash = "sha256:4f2a08d4ff34737eb6ad6402df1e424f43c9213181b32d675e4e3cafbda67bbd"}, - {file = "pytest_mypy-0.9.0-py3-none-any.whl", hash = "sha256:f8b5c6be405c8605959d1c79f20dcdeb2b3667fce8f264ff32bc854e5249f161"}, + {file = "pytest-mypy-0.9.1.tar.gz", hash = "sha256:9ffa3bf405c12c5c6be9e92e22bebb6ab2c91b9c32f45b0f0c93af473269ab5c"}, + {file = "pytest_mypy-0.9.1-py3-none-any.whl", hash = "sha256:a2505fcf61f1c0c51f950d4623ea8ca2daf6fb2101a5603554bad2e130202083"}, ] python-dotenv = [ {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, diff --git a/pyproject.toml b/pyproject.toml index a8a909b..ec6f536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ pytest-dotenv = "0.5.2" sphinxcontrib-programoutput = "0.17" docker-compose = "1.29.2" pytest-black = "0.3.12" -pytest-mypy = "0.9.0" +pytest-mypy = "0.9.1" pytest-flake8 = "1.0.7" pytest-isort = "2.0.0" From 3d9802ef6dc0e641f6269067ecc3a5537e46c956 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 12 Feb 2022 23:02:52 +0000 Subject: [PATCH 12/36] feat(Dependencies): Update dependency pytest-aiohttp to v1.0.4 | datasource | package | from | to | | ---------- | -------------- | ----- | ----- | | pypi | pytest-aiohttp | 1.0.3 | 1.0.4 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2feb957..1ed49f8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -799,7 +799,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pytest-aiohttp" -version = "1.0.3" +version = "1.0.4" description = "Pytest plugin for aiohttp support" category = "dev" optional = false @@ -1259,7 +1259,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "87136df0b74d9aa98133aa8c62fbb6c15052b86623956f3a52a9acf067af5ccc" +content-hash = "d434a2012440f1744f997097f4751982b30bb2d9ab52fa5df07707c5dbc3655f" [metadata.files] aiohttp = [ @@ -1990,8 +1990,8 @@ pytest = [ {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-aiohttp = [ - {file = "pytest-aiohttp-1.0.3.tar.gz", hash = "sha256:0c8feb48dc8eb80870e2b153acaf62bbbcc207977ebcb74365ffcfe16adc9f15"}, - {file = "pytest_aiohttp-1.0.3-py3-none-any.whl", hash = "sha256:51d8e0ac8799ebedb76ed7f6cf28cd9159cd2b5757ebdc8195a5766eb7a2afe9"}, + {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, + {file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.17.2.tar.gz", hash = "sha256:6d895b02432c028e6957d25fc936494e78c6305736e785d9fee408b1efbc7ff4"}, diff --git a/pyproject.toml b/pyproject.toml index ec6f536..64f1675 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ mypy_extensions = "0.4.3" [tool.poetry.dev-dependencies] aiohttp-devtools = "1.0.post0" pytest = "6.2.5" -pytest-aiohttp = "1.0.3" +pytest-aiohttp = "1.0.4" pre-commit = "2.17.0" black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.16.0" From ca8c3d87194367577851a9873874f7d75c7c9acb Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 13 Feb 2022 00:59:41 +0000 Subject: [PATCH 13/36] feat(Dependencies): Update dependency sphinx-autodoc-typehints to v1.17.0 | datasource | package | from | to | | ---------- | ------------------------ | ------ | ------ | | pypi | sphinx-autodoc-typehints | 1.16.0 | 1.17.0 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1ed49f8..f4d5dde 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1024,7 +1024,7 @@ test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.16.0" +version = "1.17.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" category = "dev" optional = false @@ -1259,7 +1259,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "d434a2012440f1744f997097f4751982b30bb2d9ab52fa5df07707c5dbc3655f" +content-hash = "749c77d7ce92c58545de1ec472a6eafa68aca66db477ed9aec5be5be1597209d" [metadata.files] aiohttp = [ @@ -2094,8 +2094,8 @@ sphinx = [ {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx_autodoc_typehints-1.16.0-py3-none-any.whl", hash = "sha256:b5efe1fb5754349f849ca09b1f5c9b4bb37f1e360f00fbde003b12c60d67cc3a"}, - {file = "sphinx_autodoc_typehints-1.16.0.tar.gz", hash = "sha256:21df6ee692c2c8366f6df13b13e4d4ab8af25cc0dfb65e2d182351528b6eb704"}, + {file = "sphinx_autodoc_typehints-1.17.0-py3-none-any.whl", hash = "sha256:081daf53077b4ae1c28347d6d858e13e63aefe3b4aacef79fd717dd60687b470"}, + {file = "sphinx_autodoc_typehints-1.17.0.tar.gz", hash = "sha256:51c7b3f5cb9ccd15d0b52088c62df3094f1abd9612930340365c26def8629a14"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, diff --git a/pyproject.toml b/pyproject.toml index 64f1675..bc87221 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pytest = "6.2.5" pytest-aiohttp = "1.0.4" pre-commit = "2.17.0" black = {version = "22.1.0",allow-prereleases = true} -sphinx-autodoc-typehints = "1.16.0" +sphinx-autodoc-typehints = "1.17.0" mypy = "0.931" sphinx = "4.4.0" pytest-localserver = "0.5.1.post0" From 026bbfda93efb9941733650f25e49fb1af712a32 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 15 Feb 2022 16:55:12 +0000 Subject: [PATCH 14/36] feat(Dependencies): Update dependency prometheus_async to v22 | datasource | package | from | to | | ---------- | ---------------- | ---- | ------ | | pypi | prometheus_async | 19.2 | 22.1.0 | --- poetry.lock | 21 +++++++++++---------- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index f4d5dde..8e23e58 100644 --- a/poetry.lock +++ b/poetry.lock @@ -674,23 +674,24 @@ virtualenv = ">=20.0.8" [[package]] name = "prometheus-async" -version = "19.2.0" +version = "22.1.0" description = "Async helpers for prometheus_client." category = "main" optional = false -python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4" +python-versions = ">=3.7" [package.dependencies] aiohttp = {version = ">=3", optional = true, markers = "extra == \"aiohttp\""} -prometheus-client = ">=0.0.18" +prometheus_client = ">=0.8.0" +typing_extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} wrapt = "*" [package.extras] aiohttp = ["aiohttp (>=3)"] consul = ["aiohttp (>=3)"] -dev = ["aiohttp (>=3)", "twisted", "aiohttp", "sphinx", "sphinxcontrib-asyncio", "coverage", "pytest (<4.1)", "pytest-twisted", "pre-commit", "pytest-asyncio"] -docs = ["aiohttp", "sphinx", "sphinxcontrib-asyncio", "twisted"] -tests = ["coverage", "pytest (<4.1)", "pytest-asyncio"] +dev = ["pre-commit", "pytest-twisted", "tomli", "cogapp", "mypy", "coverage", "pytest", "pytest-asyncio", "aiohttp", "furo", "myst-parser", "sphinx-notfound-page", "sphinx", "sphinxcontrib-asyncio", "twisted"] +docs = ["aiohttp", "furo", "myst-parser", "sphinx-notfound-page", "sphinx", "sphinxcontrib-asyncio", "twisted"] +tests = ["coverage", "pytest", "pytest-asyncio"] twisted = ["twisted"] [[package]] @@ -1158,7 +1159,7 @@ python-versions = ">=3.7" name = "typing-extensions" version = "4.0.1" description = "Backported and Experimental Type Hints for Python 3.6+" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -1259,7 +1260,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "749c77d7ce92c58545de1ec472a6eafa68aca66db477ed9aec5be5be1597209d" +content-hash = "2ca69abb8a21105b2aaefefd95d6b5119e7a5858b09e78318738c5f96fda502d" [metadata.files] aiohttp = [ @@ -1931,8 +1932,8 @@ pre-commit = [ {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, ] prometheus-async = [ - {file = "prometheus_async-19.2.0-py2.py3-none-any.whl", hash = "sha256:227f516e5bf98a0dc602348381e182358f8b2ed24a8db05e8e34d9cf027bab83"}, - {file = "prometheus_async-19.2.0.tar.gz", hash = "sha256:3cc68d1f39e9bbf16dbd0b51103d87671b3cbd1d75a72cda472cd9a35cc9d0d2"}, + {file = "prometheus-async-22.1.0.tar.gz", hash = "sha256:2abdf0ffd419aa9889b375b0dd5af4cb5708457a0b19ae21d3968fea68c3faf3"}, + {file = "prometheus_async-22.1.0-py3-none-any.whl", hash = "sha256:debd4b0a9e963f3118890c1164423b273b3b6c6a746521b925a2e5b5ab8cd96f"}, ] prometheus-client = [ {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"}, diff --git a/pyproject.toml b/pyproject.toml index bc87221..343da96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ python = "^3.9" aiohttp = "3.8.1" aioinflux = "0.9.0" aiohttp-swagger = "1.0.16" -prometheus_async = {version = "19.2",extras = ["aiohttp"]} +prometheus_async = {version = "22.1.0",extras = ["aiohttp"]} aiohttp_jinja2 = "1.5" aiosmtplib = "1.1.6" mypy_extensions = "0.4.3" From 559d9d84fdb8e51774653cc49d0c18e14d9d444c Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 1 Mar 2022 20:45:04 +0000 Subject: [PATCH 15/36] feat(Dependencies): Update actions/checkout action | datasource | package | from | to | | ----------- | ---------------- | ------ | ------ | | github-tags | actions/checkout | v2 | v3 | | github-tags | actions/checkout | v2.4.0 | v3.0.0 | --- .github/workflows/build-image.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 1013e47..b1384ec 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -9,7 +9,7 @@ jobs: uses: rokroskar/workflow-run-cleanup-action@v0.3.3 env: GITHUB_TOKEN: "${{ secrets.GITHUBSECRET2 }}" - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3.0.0 - name: Build with retry uses: Wandalen/wretry.action@v1.0.11 with: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6926c6e..3df5453 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL From 969f887ddf9a505f9ef954d006af25cb3c789461 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sat, 5 Mar 2022 09:16:37 +0000 Subject: [PATCH 16/36] feat(Dependencies): Update dependency pytest-flake8 to v1.1.0 | datasource | package | from | to | | ---------- | ------------- | ----- | ----- | | pypi | pytest-flake8 | 1.0.7 | 1.1.0 | --- poetry.lock | 92 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8e23e58..f25db8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -272,7 +272,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -870,7 +870,7 @@ python-dotenv = ">=0.9.1" [[package]] name = "pytest-flake8" -version = "1.0.7" +version = "1.1.0" description = "pytest plugin to check FLAKE8 requirements" category = "dev" optional = false @@ -1260,7 +1260,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "2ca69abb8a21105b2aaefefd95d6b5119e7a5858b09e78318738c5f96fda502d" +content-hash = "0e8ec102909d04b6aa79f8ccd389001c4fa88a8dea2192aa0c117fe21c7e04e8" [metadata.files] aiohttp = [ @@ -1501,47 +1501,47 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] cryptography = [ {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, @@ -2010,8 +2010,8 @@ pytest-dotenv = [ {file = "pytest_dotenv-0.5.2-py3-none-any.whl", hash = "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f"}, ] pytest-flake8 = [ - {file = "pytest-flake8-1.0.7.tar.gz", hash = "sha256:f0259761a903563f33d6f099914afef339c085085e643bee8343eb323b32dd6b"}, - {file = "pytest_flake8-1.0.7-py2.py3-none-any.whl", hash = "sha256:c28cf23e7d359753c896745fd4ba859495d02e16c84bac36caa8b1eec58f5bc1"}, + {file = "pytest-flake8-1.1.0.tar.gz", hash = "sha256:358d449ca06b80dbadcb43506cd3e38685d273b4968ac825da871bd4cc436202"}, + {file = "pytest_flake8-1.1.0-py2.py3-none-any.whl", hash = "sha256:f1b19dad0b9f0aa651d391c9527ebc20ac1a0f847aa78581094c747462bfa182"}, ] pytest-isort = [ {file = "pytest-isort-2.0.0.tar.gz", hash = "sha256:821a8c5c9c4f3a3c52cfa9c541fbe89ac9e28728125125af53724c4c3f129117"}, diff --git a/pyproject.toml b/pyproject.toml index 343da96..4e89792 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ sphinxcontrib-programoutput = "0.17" docker-compose = "1.29.2" pytest-black = "0.3.12" pytest-mypy = "0.9.1" -pytest-flake8 = "1.0.7" +pytest-flake8 = "1.1.0" pytest-isort = "2.0.0" [tool.poetry.scripts] From 14ed77384a01acf17dcb462d0da8683d860e5d3a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 24 Mar 2022 17:54:01 +0000 Subject: [PATCH 17/36] feat(Dependencies): Update dependency mypy to v0.942 | datasource | package | from | to | | ---------- | ------- | ----- | ----- | | pypi | mypy | 0.931 | 0.942 | --- poetry.lock | 48 ++++++++++++++++++++++++++---------------------- pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/poetry.lock b/poetry.lock index f25db8c..cb25b02 100644 --- a/poetry.lock +++ b/poetry.lock @@ -564,7 +564,7 @@ python-versions = ">=3.6" [[package]] name = "mypy" -version = "0.931" +version = "0.942" description = "Optional static typing for Python" category = "dev" optional = false @@ -578,6 +578,7 @@ typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] [[package]] name = "mypy-extensions" @@ -1260,7 +1261,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "0e8ec102909d04b6aa79f8ccd389001c4fa88a8dea2192aa0c117fe21c7e04e8" +content-hash = "04f5a4369cc065734d5e27c456f8329d8024b7f6291111038b25167985b5b88b" [metadata.files] aiohttp = [ @@ -1878,26 +1879,29 @@ multidict = [ {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, ] mypy = [ - {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, - {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, - {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, - {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, - {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, - {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, - {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, - {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, - {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, - {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, - {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, - {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, - {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, - {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, - {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, - {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, - {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, - {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, - {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, - {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, + {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, + {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, + {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, + {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, + {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, + {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, + {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, + {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, + {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, + {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, + {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, + {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, + {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, + {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, + {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, + {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, + {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, + {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, + {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, + {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, + {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, + {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, + {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, diff --git a/pyproject.toml b/pyproject.toml index 4e89792..77c6b43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ pytest-aiohttp = "1.0.4" pre-commit = "2.17.0" black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.17.0" -mypy = "0.931" +mypy = "0.942" sphinx = "4.4.0" pytest-localserver = "0.5.1.post0" pytest-cov = "3.0.0" From 6afc0852fefef0c462afbaa57d422fe3719f6c3d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 24 Mar 2022 23:23:41 +0000 Subject: [PATCH 18/36] feat(Dependencies): Update dependency python to v3.10.4 | datasource | package | from | to | | ---------- | ------- | ------ | ------ | | docker | python | 3.10.2 | 3.10.4 | | docker | python | 3.10.2 | 3.10.4 | | docker | python | 3.10.2 | 3.10.4 | --- Dockerfile | 4 ++-- Dockerfile.dev | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8fcc1e1..bbe00b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.2-alpine as builder +FROM python:3.10.4-alpine as builder ADD src /code/src ADD pyproject.toml poetry.lock /code/ @@ -8,7 +8,7 @@ RUN pip install poetry && poetry build -f wheel # by using a build container we prevent us from carrying around poetry # alongside its dependencies -FROM python:3.10.2-alpine +FROM python:3.10.4-alpine ARG OS_CREDITS_VERSION ARG WHEEL_NAME=os_credits-1.1.0-py3-none-any.whl EXPOSE 80 diff --git a/Dockerfile.dev b/Dockerfile.dev index ac7fe75..302c21a 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,6 +1,6 @@ # Expected to be called with `--volume $(PWD)/src:/code/src:ro` # Uses `adev` from `aiohttp-devtools` to restart the application on any code change -FROM python:3.10.2-alpine +FROM python:3.10.4-alpine ADD src /code/src ADD pyproject.toml poetry.lock /code/ From d91361e4375f8378097eacce37a6d6561b18b50c Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Wed, 30 Mar 2022 17:57:34 +0200 Subject: [PATCH 19/36] feat(database): change from influxdb to timescaledb --- .default.env | 34 +- .editorconfig | 2 +- .gitignore | 2 + Dockerfile | 7 +- Dockerfile.dev | 4 +- Makefile | 69 +- README.rst | 29 +- config/credits.toml.in | 57 - db_init/init_db.sh | 23 + docker-compose.yml | 50 +- mypy.ini | 6 - poetry.lock | 2028 +++++++++------------ pyproject.toml | 33 +- src/{os_credits/credits => }/__init__.py | 0 src/os_credits/__init__.py | 2 +- src/os_credits/credits/base_models.py | 238 --- src/os_credits/credits/billing.py | 40 - src/os_credits/credits/models.py | 90 - src/os_credits/credits/tasks.py | 331 ---- src/os_credits/db_client/__init__.py | 1 + src/os_credits/db_client/client.py | 444 +++++ src/os_credits/db_client/model.py | 147 ++ src/os_credits/db_client/tasks.py | 179 ++ src/os_credits/exceptions.py | 43 - src/os_credits/influx/__init__.py | 0 src/os_credits/influx/client.py | 312 ---- src/os_credits/influx/exceptions.py | 7 - src/os_credits/influx/helper.py | 205 --- src/os_credits/influx/model.py | 202 -- src/os_credits/log.py | 39 +- src/os_credits/main.py | 129 +- src/os_credits/notifications.py | 273 --- src/os_credits/perun/__init__.py | 0 src/os_credits/perun/attributes.py | 144 -- src/os_credits/perun/attributesManager.py | 72 - src/os_credits/perun/base_attributes.py | 299 --- src/os_credits/perun/exceptions.py | 55 - src/os_credits/perun/group.py | 285 --- src/os_credits/perun/groupsManager.py | 33 - src/os_credits/perun/requests.py | 71 - src/os_credits/perun/resourcesManager.py | 27 - src/os_credits/prometheus_metrics.py | 12 - src/os_credits/settings.py | 141 +- src/os_credits/views.py | 483 ++--- src/os_credits/worker_helper.py | 28 +- tests/__init__.py | 0 tests/conftest.py | 79 - tests/docker-compose.yml | 15 - tests/patches.py | 96 - tests/test.env | 8 - tests/test_application.py | 529 ------ tests/test_attribute.py | 100 - tests/test_calculations.py | 94 - tests/test_credits_history.py | 84 - tests/test_group.py | 10 - tests/test_influx.py | 129 -- tests/test_notifications.py | 187 -- tests/test_perun.py | 113 -- tests/test_settings.py | 51 - 59 files changed, 2076 insertions(+), 6095 deletions(-) delete mode 100644 config/credits.toml.in create mode 100644 db_init/init_db.sh delete mode 100644 mypy.ini rename src/{os_credits/credits => }/__init__.py (100%) delete mode 100644 src/os_credits/credits/base_models.py delete mode 100644 src/os_credits/credits/billing.py delete mode 100644 src/os_credits/credits/models.py delete mode 100644 src/os_credits/credits/tasks.py create mode 100644 src/os_credits/db_client/__init__.py create mode 100644 src/os_credits/db_client/client.py create mode 100644 src/os_credits/db_client/model.py create mode 100644 src/os_credits/db_client/tasks.py delete mode 100644 src/os_credits/exceptions.py delete mode 100644 src/os_credits/influx/__init__.py delete mode 100644 src/os_credits/influx/client.py delete mode 100644 src/os_credits/influx/exceptions.py delete mode 100644 src/os_credits/influx/helper.py delete mode 100644 src/os_credits/influx/model.py delete mode 100644 src/os_credits/notifications.py delete mode 100644 src/os_credits/perun/__init__.py delete mode 100644 src/os_credits/perun/attributes.py delete mode 100644 src/os_credits/perun/attributesManager.py delete mode 100644 src/os_credits/perun/base_attributes.py delete mode 100644 src/os_credits/perun/exceptions.py delete mode 100644 src/os_credits/perun/group.py delete mode 100644 src/os_credits/perun/groupsManager.py delete mode 100644 src/os_credits/perun/requests.py delete mode 100644 src/os_credits/perun/resourcesManager.py delete mode 100644 src/os_credits/prometheus_metrics.py delete mode 100644 tests/__init__.py delete mode 100644 tests/conftest.py delete mode 100644 tests/docker-compose.yml delete mode 100644 tests/patches.py delete mode 100644 tests/test.env delete mode 100644 tests/test_application.py delete mode 100644 tests/test_attribute.py delete mode 100644 tests/test_calculations.py delete mode 100644 tests/test_credits_history.py delete mode 100644 tests/test_group.py delete mode 100644 tests/test_influx.py delete mode 100644 tests/test_notifications.py delete mode 100644 tests/test_perun.py delete mode 100644 tests/test_settings.py diff --git a/.default.env b/.default.env index 1f39cae..7d00b65 100644 --- a/.default.env +++ b/.default.env @@ -1,25 +1,13 @@ -# Your Login to Perun -OS_CREDITS_PERUN_LOGIN= -# Corresponding Password -OS_CREDITS_PERUN_PASSWORD= -# ID of your virtual organization -OS_CREDITS_PERUN_VO_ID= -# how to connect to the InfluxDB storing the prometheus data -INFLUXDB_HOST=portal_influxdb -INFLUXDB_USER=prometheus -INFLUXDB_USER_PASSWORD=secret -INFLUXDB_DB=portal_prometheus +#TimescaleDB +POSTGRES_DB=credits_db +POSTGRES_PORT=5432 +POSTGRES_USER=postgres +POSTGRES_PASSWORD=password +POSTGRES_HOST=timescaledb -API_KEY="change-me-but-not-to-123" +#API key to secure some endpoints +API_KEY=super-secret -# Semicolon-separated list of project/group names. -# If set only those will be billed -# OS_CREDITS_PROJECT_WHITELIST -# How many worker tasks will process the queue. Default is 10 -# OS_CREDITS_WORKERS=10 -# Float precision of `credits_current` inside Perun -# OS_CREDITS_PRECISION=2 -# Cost of running one vCPU core for one hour -# VCPU_CREDIT_PER_HOUR=1 -# Cost of running one GB of RAM for one hour -# RAM_CREDIT_PER_HOUR=0.3 +METRICS_TO_BILL={"project_vcpu_usage": 1.0, "project_mb_usage": 0.3} + +ENDPOINTS_ONLY=True diff --git a/.editorconfig b/.editorconfig index 5162f64..220548e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,7 +15,7 @@ max_line_length = 80 charset = utf-8 indent_style = space indent_size = 4 -max_line_length = 88 +max_line_length = 140 # 2 space indentation [*.{html,css,less,scss,yml,json}] diff --git a/.gitignore b/.gitignore index b93341c..30091c9 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,5 @@ dmypy.json # project specific settings .vim/ .nvimrc + +db_data diff --git a/Dockerfile b/Dockerfile index 28b4507..398c723 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,21 +3,22 @@ FROM python:3.10.1-alpine as builder ADD src /code/src ADD pyproject.toml poetry.lock /code/ WORKDIR /code -RUN apk add gcc musl-dev python3-dev libffi-dev openssl-dev cargo +RUN apk -U upgrade && apk --no-cache add gcc wget linux-headers musl-dev libffi-dev libressl-dev cargo build-base libpq-dev +RUN pip install cryptography RUN pip install poetry && poetry build -f wheel # by using a build container we prevent us from carrying around poetry # alongside its dependencies FROM python:3.10.1-alpine ARG OS_CREDITS_VERSION -ARG WHEEL_NAME=os_credits-1.2.0-py3-none-any.whl +ARG WHEEL_NAME=os_credits-2.0.0-py3-none-any.whl EXPOSE 80 ENV CREDITS_PORT 80 ENV CREDITS_HOST 0.0.0.0 COPY --from=builder /code/dist/$WHEEL_NAME /tmp/ # wget to perform healthcheck against /ping endpoint -RUN apk update && apk --no-cache add gcc wget linux-headers musl-dev \ +RUN apk update && apk --no-cache add gcc wget linux-headers musl-dev libpq-dev \ && pip install --no-cache /tmp/$WHEEL_NAME \ && rm /tmp/$WHEEL_NAME diff --git a/Dockerfile.dev b/Dockerfile.dev index 3219248..8e6265b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -5,14 +5,14 @@ FROM python:3.10.1-alpine ADD src /code/src ADD pyproject.toml poetry.lock /code/ WORKDIR /code -RUN apk -U upgrade && apk --no-cache add gcc wget linux-headers musl-dev libffi-dev libressl-dev cargo +RUN apk -U upgrade && apk --no-cache add gcc wget linux-headers musl-dev libffi-dev libressl-dev cargo build-base libpq-dev RUN pip install cryptography RUN pip install poetry # install to system RUN poetry config virtualenvs.create false # we do not need any development packages except aiohttp-devtools to # automatically restart the app once we changed the bind-mounted source code -RUN cp /usr/local/lib/python3.9/site-packages/certifi/cacert.pem /cacert.pem +RUN cp /usr/local/lib/python3.10/site-packages/certifi/cacert.pem /cacert.pem RUN export REQUESTS_CA_BUNDLE=/cacert.pem && poetry install --no-dev && pip install --no-cache aiohttp-devtools EXPOSE 80 diff --git a/Makefile b/Makefile index e8b9134..41f34af 100644 --- a/Makefile +++ b/Makefile @@ -28,75 +28,22 @@ clean-build: ## Remove any python build artifacts .PHONY: docker-build docker-build: ## Call bin/build_docker.py with $DOCKER_USERNAME[$USER] and $DOCKER_IMAGENAME[os_credits] - find . -type d -name '__pycache__' -prune -exec rm -rf {} \; + find src -type d -name '__pycache__' -prune -exec rm -rf {} \; poetry run bin/build_docker.py -u $(DOCKER_USERNAME) -i $(DOCKER_IMAGENAME) .PHONY: docker-build-dev docker-build-dev: ## Build Dockerfile.dev with name 'os_credits-dev' - find . -type d -name '__pycache__' -prune -exec rm -rf {} \; - docker build -f Dockerfile.dev -t os_credits-dev . - -.PHONY: build-run-dev -build-run-dev: ## Build Dockerfile.dev with name 'os_credits-dev' and start docker-compose - find . -type d -name '__pycache__' -prune -exec rm -rf {} \; + find src -type d -name '__pycache__' -prune -exec rm -rf {} \; docker build -f Dockerfile.dev -t os_credits-dev . - docker-compose up -d - docker stop os_credits_credits_1 - docker start os_credits_credits_1 -.PHONY: docker-run-dev -docker-run-dev: ## Run 'os_credits-dev' inside 'docker-compose.yml' attached - os_credits-dev:80 -> localhost:8000 - poetry run docker-compose up +.PHONY: up-dev +up-dev: ## Build Dockerfile.dev with name 'os_credits-dev' and docker-compose up --detach + docker-compose up --detach -.PHONY: docker-project_usage-dev -docker-project_usage-dev: ## Run 'os_credits-dev' and integrate it into the 'dev' profile of 'project_usage' - docker stop portal_credits || true - docker rm portal_credits || true - docker run \ - --publish=8002:80 \ - --name portal_credits \ - --network project_usage_portal \ - --volume $(PWD)/src:/code/src:ro \ - --env-file .env \ - --env MAIL_NOT_STARTTLS=1 \ - --env MAIL_SMTP_SERVER=portal_smtp_server \ - --detach \ - os_credits-dev:latest +.PHONY: up-dev +down: ## docker-compose down + docker-compose down .PHONY: docs docs: ## Build HTML documentation cd docs && $(MAKE) html - -.PHONY: docs-doctest -docs-doctest: ## Run doctests inside documentation - cd docs && $(MAKE) doctest - -.PHONY: test -test: ## Start tests/docker-compose.yml, run test suite and stop docker-compose - poetry run docker-compose -f tests/docker-compose.yml up --detach - @echo 'Waiting until InfluxDB is ready' - . tests/test.env && until `curl -o /dev/null -s -I -f "http://$$INFLUXDB_HOST:$$INFLUXDB_PORT/ping"`; \ - do printf '.'; \ - sleep 1; \ - done - poetry run pytest --color=yes tests src || true - poetry run docker-compose -f tests/docker-compose.yml down --volumes --remove-orphans - -.PHONY: test-online -test-online: ## Same as `test` but does also run tests against Perun - env TEST_ONLINE=1 $(MAKE) test - -.PHONY: test-online-only -test-online-only: ## Only run tests against Perun - poetry run env TEST_ONLINE=1 pytest --color=yes --no-cov tests/test_perun.py - -.PHONY: mypy -mypy: ## Run `mypy`, a static type checker for python, see 'htmlcov/mypy/index.html' - poetry run mypy src/os_credits --html-report=htmlcov/mypy - -.PHONY: setup -setup: ## Setup development environment - @echo 'Requires poetry from - https://poetry.eustace.io/docs/' - poetry install - poetry run pre-commit install -t pre-commit - poetry run pre-commit install -t pre-push diff --git a/README.rst b/README.rst index beb088e..a0d5072 100644 --- a/README.rst +++ b/README.rst @@ -12,18 +12,17 @@ The service is integrated into the *Portal stack* of the `project_usage project `_, please refer to its wiki for corresponding setup instructions/required services. -The development has been part of the master thesis **Accounting and Reporting of -OpenStack Cloud instances via Prometheus** which therefore -contains a large introduction to the area of *Cloud Billing* and motivations which lead -to the current design. A development manual can be found inside the -``docs/`` folder of this repository which can be build via ``make docs``. - Development ----------- The project has been developed with Python 3.7 and uses the `aiohttp `_ framework communication. Its dependencies are managed via `Poetry `_. +If you want to develop while using the whole stack, please see `project_usage project +`_ for more information. +If you only need some endpoints which do not need the whole stack (e.g. /cost_per_hour), +copy the .default.env to .env and run make up-dev. This will build the container from +Dockerfile.dev. Please note that a named volume will be created: credits_data. Monitoring/Debugging ~~~~~~~~~~~~~~~~~~~~ @@ -44,16 +43,14 @@ the image. To modify this values call ``make build-docker DOCKER_USERNAME= DOCKER_IMAGENAME=``. -Stack integration +Additional notes ~~~~~~~~~~~~~~~~~ -To run the code use the provided ``Dockerfile.dev`` which you can build -via ``make docker-build-dev``. Afterward use ``make docker-project_usage-dev`` to -integrate the development container into the ``project_usage`` stack. +The development has been part of the master thesis **Accounting and Reporting of +OpenStack Cloud instances via Prometheus** which therefore +contains a large introduction to the area of *Cloud Billing* and motivations which lead +to the current design. -The development container is using the ``adev runserver`` command from -the -```aiohttp-devtools`` `__ -which will restart your app on any code change. But since the code is -bind mounted inside the container you can simply continue editing and -have it restart on any change. +Update 2022: +The design of this system changed due to exchanging InfluxDB with TimescaleDB +and some unforeseen requirements. diff --git a/config/credits.toml.in b/config/credits.toml.in deleted file mode 100644 index 7b7e889..0000000 --- a/config/credits.toml.in +++ /dev/null @@ -1,57 +0,0 @@ -number_of_workers = 10 -# precision by which float results should be rounded before saving -credits_precision = 2 -# if present only groups whose exact name is part of the list will be processed -#project_whitelist = ['credits'] - -[perun] -vo_id = your_vo_id_here -login = your_login_here -password = your_password_here - -[influxdb] -host = your_host_here -port = 8086 -username = your_username_here -password = your_password_here -database = your_db_here - -[logging] -version=1 -disable_existing_loggers = false - -[logging.formatters.task_handler] -format = "[%(task_id)s] %(levelname)-8s %(asctime)s: %(message)s" - -[logging.formatters.simple_handler] -format = "%(asctime)s %(levelname)-8s %(name)-15s %(message)s" - -[logging.filters.task_id_filter] -'()' = 'os_credits.log._TaskIdFilter' - -[logging.handlers.with_task_id] -class = 'logging.StreamHandler' -level = 'DEBUG' -stream = 'ext://sys.stdout' -formatter = 'task_handler' - -[logging.handlers.simple] -class = 'logging.StreamHandler' -level = 'DEBUG' -stream = 'ext://sys.stdout' -formatter = 'simple_handler' - -[logging.loggers."os_credits.tasks"] -level = 'INFO' -handlers = ['with_task_id'] -filters = ['task_id_filter'] - -[logging.loggers."os_credits.internal"] -level = 'INFO' -handlers = ['with_task_id'] -filters = ['task_id_filter'] - -[logging.loggers."os_credits.requests"] -level = 'INFO' -handlers = ['with_task_id'] -filters = ['task_id_filter'] diff --git a/db_init/init_db.sh b/db_init/init_db.sh new file mode 100644 index 0000000..500cf72 --- /dev/null +++ b/db_init/init_db.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +FILE=/docker-entrypoint-initdb.d/credits_db.dump + +psql -v ON_ERROR_STOP=1 -U postgres -d credits_db <<-EOSQL + ALTER ROLE postgres SET client_encoding TO 'utf8'; + ALTER ROLE postgres SET default_transaction_isolation TO 'read committed'; + ALTER ROLE postgres SET timezone TO 'UTC'; + GRANT ALL PRIVILEGES ON DATABASE credits_db TO postgres; + CREATE EXTENSION IF NOT EXISTS timescaledb; +EOSQL + +if test -f "$FILE"; then +echo "got here" +psql -v ON_ERROR_STOP=1 -U postgres -d credits_db <<-EOSQL + SELECT timescaledb_pre_restore(); +EOSQL +psql -U postgres --set ON_ERROR_STOP=on -d credits_db -f /docker-entrypoint-initdb.d/credits_db.dump +psql -v ON_ERROR_STOP=1 -U postgres -d credits_db <<-EOSQL + SELECT timescaledb_post_restore(); +EOSQL +fi diff --git a/docker-compose.yml b/docker-compose.yml index 5275034..9546ef2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,38 +2,40 @@ version: "3.5" services: credits: + build: + dockerfile: Dockerfile.dev image: os_credits-dev:latest + container_name: dev_os_credits + networks: + - credits env_file: - .env - environment: - - MAIL_SMTP_SERVER=smtp_server - - MAIL_NOT_STARTTLS=1 - - INFLUXDB_HOST=influxdb - - API_KEY ports: - "8002:80" depends_on: - - influxdb - - smtp_server + - timescaledb volumes: - "./src:/code/src:ro" - influxdb: - image: "influxdb:1.8-alpine" - container_name: portal_influxdb + timescaledb: + image: "timescale/timescaledb:2.5.1-pg14" + container_name: dev_timescaledb + command: postgres -c shared_preload_libraries=timescaledb + networks: + - credits + env_file: + - .env environment: - - INFLUXDB_REPORTING_DISABLED=true - - INFLUXDB_DB=credits_history - - INFLUXDB_HTTP_AUTH_ENABLED=false - healthcheck: - test: wget http://localhost:8086/ping -qO /dev/null || return 1 - interval: 2m - timeout: 10s - retries: 3 - start_period: 5s - - smtp_server: - image: "python:3.10-alpine" - command: python3 -m smtpd -n -c DebuggingServer + - TIMESCALEDB_TELEMETRY=off expose: - - "25" + - "5432" + volumes: + - credits_data:/var/lib/postgresql/data + - ./db_init:/docker-entrypoint-initdb.d + +volumes: + credits_data: + external: false + +networks: + credits: diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 0d99662..0000000 --- a/mypy.ini +++ /dev/null @@ -1,6 +0,0 @@ -[mypy] -ignore_missing_imports = True -strict_equality = True - -[mypy-tests.*] -ignore_errors = True diff --git a/poetry.lock b/poetry.lock index 10ec0d7..84a9881 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,24 +61,6 @@ pyYAML = ">=5.1" [package.extras] performance = ["ujson"] -[[package]] -name = "aioinflux" -version = "0.9.0" -description = "Asynchronous Python client for InfluxDB" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -aiohttp = ">=3.0" -ciso8601 = "*" - -[package.extras] -cache = ["aioredis (>=1.2.0)", "lz4 (>=2.1.0)"] -docs = ["docutils", "sphinx", "sphinx-rtd-theme", "sphinx-autodoc-typehints"] -pandas = ["pandas (>=0.21)", "numpy"] -test = ["pytest", "pytest-asyncio", "pytest-cov", "pyyaml", "pytz", "flake8", "pep8-naming", "flake8-rst-docstrings", "pygments", "dataclasses"] - [[package]] name = "aiosignal" version = "1.2.0" @@ -111,13 +93,51 @@ optional = false python-versions = "*" [[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "alembic" +version = "1.7.7" +description = "A database migration tool for SQLAlchemy." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" + +[package.extras] +tz = ["python-dateutil"] + +[[package]] +name = "anyio" +version = "3.5.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16)"] + +[[package]] +name = "asttokens" +version = "2.0.5" +description = "Annotate AST trees with source code positions" category = "dev" optional = false python-versions = "*" +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + [[package]] name = "async-timeout" version = "4.0.2" @@ -127,30 +147,35 @@ optional = false python-versions = ">=3.6" [[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "dev" +name = "asyncpg" +version = "0.25.0" +description = "An asyncio PostgreSQL driver" +category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6.0" + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["pycodestyle (>=2.7.0,<2.8.0)", "flake8 (>=3.9.2,<3.10.0)", "uvloop (>=0.15.3)"] [[package]] name = "attrs" -version = "20.3.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "babel" -version = "2.9.0" +version = "2.9.1" description = "Internationalization utilities" category = "dev" optional = false @@ -177,29 +202,28 @@ typecheck = ["mypy"] [[package]] name = "black" -version = "20.8b1" +version = "22.3.0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.6,<1" -regex = ">=2020.1.8" -toml = ">=0.10.1" -typed-ast = ">=1.4.0" -typing-extensions = ">=3.7.4" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2020.12.5" +version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false @@ -207,7 +231,7 @@ python-versions = "*" [[package]] name = "cffi" -version = "1.14.5" +version = "1.15.0" description = "Foreign Function Interface for Python calling C code." category = "dev" optional = false @@ -218,23 +242,15 @@ pycparser = "*" [[package]] name = "cfgv" -version = "3.2.0" +version = "3.3.1" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false python-versions = ">=3.6.1" -[[package]] -name = "chardet" -version = "3.0.4" -description = "Universal encoding detector for Python 2 and 3" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "charset-normalizer" -version = "2.0.10" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -243,21 +259,16 @@ python-versions = ">=3.5.0" [package.extras] unicode_backport = ["unicodedata2"] -[[package]] -name = "ciso8601" -version = "2.1.3" -description = "Fast ISO8601 date time parser for Python written in C" -category = "main" -optional = false -python-versions = "*" - [[package]] name = "click" -version = "7.1.2" +version = "8.1.0" description = "Composable command line interface toolkit" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" @@ -267,23 +278,9 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "coverage" -version = "6.2" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - [[package]] name = "cryptography" -version = "3.4.6" +version = "36.0.2" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "dev" optional = false @@ -294,26 +291,30 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools-rust (>=0.11.4)"] +sdist = ["setuptools_rust (>=0.11.4)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] +test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] [[package]] name = "devtools" -version = "0.6.1" +version = "0.8.0" description = "Python's missing debug print command and other development tools." category = "dev" optional = false python-versions = ">=3.6" +[package.dependencies] +asttokens = ">=2.0.0,<3.0.0" +executing = ">=0.8.0,<1.0.0" + [package.extras] pygments = ["Pygments (>=2.2.0)"] [[package]] name = "distlib" -version = "0.3.1" +version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false @@ -321,11 +322,11 @@ python-versions = "*" [[package]] name = "distro" -version = "1.5.0" +version = "1.7.0" description = "Distro - an OS platform information API" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "docker" @@ -391,96 +392,85 @@ python-versions = "*" [[package]] name = "docutils" -version = "0.16" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] -name = "filelock" -version = "3.0.12" -description = "A platform independent file lock." +name = "executing" +version = "0.8.3" +description = "Get the currently executing AST node of a frame, and other information" category = "dev" optional = false python-versions = "*" [[package]] -name = "flake8" -version = "3.9.0" -description = "the modular source code checker: pep8 pyflakes and co" +name = "filelock" +version = "3.6.0" +description = "A platform independent file lock." category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.7" -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] [[package]] name = "frozenlist" -version = "1.2.0" +version = "1.3.0" description = "A list-like structure which implements collections.abc.MutableSequence" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[[package]] +name = "greenlet" +version = "1.1.2" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] [[package]] name = "identify" -version = "2.1.3" +version = "2.4.12" description = "File identification library for Python" category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" [package.extras] -license = ["editdistance"] +license = ["ukkonen"] [[package]] name = "idna" -version = "2.10" +version = "3.3" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "imagesize" -version = "1.2.0" +version = "1.3.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "5.7.0" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6,<4.0" - -[package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] - [[package]] name = "jinja2" -version = "3.0.3" +version = "3.1.1" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] MarkupSafe = ">=2.0" @@ -507,7 +497,7 @@ format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator [[package]] name = "lxml" -version = "4.7.1" +version = "4.8.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." category = "dev" optional = false @@ -520,56 +510,48 @@ htmlsoup = ["beautifulsoup4"] source = ["Cython (>=0.29.7)"] [[package]] -name = "markupsafe" -version = "2.0.1" -description = "Safely add untrusted strings to HTML/XML markup." +name = "mako" +version = "1.2.0" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["babel"] +lingua = ["lingua"] +testing = ["pytest"] [[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "multidict" -version = "5.1.0" +version = "6.0.2" description = "multidict implementation" category = "main" optional = false -python-versions = ">=3.6" - -[[package]] -name = "mypy" -version = "0.800" -description = "Optional static typing for Python" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] +python-versions = ">=3.7" [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "main" +category = "dev" optional = false python-versions = "*" [[package]] name = "nodeenv" -version = "1.5.0" +version = "1.6.0" description = "Node.js virtual environment builder" category = "dev" optional = false @@ -577,18 +559,18 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.9" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "paramiko" -version = "2.7.2" +version = "2.10.3" description = "SSH2 protocol library" category = "dev" optional = false @@ -598,6 +580,7 @@ python-versions = "*" bcrypt = ">=3.1.3" cryptography = ">=2.5" pynacl = ">=1.0.1" +six = "*" [package.extras] all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"] @@ -607,26 +590,27 @@ invoke = ["invoke (>=1.3)"] [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] -name = "pluggy" -version = "0.13.1" -description = "plugin and hook calling mechanisms for python" +name = "platformdirs" +version = "2.5.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.7" [package.extras] -dev = ["pre-commit", "tox"] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] [[package]] name = "pre-commit" -version = "2.16.0" +version = "2.17.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -641,72 +625,24 @@ toml = "*" virtualenv = ">=20.0.8" [[package]] -name = "prometheus-async" -version = "19.2.0" -description = "Async helpers for prometheus_client." +name = "psycopg2" +version = "2.9.3" +description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" optional = false -python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4" - -[package.dependencies] -aiohttp = {version = ">=3", optional = true, markers = "extra == \"aiohttp\""} -prometheus-client = ">=0.0.18" -wrapt = "*" - -[package.extras] -aiohttp = ["aiohttp (>=3)"] -consul = ["aiohttp (>=3)"] -dev = ["aiohttp (>=3)", "twisted", "aiohttp", "sphinx", "sphinxcontrib-asyncio", "coverage", "pytest (<4.1)", "pytest-twisted", "pre-commit", "pytest-asyncio"] -docs = ["aiohttp", "sphinx", "sphinxcontrib-asyncio", "twisted"] -tests = ["coverage", "pytest (<4.1)", "pytest-asyncio"] -twisted = ["twisted"] - -[[package]] -name = "prometheus-client" -version = "0.9.0" -description = "Python client for the Prometheus monitoring system." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -twisted = ["twisted"] - -[[package]] -name = "py" -version = "1.10.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pycodestyle" -version = "2.7.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [[package]] name = "pycparser" -version = "2.20" +version = "2.21" description = "C parser in Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -[[package]] -name = "pyflakes" -version = "2.3.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "pygments" -version = "2.8.1" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false @@ -714,15 +650,14 @@ python-versions = ">=3.5" [[package]] name = "pynacl" -version = "1.4.0" +version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] cffi = ">=1.4.1" -six = "*" [package.extras] docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] @@ -730,158 +665,37 @@ tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"] [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.7" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "pyrsistent" -version = "0.17.3" -description = "Persistent/Functional/Immutable data structures" -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "pytest" -version = "6.2.5" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false python-versions = ">=3.6" -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" - [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] -name = "pytest-aiohttp" -version = "0.3.0" -description = "pytest plugin for aiohttp support" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -aiohttp = ">=2.3.5" -pytest = "*" - -[[package]] -name = "pytest-black" -version = "0.3.12" -description = "A pytest plugin to enable format checking with black" -category = "dev" -optional = false -python-versions = ">=2.7" - -[package.dependencies] -black = {version = "*", markers = "python_version >= \"3.6\""} -pytest = ">=3.5.0" -toml = "*" - -[[package]] -name = "pytest-cov" -version = "3.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pytest-dotenv" -version = "0.5.2" -description = "A py.test plugin that parses environment files before running tests" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -pytest = ">=5.0.0" -python-dotenv = ">=0.9.1" - -[[package]] -name = "pytest-flake8" -version = "1.0.7" -description = "pytest plugin to check FLAKE8 requirements" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = ">=3.5" -pytest = ">=3.5" - -[[package]] -name = "pytest-isort" -version = "2.0.0" -description = "py.test plugin to check import ordering using isort" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -isort = ">=4.0" - -[package.extras] -tests = ["mock"] - -[[package]] -name = "pytest-localserver" -version = "0.5.1.post0" -description = "py.test plugin to test server connections locally." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[package.dependencies] -werkzeug = ">=0.10" - -[[package]] -name = "pytest-mypy" -version = "0.8.1" -description = "Mypy static type checker plugin for Pytest" +name = "pyrsistent" +version = "0.18.1" +description = "Persistent/Functional/Immutable data structures" category = "dev" optional = false -python-versions = ">=3.5" - -[package.dependencies] -attrs = ">=19.0" -filelock = ">=3.0" -mypy = {version = ">=0.780", markers = "python_version >= \"3.9\""} -pytest = ">=3.5" +python-versions = ">=3.7" [[package]] name = "python-dotenv" -version = "0.15.0" -description = "Add .env support to your django/flask apps in development and deployments" +version = "0.20.0" +description = "Read key-value pairs from a .env file and set them as environment variables" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" [package.extras] cli = ["click (>=5.0)"] [[package]] name = "pytz" -version = "2021.1" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false @@ -903,43 +717,43 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -[[package]] -name = "regex" -version = "2021.3.17" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "requests" -version = "2.25.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<5" -idna = ">=2.5,<3" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sniffio" +version = "1.2.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.5" + [[package]] name = "snowballstemmer" -version = "2.1.0" +version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "dev" optional = false @@ -947,17 +761,17 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.5.4" +version = "4.5.0" description = "Python documentation generator" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12,<0.17" +docutils = ">=0.14,<0.18" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -966,29 +780,29 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.11.1" +version = "1.17.0" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" category = "dev" optional = false -python-versions = ">=3.5.2" +python-versions = ">=3.7" [package.dependencies] -Sphinx = ">=3.0" +Sphinx = ">=4" [package.extras] -test = ["pytest (>=3.1.0)", "typing-extensions (>=3.5)", "sphobjinv (>=2.0)", "Sphinx (>=3.2.0)", "dataclasses"] +testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "nptyping (>=1)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] type_comments = ["typed-ast (>=1.4.0)"] [[package]] @@ -1017,11 +831,11 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "1.0.3" +version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.extras] lint = ["flake8", "mypy", "docutils-stubs"] @@ -1063,7 +877,7 @@ test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.4" +version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." category = "dev" optional = false @@ -1084,9 +898,41 @@ python-versions = "*" [package.dependencies] sphinx = ">=1.7" +[[package]] +name = "sqlalchemy" +version = "1.4.31" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3)"] +mariadb_connector = ["mariadb (>=1.0.1)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + [[package]] name = "texttable" -version = "1.6.3" +version = "1.6.4" description = "module for creating simple ASCII tables" category = "dev" optional = false @@ -1102,70 +948,57 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "2.0.0" +version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" -[[package]] -name = "typed-ast" -version = "1.4.2" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "urllib3" -version = "1.26.4" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [[package]] name = "virtualenv" -version = "20.4.3" +version = "20.14.0" description = "Virtual Python Environment builder" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -appdirs = ">=1.4.3,<2" distlib = ">=0.3.1,<1" -filelock = ">=3.0.0,<4" +filelock = ">=3.2,<4" +platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "watchgod" -version = "0.7" +version = "0.8.1" description = "Simple, modern file watching and code reload in python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0.0,<4" [[package]] name = "websocket-client" -version = "0.58.0" +version = "0.59.0" description = "WebSocket client for Python with low level API options" category = "dev" optional = false @@ -1174,29 +1007,9 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] six = "*" -[[package]] -name = "werkzeug" -version = "1.0.1" -description = "The comprehensive WSGI web application library." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] -watchdog = ["watchdog"] - -[[package]] -name = "wrapt" -version = "1.12.1" -description = "Module for decorators, wrappers and monkey patching." -category = "main" -optional = false -python-versions = "*" - [[package]] name = "yarl" -version = "1.6.3" +version = "1.7.2" description = "Yet another URL library" category = "main" optional = false @@ -1208,8 +1021,8 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" -python-versions = "^3.9" -content-hash = "eeb4b364d0c958ad989439b164612e2588a69f34a935f3e689fd7b563d777ea7" +python-versions = "3.10.1" +content-hash = "5ab83e1bbce7fc6d52dec47126839de8c4b20b198b131a13917b47cf815b7ed6" [metadata.files] aiohttp = [ @@ -1298,10 +1111,6 @@ aiohttp-swagger = [ {file = "aiohttp-swagger-1.0.16.tar.gz", hash = "sha256:48e5d9e9a9ece13afd67ab37209c01fbd2b691694559b08141e4a89f8a28b126"}, {file = "aiohttp_swagger-1.0.16-py3-none-any.whl", hash = "sha256:96ada287da3fb4ed47c0a9853d565ba500a17e4230bcaafda0e8bb66974787e9"}, ] -aioinflux = [ - {file = "aioinflux-0.9.0-py3-none-any.whl", hash = "sha256:f812d2814492164282c28cbe867d32cce3e8d256e116c6cf4675e896fe804953"}, - {file = "aioinflux-0.9.0.tar.gz", hash = "sha256:720d056a9069ac3688f83b35786b234c890afb7c9a379eb421e5379e1eabc5cb"}, -] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, @@ -1314,25 +1123,57 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +alembic = [ + {file = "alembic-1.7.7-py3-none-any.whl", hash = "sha256:29be0856ec7591c39f4e1cb10f198045d890e6e2274cf8da80cb5e721a09642b"}, + {file = "alembic-1.7.7.tar.gz", hash = "sha256:4961248173ead7ce8a21efb3de378f13b8398e6630fab0eb258dc74a8af24c58"}, +] +anyio = [ + {file = "anyio-3.5.0-py3-none-any.whl", hash = "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e"}, + {file = "anyio-3.5.0.tar.gz", hash = "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6"}, +] +asttokens = [ + {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, + {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, ] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +asyncpg = [ + {file = "asyncpg-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf5e3408a14a17d480f36ebaf0401a12ff6ae5457fdf45e4e2775c51cc9517d3"}, + {file = "asyncpg-0.25.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bc197fc4aca2fd24f60241057998124012469d2e414aed3f992579db0c88e3a"}, + {file = "asyncpg-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a70783f6ffa34cc7dd2de20a873181414a34fd35a4a208a1f1a7f9f695e4ec4"}, + {file = "asyncpg-0.25.0-cp310-cp310-win32.whl", hash = "sha256:43cde84e996a3afe75f325a68300093425c2f47d340c0fc8912765cf24a1c095"}, + {file = "asyncpg-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:56d88d7ef4341412cd9c68efba323a4519c916979ba91b95d4c08799d2ff0c09"}, + {file = "asyncpg-0.25.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a84d30e6f850bac0876990bcd207362778e2208df0bee8be8da9f1558255e634"}, + {file = "asyncpg-0.25.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:beaecc52ad39614f6ca2e48c3ca15d56e24a2c15cbfdcb764a4320cc45f02fd5"}, + {file = "asyncpg-0.25.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6f8f5fc975246eda83da8031a14004b9197f510c41511018e7b1bedde6968e92"}, + {file = "asyncpg-0.25.0-cp36-cp36m-win32.whl", hash = "sha256:ddb4c3263a8d63dcde3d2c4ac1c25206bfeb31fa83bd70fd539e10f87739dee4"}, + {file = "asyncpg-0.25.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bf6dc9b55b9113f39eaa2057337ce3f9ef7de99a053b8a16360395ce588925cd"}, + {file = "asyncpg-0.25.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acb311722352152936e58a8ee3c5b8e791b24e84cd7d777c414ff05b3530ca68"}, + {file = "asyncpg-0.25.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a61fb196ce4dae2f2fa26eb20a778db21bbee484d2e798cb3cc988de13bdd1b"}, + {file = "asyncpg-0.25.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2633331cbc8429030b4f20f712f8d0fbba57fa8555ee9b2f45f981b81328b256"}, + {file = "asyncpg-0.25.0-cp37-cp37m-win32.whl", hash = "sha256:863d36eba4a7caa853fd7d83fad5fd5306f050cc2fe6e54fbe10cdb30420e5e9"}, + {file = "asyncpg-0.25.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fe471ccd915b739ca65e2e4dbd92a11b44a5b37f2e38f70827a1c147dafe0fa8"}, + {file = "asyncpg-0.25.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:72a1e12ea0cf7c1e02794b697e3ca967b2360eaa2ce5d4bfdd8604ec2d6b774b"}, + {file = "asyncpg-0.25.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4327f691b1bdb222df27841938b3e04c14068166b3a97491bec2cb982f49f03e"}, + {file = "asyncpg-0.25.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:739bbd7f89a2b2f6bc44cb8bf967dab12c5bc714fcbe96e68d512be45ecdf962"}, + {file = "asyncpg-0.25.0-cp38-cp38-win32.whl", hash = "sha256:18d49e2d93a7139a2fdbd113e320cc47075049997268a61bfbe0dde680c55471"}, + {file = "asyncpg-0.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:191fe6341385b7fdea7dbdcf47fd6db3fd198827dcc1f2b228476d13c05a03c6"}, + {file = "asyncpg-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fab7f1b2c29e187dd8781fce896249500cf055b63471ad66332e537e9b5f7e"}, + {file = "asyncpg-0.25.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a738f1b2876f30d710d3dc1e7858160a0afe1603ba16bf5f391f5316eb0ed855"}, + {file = "asyncpg-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4105f57ad1e8fbc8b1e535d8fcefa6ce6c71081228f08680c6dea24384ff0e"}, + {file = "asyncpg-0.25.0-cp39-cp39-win32.whl", hash = "sha256:f55918ded7b85723a5eaeb34e86e7b9280d4474be67df853ab5a7fa0cc7c6bf2"}, + {file = "asyncpg-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:649e2966d98cc48d0646d9a4e29abecd8b59d38d55c256d5c857f6b27b7407ac"}, + {file = "asyncpg-0.25.0.tar.gz", hash = "sha256:63f8e6a69733b285497c2855464a34de657f2cccd25aeaeeb5071872e9382540"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] babel = [ - {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, - {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] bcrypt = [ {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"}, @@ -1347,160 +1188,135 @@ bcrypt = [ {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] black = [ - {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, ] certifi = [ - {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, - {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, ] cffi = [ - {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, - {file = "cffi-1.14.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa"}, - {file = "cffi-1.14.5-cp27-cp27m-win32.whl", hash = "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3"}, - {file = "cffi-1.14.5-cp27-cp27m-win_amd64.whl", hash = "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482"}, - {file = "cffi-1.14.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6"}, - {file = "cffi-1.14.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa"}, - {file = "cffi-1.14.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406"}, - {file = "cffi-1.14.5-cp35-cp35m-win32.whl", hash = "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369"}, - {file = "cffi-1.14.5-cp35-cp35m-win_amd64.whl", hash = "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315"}, - {file = "cffi-1.14.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed"}, - {file = "cffi-1.14.5-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55"}, - {file = "cffi-1.14.5-cp36-cp36m-win32.whl", hash = "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53"}, - {file = "cffi-1.14.5-cp36-cp36m-win_amd64.whl", hash = "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813"}, - {file = "cffi-1.14.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05"}, - {file = "cffi-1.14.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc"}, - {file = "cffi-1.14.5-cp37-cp37m-win32.whl", hash = "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62"}, - {file = "cffi-1.14.5-cp37-cp37m-win_amd64.whl", hash = "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4"}, - {file = "cffi-1.14.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f"}, - {file = "cffi-1.14.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76"}, - {file = "cffi-1.14.5-cp38-cp38-win32.whl", hash = "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e"}, - {file = "cffi-1.14.5-cp38-cp38-win_amd64.whl", hash = "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396"}, - {file = "cffi-1.14.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333"}, - {file = "cffi-1.14.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7"}, - {file = "cffi-1.14.5-cp39-cp39-win32.whl", hash = "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396"}, - {file = "cffi-1.14.5-cp39-cp39-win_amd64.whl", hash = "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d"}, - {file = "cffi-1.14.5.tar.gz", hash = "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"}, + {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, + {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, + {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, + {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, + {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, + {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, + {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, + {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, + {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, + {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, + {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, + {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, + {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, + {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, + {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, + {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, + {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, + {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, + {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, + {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, + {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, + {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, + {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, + {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, + {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, + {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, ] cfgv = [ - {file = "cfgv-3.2.0-py2.py3-none-any.whl", hash = "sha256:32e43d604bbe7896fe7c248a9c2276447dbef840feb28fe20494f62af110211d"}, - {file = "cfgv-3.2.0.tar.gz", hash = "sha256:cf22deb93d4bcf92f345a5c3cd39d3d41d6340adc60c78bbbd6588c384fda6a1"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.10.tar.gz", hash = "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"}, - {file = "charset_normalizer-2.0.10-py3-none-any.whl", hash = "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"}, -] -ciso8601 = [ - {file = "ciso8601-2.1.3.tar.gz", hash = "sha256:bdbb5b366058b1c87735603b23060962c439ac9be66f1ae91e8c7dbd7d59e262"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, - {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, + {file = "click-8.1.0-py3-none-any.whl", hash = "sha256:19a4baa64da924c5e0cd889aba8e947f280309f1a2ce0947a3e3a7bcb7cc72d6"}, + {file = "click-8.1.0.tar.gz", hash = "sha256:977c213473c7665d3aa092b41ff12063227751c41d7b17165013e10069cc5cd2"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] -coverage = [ - {file = "coverage-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da"}, - {file = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971"}, - {file = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"}, - {file = "coverage-6.2-cp310-cp310-win32.whl", hash = "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c"}, - {file = "coverage-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f"}, - {file = "coverage-6.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76"}, - {file = "coverage-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47"}, - {file = "coverage-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64"}, - {file = "coverage-6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48"}, - {file = "coverage-6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17"}, - {file = "coverage-6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781"}, - {file = "coverage-6.2-cp36-cp36m-win32.whl", hash = "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a"}, - {file = "coverage-6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0"}, - {file = "coverage-6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884"}, - {file = "coverage-6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617"}, - {file = "coverage-6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8"}, - {file = "coverage-6.2-cp37-cp37m-win32.whl", hash = "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4"}, - {file = "coverage-6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74"}, - {file = "coverage-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc"}, - {file = "coverage-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475"}, - {file = "coverage-6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57"}, - {file = "coverage-6.2-cp38-cp38-win32.whl", hash = "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c"}, - {file = "coverage-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2"}, - {file = "coverage-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c"}, - {file = "coverage-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3"}, - {file = "coverage-6.2-cp39-cp39-win32.whl", hash = "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282"}, - {file = "coverage-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644"}, - {file = "coverage-6.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de"}, - {file = "coverage-6.2.tar.gz", hash = "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8"}, -] cryptography = [ - {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, - {file = "cryptography-3.4.6-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b"}, - {file = "cryptography-3.4.6-cp36-abi3-manylinux2014_x86_64.whl", hash = "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"}, - {file = "cryptography-3.4.6-cp36-abi3-win32.whl", hash = "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2"}, - {file = "cryptography-3.4.6-cp36-abi3-win_amd64.whl", hash = "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b"}, - {file = "cryptography-3.4.6-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336"}, - {file = "cryptography-3.4.6-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724"}, - {file = "cryptography-3.4.6.tar.gz", hash = "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"}, + {file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf"}, + {file = "cryptography-36.0.2-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2"}, + {file = "cryptography-36.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb"}, + {file = "cryptography-36.0.2-cp36-abi3-win32.whl", hash = "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6"}, + {file = "cryptography-36.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e"}, + {file = "cryptography-36.0.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51"}, + {file = "cryptography-36.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3"}, + {file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"}, ] devtools = [ - {file = "devtools-0.6.1-py3-none-any.whl", hash = "sha256:7334183972a8d04e81d08b7f62126abca9b6f4de51d825c5fdcb9c88f252601a"}, - {file = "devtools-0.6.1.tar.gz", hash = "sha256:a054307594d35d28fae8df7629967363e851ae0ac7b2152640a8a401c39d42d7"}, + {file = "devtools-0.8.0-py3-none-any.whl", hash = "sha256:00717ef184223cf36c65bbd17c6eb412f8a7564f47957f9e8b2b7610661b17fb"}, + {file = "devtools-0.8.0.tar.gz", hash = "sha256:6162a2f61c70242479dff3163e7837e6a9bf32451661af1347bfa3115602af16"}, ] distlib = [ - {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"}, - {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"}, + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] distro = [ - {file = "distro-1.5.0-py2.py3-none-any.whl", hash = "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799"}, - {file = "distro-1.5.0.tar.gz", hash = "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92"}, + {file = "distro-1.7.0-py3-none-any.whl", hash = "sha256:d596311d707e692c2160c37807f83e3820c5d539d5a83e87cfb6babd8ba3a06b"}, + {file = "distro-1.7.0.tar.gz", hash = "sha256:151aeccf60c216402932b52e40ee477a939f8d58898927378a02abbe852c1c39"}, ] docker = [ {file = "docker-5.0.3-py2.py3-none-any.whl", hash = "sha256:7a79bb439e3df59d0a72621775d600bc8bc8b422d285824cb37103eab91d1ce0"}, @@ -1517,444 +1333,420 @@ docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] docutils = [ - {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, - {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -filelock = [ - {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, - {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +executing = [ + {file = "executing-0.8.3-py2.py3-none-any.whl", hash = "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9"}, + {file = "executing-0.8.3.tar.gz", hash = "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501"}, ] -flake8 = [ - {file = "flake8-3.9.0-py2.py3-none-any.whl", hash = "sha256:12d05ab02614b6aee8df7c36b97d1a3b2372761222b19b58621355e82acddcff"}, - {file = "flake8-3.9.0.tar.gz", hash = "sha256:78873e372b12b093da7b5e5ed302e8ad9e988b38b063b61ad937f26ca58fc5f0"}, +filelock = [ + {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, + {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, ] frozenlist = [ - {file = "frozenlist-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:977a1438d0e0d96573fd679d291a1542097ea9f4918a8b6494b06610dfeefbf9"}, - {file = "frozenlist-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8d86547a5e98d9edd47c432f7a14b0c5592624b496ae9880fb6332f34af1edc"}, - {file = "frozenlist-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:181754275d5d32487431a0a29add4f897968b7157204bc1eaaf0a0ce80c5ba7d"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5df31bb2b974f379d230a25943d9bf0d3bc666b4b0807394b131a28fca2b0e5f"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4766632cd8a68e4f10f156a12c9acd7b1609941525569dd3636d859d79279ed3"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16eef427c51cb1203a7c0ab59d1b8abccaba9a4f58c4bfca6ed278fc896dc193"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:01d79515ed5aa3d699b05f6bdcf1fe9087d61d6b53882aa599a10853f0479c6c"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:28e164722ea0df0cf6d48c4d5bdf3d19e87aaa6dfb39b0ba91153f224b912020"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e63ad0beef6ece06475d29f47d1f2f29727805376e09850ebf64f90777962792"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41de4db9b9501679cf7cddc16d07ac0f10ef7eb58c525a1c8cbff43022bddca4"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a9d84ee6427b65a81fc24e6ef589cb794009f5ca4150151251c062773e7ed2"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5f3b2942c3b8b9bfe76b408bbaba3d3bb305ee3693e8b1d631fe0a0d4f93673"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c98d3c04701773ad60d9545cd96df94d955329efc7743fdb96422c4b669c633b"}, - {file = "frozenlist-1.2.0-cp310-cp310-win32.whl", hash = "sha256:72cfbeab7a920ea9e74b19aa0afe3b4ad9c89471e3badc985d08756efa9b813b"}, - {file = "frozenlist-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:11ff401951b5ac8c0701a804f503d72c048173208490c54ebb8d7bb7c07a6d00"}, - {file = "frozenlist-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b46f997d5ed6d222a863b02cdc9c299101ee27974d9bbb2fd1b3c8441311c408"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351686ca020d1bcd238596b1fa5c8efcbc21bffda9d0efe237aaa60348421e2a"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfbaa08cf1452acad9cb1c1d7b89394a41e712f88df522cea1a0f296b57782a0"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ae2f5e9fa10805fb1c9adbfefaaecedd9e31849434be462c3960a0139ed729"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6790b8d96bbb74b7a6f4594b6f131bd23056c25f2aa5d816bd177d95245a30e3"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41f62468af1bd4e4b42b5508a3fe8cc46a693f0cdd0ca2f443f51f207893d837"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:ec6cf345771cdb00791d271af9a0a6fbfc2b6dd44cb753f1eeaa256e21622adb"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:14a5cef795ae3e28fb504b73e797c1800e9249f950e1c964bb6bdc8d77871161"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8b54cdd2fda15467b9b0bfa78cee2ddf6dbb4585ef23a16e14926f4b076dfae4"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f025f1d6825725b09c0038775acab9ae94264453a696cc797ce20c0769a7b367"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:84e97f59211b5b9083a2e7a45abf91cfb441369e8bb6d1f5287382c1c526def3"}, - {file = "frozenlist-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:c5328ed53fdb0a73c8a50105306a3bc013e5ca36cca714ec4f7bd31d38d8a97f"}, - {file = "frozenlist-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9ade70aea559ca98f4b1b1e5650c45678052e76a8ab2f76d90f2ac64180215a2"}, - {file = "frozenlist-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0d3ffa8772464441b52489b985d46001e2853a3b082c655ec5fad9fb6a3d618"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3457f8cf86deb6ce1ba67e120f1b0128fcba1332a180722756597253c465fc1d"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a72eecf37eface331636951249d878750db84034927c997d47f7f78a573b72b"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:acc4614e8d1feb9f46dd829a8e771b8f5c4b1051365d02efb27a3229048ade8a"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:87521e32e18a2223311afc2492ef2d99946337da0779ddcda77b82ee7319df59"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b4c7665a17c3a5430edb663e4ad4e1ad457614d1b2f2b7f87052e2ef4fa45ca"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ed58803563a8c87cf4c0771366cf0ad1aa265b6b0ae54cbbb53013480c7ad74d"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa44c4740b4e23fcfa259e9dd52315d2b1770064cde9507457e4c4a65a04c397"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:2de5b931701257d50771a032bba4e448ff958076380b049fd36ed8738fdb375b"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6e105013fa84623c057a4381dc8ea0361f4d682c11f3816cc80f49a1f3bc17c6"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:705c184b77565955a99dc360f359e8249580c6b7eaa4dc0227caa861ef46b27a"}, - {file = "frozenlist-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:a37594ad6356e50073fe4f60aa4187b97d15329f2138124d252a5a19c8553ea4"}, - {file = "frozenlist-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:25b358aaa7dba5891b05968dd539f5856d69f522b6de0bf34e61f133e077c1a4"}, - {file = "frozenlist-1.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af2a51c8a381d76eabb76f228f565ed4c3701441ecec101dd18be70ebd483cfd"}, - {file = "frozenlist-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82d22f6e6f2916e837c91c860140ef9947e31194c82aaeda843d6551cec92f19"}, - {file = "frozenlist-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cfe6fef507f8bac40f009c85c7eddfed88c1c0d38c75e72fe10476cef94e10f"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f602e380a5132880fa245c92030abb0fc6ff34e0c5500600366cedc6adb06a"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ad065b2ebd09f32511ff2be35c5dfafee6192978b5a1e9d279a5c6e121e3b03"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc93f5f62df3bdc1f677066327fc81f92b83644852a31c6aa9b32c2dde86ea7d"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:89fdfc84c6bf0bff2ff3170bb34ecba8a6911b260d318d377171429c4be18c73"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:47b2848e464883d0bbdcd9493c67443e5e695a84694efff0476f9059b4cb6257"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f52d0732e56906f8ddea4bd856192984650282424049c956857fed43697ea43"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:16ef7dd5b7d17495404a2e7a49bac1bc13d6d20c16d11f4133c757dd94c4144c"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1cf63243bc5f5c19762943b0aa9e0d3fb3723d0c514d820a18a9b9a5ef864315"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:54a1e09ab7a69f843cd28fefd2bcaf23edb9e3a8d7680032c8968b8ac934587d"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:954b154a4533ef28bd3e83ffdf4eadf39deeda9e38fb8feaf066d6069885e034"}, - {file = "frozenlist-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cb3957c39668d10e2b486acc85f94153520a23263b6401e8f59422ef65b9520d"}, - {file = "frozenlist-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0a7c7cce70e41bc13d7d50f0e5dd175f14a4f1837a8549b0936ed0cbe6170bf9"}, - {file = "frozenlist-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4c457220468d734e3077580a3642b7f682f5fd9507f17ddf1029452450912cdc"}, - {file = "frozenlist-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e74f8b4d8677ebb4015ac01fcaf05f34e8a1f22775db1f304f497f2f88fdc697"}, - {file = "frozenlist-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fbd4844ff111449f3bbe20ba24fbb906b5b1c2384d0f3287c9f7da2354ce6d23"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0081a623c886197ff8de9e635528fd7e6a387dccef432149e25c13946cb0cd0"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b6e21e5770df2dea06cb7b6323fbc008b13c4a4e3b52cb54685276479ee7676"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:406aeb340613b4b559db78d86864485f68919b7141dec82aba24d1477fd2976f"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:878ebe074839d649a1cdb03a61077d05760624f36d196884a5cafb12290e187b"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1fef737fd1388f9b93bba8808c5f63058113c10f4e3c0763ced68431773f72f9"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a495c3d513573b0b3f935bfa887a85d9ae09f0627cf47cad17d0cc9b9ba5c38"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e7d0dd3e727c70c2680f5f09a0775525229809f1a35d8552b92ff10b2b14f2c2"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:66a518731a21a55b7d3e087b430f1956a36793acc15912e2878431c7aec54210"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:94728f97ddf603d23c8c3dd5cae2644fa12d33116e69f49b1644a71bb77b89ae"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c1e8e9033d34c2c9e186e58279879d78c94dd365068a3607af33f2bc99357a53"}, - {file = "frozenlist-1.2.0-cp39-cp39-win32.whl", hash = "sha256:83334e84a290a158c0c4cc4d22e8c7cfe0bba5b76d37f1c2509dabd22acafe15"}, - {file = "frozenlist-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:735f386ec522e384f511614c01d2ef9cf799f051353876b4c6fb93ef67a6d1ee"}, - {file = "frozenlist-1.2.0.tar.gz", hash = "sha256:68201be60ac56aff972dc18085800b6ee07973c49103a8aba669dee3d71079de"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"}, + {file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"}, + {file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"}, + {file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"}, + {file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"}, + {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, + {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, +] +greenlet = [ + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, ] identify = [ - {file = "identify-2.1.3-py2.py3-none-any.whl", hash = "sha256:46d1816c6a4fc2d1e8758f293a5dcc1ae6404ab344179d7c1e73637bf283beb1"}, - {file = "identify-2.1.3.tar.gz", hash = "sha256:ed4a05fb80e3cbd12e83c959f9ff7f729ba6b66ab8d6178850fd5cb4c1cf6c5d"}, + {file = "identify-2.4.12-py2.py3-none-any.whl", hash = "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323"}, + {file = "identify-2.4.12.tar.gz", hash = "sha256:3f3244a559290e7d3deb9e9adc7b33594c1bc85a9dd82e0f1be519bf12a1ec17"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ - {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, - {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isort = [ - {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, - {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, + {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, + {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, + {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, + {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, ] jsonschema = [ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, ] lxml = [ - {file = "lxml-4.7.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:d546431636edb1d6a608b348dd58cc9841b81f4116745857b6cb9f8dadb2725f"}, - {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6308062534323f0d3edb4e702a0e26a76ca9e0e23ff99be5d82750772df32a9e"}, - {file = "lxml-4.7.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f76dbe44e31abf516114f6347a46fa4e7c2e8bceaa4b6f7ee3a0a03c8eba3c17"}, - {file = "lxml-4.7.1-cp27-cp27m-win32.whl", hash = "sha256:d5618d49de6ba63fe4510bdada62d06a8acfca0b4b5c904956c777d28382b419"}, - {file = "lxml-4.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9393a05b126a7e187f3e38758255e0edf948a65b22c377414002d488221fdaa2"}, - {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:50d3dba341f1e583265c1a808e897b4159208d814ab07530202b6036a4d86da5"}, - {file = "lxml-4.7.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:44f552e0da3c8ee3c28e2eb82b0b784200631687fc6a71277ea8ab0828780e7d"}, - {file = "lxml-4.7.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:e662c6266e3a275bdcb6bb049edc7cd77d0b0f7e119a53101d367c841afc66dc"}, - {file = "lxml-4.7.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4c093c571bc3da9ebcd484e001ba18b8452903cd428c0bc926d9b0141bcb710e"}, - {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3e26ad9bc48d610bf6cc76c506b9e5ad9360ed7a945d9be3b5b2c8535a0145e3"}, - {file = "lxml-4.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a5f623aeaa24f71fce3177d7fee875371345eb9102b355b882243e33e04b7175"}, - {file = "lxml-4.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7b5e2acefd33c259c4a2e157119c4373c8773cf6793e225006a1649672ab47a6"}, - {file = "lxml-4.7.1-cp310-cp310-win32.whl", hash = "sha256:67fa5f028e8a01e1d7944a9fb616d1d0510d5d38b0c41708310bd1bc45ae89f6"}, - {file = "lxml-4.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:b1d381f58fcc3e63fcc0ea4f0a38335163883267f77e4c6e22d7a30877218a0e"}, - {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38d9759733aa04fb1697d717bfabbedb21398046bd07734be7cccc3d19ea8675"}, - {file = "lxml-4.7.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dfd0d464f3d86a1460683cd742306d1138b4e99b79094f4e07e1ca85ee267fe7"}, - {file = "lxml-4.7.1-cp35-cp35m-win32.whl", hash = "sha256:534e946bce61fd162af02bad7bfd2daec1521b71d27238869c23a672146c34a5"}, - {file = "lxml-4.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:6ec829058785d028f467be70cd195cd0aaf1a763e4d09822584ede8c9eaa4b03"}, - {file = "lxml-4.7.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:ade74f5e3a0fd17df5782896ddca7ddb998845a5f7cd4b0be771e1ffc3b9aa5b"}, - {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41358bfd24425c1673f184d7c26c6ae91943fe51dfecc3603b5e08187b4bcc55"}, - {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6e56521538f19c4a6690f439fefed551f0b296bd785adc67c1777c348beb943d"}, - {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5b0f782f0e03555c55e37d93d7a57454efe7495dab33ba0ccd2dbe25fc50f05d"}, - {file = "lxml-4.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:490712b91c65988012e866c411a40cc65b595929ececf75eeb4c79fcc3bc80a6"}, - {file = "lxml-4.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c22eb8c819d59cec4444d9eebe2e38b95d3dcdafe08965853f8799fd71161d"}, - {file = "lxml-4.7.1-cp36-cp36m-win32.whl", hash = "sha256:2a906c3890da6a63224d551c2967413b8790a6357a80bf6b257c9a7978c2c42d"}, - {file = "lxml-4.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:36b16fecb10246e599f178dd74f313cbdc9f41c56e77d52100d1361eed24f51a"}, - {file = "lxml-4.7.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a5edc58d631170de90e50adc2cc0248083541affef82f8cd93bea458e4d96db8"}, - {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:87c1b0496e8c87ec9db5383e30042357b4839b46c2d556abd49ec770ce2ad868"}, - {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:0a5f0e4747f31cff87d1eb32a6000bde1e603107f632ef4666be0dc065889c7a"}, - {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bf6005708fc2e2c89a083f258b97709559a95f9a7a03e59f805dd23c93bc3986"}, - {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc15874816b9320581133ddc2096b644582ab870cf6a6ed63684433e7af4b0d3"}, - {file = "lxml-4.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0b5e96e25e70917b28a5391c2ed3ffc6156513d3db0e1476c5253fcd50f7a944"}, - {file = "lxml-4.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ec9027d0beb785a35aa9951d14e06d48cfbf876d8ff67519403a2522b181943b"}, - {file = "lxml-4.7.1-cp37-cp37m-win32.whl", hash = "sha256:9fbc0dee7ff5f15c4428775e6fa3ed20003140560ffa22b88326669d53b3c0f4"}, - {file = "lxml-4.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1104a8d47967a414a436007c52f533e933e5d52574cab407b1e49a4e9b5ddbd1"}, - {file = "lxml-4.7.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fc9fb11b65e7bc49f7f75aaba1b700f7181d95d4e151cf2f24d51bfd14410b77"}, - {file = "lxml-4.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:317bd63870b4d875af3c1be1b19202de34c32623609ec803b81c99193a788c1e"}, - {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:610807cea990fd545b1559466971649e69302c8a9472cefe1d6d48a1dee97440"}, - {file = "lxml-4.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:09b738360af8cb2da275998a8bf79517a71225b0de41ab47339c2beebfff025f"}, - {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a2ab9d089324d77bb81745b01f4aeffe4094306d939e92ba5e71e9a6b99b71e"}, - {file = "lxml-4.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eed394099a7792834f0cb4a8f615319152b9d801444c1c9e1b1a2c36d2239f9e"}, - {file = "lxml-4.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:735e3b4ce9c0616e85f302f109bdc6e425ba1670a73f962c9f6b98a6d51b77c9"}, - {file = "lxml-4.7.1-cp38-cp38-win32.whl", hash = "sha256:772057fba283c095db8c8ecde4634717a35c47061d24f889468dc67190327bcd"}, - {file = "lxml-4.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:13dbb5c7e8f3b6a2cf6e10b0948cacb2f4c9eb05029fe31c60592d08ac63180d"}, - {file = "lxml-4.7.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:718d7208b9c2d86aaf0294d9381a6acb0158b5ff0f3515902751404e318e02c9"}, - {file = "lxml-4.7.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:5bee1b0cbfdb87686a7fb0e46f1d8bd34d52d6932c0723a86de1cc532b1aa489"}, - {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e410cf3a2272d0a85526d700782a2fa92c1e304fdcc519ba74ac80b8297adf36"}, - {file = "lxml-4.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:585ea241ee4961dc18a95e2f5581dbc26285fcf330e007459688096f76be8c42"}, - {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a555e06566c6dc167fbcd0ad507ff05fd9328502aefc963cb0a0547cfe7f00db"}, - {file = "lxml-4.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:adaab25be351fff0d8a691c4f09153647804d09a87a4e4ea2c3f9fe9e8651851"}, - {file = "lxml-4.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:82d16a64236970cb93c8d63ad18c5b9f138a704331e4b916b2737ddfad14e0c4"}, - {file = "lxml-4.7.1-cp39-cp39-win32.whl", hash = "sha256:59e7da839a1238807226f7143c68a479dee09244d1b3cf8c134f2fce777d12d0"}, - {file = "lxml-4.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:a1bbc4efa99ed1310b5009ce7f3a1784698082ed2c1ef3895332f5df9b3b92c2"}, - {file = "lxml-4.7.1-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:0607ff0988ad7e173e5ddf7bf55ee65534bd18a5461183c33e8e41a59e89edf4"}, - {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:6c198bfc169419c09b85ab10cb0f572744e686f40d1e7f4ed09061284fc1303f"}, - {file = "lxml-4.7.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a58d78653ae422df6837dd4ca0036610b8cb4962b5cfdbd337b7b24de9e5f98a"}, - {file = "lxml-4.7.1-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:e18281a7d80d76b66a9f9e68a98cf7e1d153182772400d9a9ce855264d7d0ce7"}, - {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8e54945dd2eeb50925500957c7c579df3cd07c29db7810b83cf30495d79af267"}, - {file = "lxml-4.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:447d5009d6b5447b2f237395d0018901dcc673f7d9f82ba26c1b9f9c3b444b60"}, - {file = "lxml-4.7.1.tar.gz", hash = "sha256:a1613838aa6b89af4ba10a0f3a972836128801ed008078f8c1244e65958f1b24"}, + {file = "lxml-4.8.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b"}, + {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430"}, + {file = "lxml-4.8.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a"}, + {file = "lxml-4.8.0-cp27-cp27m-win32.whl", hash = "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5"}, + {file = "lxml-4.8.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9"}, + {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc"}, + {file = "lxml-4.8.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170"}, + {file = "lxml-4.8.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9"}, + {file = "lxml-4.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03"}, + {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe"}, + {file = "lxml-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa"}, + {file = "lxml-4.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1"}, + {file = "lxml-4.8.0-cp310-cp310-win32.whl", hash = "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b"}, + {file = "lxml-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76"}, + {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6"}, + {file = "lxml-4.8.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2"}, + {file = "lxml-4.8.0-cp35-cp35m-win32.whl", hash = "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150"}, + {file = "lxml-4.8.0-cp35-cp35m-win_amd64.whl", hash = "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654"}, + {file = "lxml-4.8.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e"}, + {file = "lxml-4.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613"}, + {file = "lxml-4.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33"}, + {file = "lxml-4.8.0-cp36-cp36m-win32.whl", hash = "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429"}, + {file = "lxml-4.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63"}, + {file = "lxml-4.8.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c"}, + {file = "lxml-4.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85"}, + {file = "lxml-4.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141"}, + {file = "lxml-4.8.0-cp37-cp37m-win32.whl", hash = "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63"}, + {file = "lxml-4.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8"}, + {file = "lxml-4.8.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870"}, + {file = "lxml-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9"}, + {file = "lxml-4.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68"}, + {file = "lxml-4.8.0-cp38-cp38-win32.whl", hash = "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696"}, + {file = "lxml-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939"}, + {file = "lxml-4.8.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c"}, + {file = "lxml-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87"}, + {file = "lxml-4.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9"}, + {file = "lxml-4.8.0-cp39-cp39-win32.whl", hash = "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea"}, + {file = "lxml-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c"}, + {file = "lxml-4.8.0-pp37-pypy37_pp73-macosx_10_14_x86_64.whl", hash = "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507"}, + {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9"}, + {file = "lxml-4.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e"}, + {file = "lxml-4.8.0-pp38-pypy38_pp73-macosx_10_14_x86_64.whl", hash = "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0"}, + {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79"}, + {file = "lxml-4.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93"}, + {file = "lxml-4.8.0.tar.gz", hash = "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23"}, +] +mako = [ + {file = "Mako-1.2.0-py3-none-any.whl", hash = "sha256:23aab11fdbbb0f1051b93793a58323ff937e98e34aece1c4219675122e57e4ba"}, + {file = "Mako-1.2.0.tar.gz", hash = "sha256:9a7c7e922b87db3686210cf49d5d767033a41d4010b284e747682c92bddd8b39"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] multidict = [ - {file = "multidict-5.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da"}, - {file = "multidict-5.1.0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224"}, - {file = "multidict-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26"}, - {file = "multidict-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6"}, - {file = "multidict-5.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9"}, - {file = "multidict-5.1.0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37"}, - {file = "multidict-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5"}, - {file = "multidict-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632"}, - {file = "multidict-5.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a"}, - {file = "multidict-5.1.0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea"}, - {file = "multidict-5.1.0-cp38-cp38-win32.whl", hash = "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656"}, - {file = "multidict-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3"}, - {file = "multidict-5.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841"}, - {file = "multidict-5.1.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda"}, - {file = "multidict-5.1.0-cp39-cp39-win32.whl", hash = "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"}, - {file = "multidict-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359"}, - {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, -] -mypy = [ - {file = "mypy-0.800-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:e1c84c65ff6d69fb42958ece5b1255394714e0aac4df5ffe151bc4fe19c7600a"}, - {file = "mypy-0.800-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:947126195bfe4709c360e89b40114c6746ae248f04d379dca6f6ab677aa07641"}, - {file = "mypy-0.800-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:b95068a3ce3b50332c40e31a955653be245666a4bc7819d3c8898aa9fb9ea496"}, - {file = "mypy-0.800-cp35-cp35m-win_amd64.whl", hash = "sha256:ca7ad5aed210841f1e77f5f2f7d725b62c78fa77519312042c719ed2ab937876"}, - {file = "mypy-0.800-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e32b7b282c4ed4e378bba8b8dfa08e1cfa6f6574067ef22f86bee5b1039de0c9"}, - {file = "mypy-0.800-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e497a544391f733eca922fdcb326d19e894789cd4ff61d48b4b195776476c5cf"}, - {file = "mypy-0.800-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:5615785d3e2f4f03ab7697983d82c4b98af5c321614f51b8f1034eb9ebe48363"}, - {file = "mypy-0.800-cp36-cp36m-win_amd64.whl", hash = "sha256:2b216eacca0ec0ee124af9429bfd858d5619a0725ee5f88057e6e076f9eb1a7b"}, - {file = "mypy-0.800-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e3b8432f8df19e3c11235c4563a7250666dc9aa7cdda58d21b4177b20256ca9f"}, - {file = "mypy-0.800-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d16c54b0dffb861dc6318a8730952265876d90c5101085a4bc56913e8521ba19"}, - {file = "mypy-0.800-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d2fc8beb99cd88f2d7e20d69131353053fbecea17904ee6f0348759302c52fa"}, - {file = "mypy-0.800-cp37-cp37m-win_amd64.whl", hash = "sha256:aa9d4901f3ee1a986a3a79fe079ffbf7f999478c281376f48faa31daaa814e86"}, - {file = "mypy-0.800-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:319ee5c248a7c3f94477f92a729b7ab06bf8a6d04447ef3aa8c9ba2aa47c6dcf"}, - {file = "mypy-0.800-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:74f5aa50d0866bc6fb8e213441c41e466c86678c800700b87b012ed11c0a13e0"}, - {file = "mypy-0.800-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a301da58d566aca05f8f449403c710c50a9860782148332322decf73a603280b"}, - {file = "mypy-0.800-cp38-cp38-win_amd64.whl", hash = "sha256:b9150db14a48a8fa114189bfe49baccdff89da8c6639c2717750c7ae62316738"}, - {file = "mypy-0.800-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5fdf935a46aa20aa937f2478480ebf4be9186e98e49cc3843af9a5795a49a25"}, - {file = "mypy-0.800-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6f8425fecd2ba6007e526209bb985ce7f49ed0d2ac1cc1a44f243380a06a84fb"}, - {file = "mypy-0.800-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:5ff616787122774f510caeb7b980542a7cc2222be3f00837a304ea85cd56e488"}, - {file = "mypy-0.800-cp39-cp39-win_amd64.whl", hash = "sha256:90b6f46dc2181d74f80617deca611925d7e63007cf416397358aa42efb593e07"}, - {file = "mypy-0.800-py3-none-any.whl", hash = "sha256:3e0c159a7853e3521e3f582adb1f3eac66d0b0639d434278e2867af3a8c62653"}, - {file = "mypy-0.800.tar.gz", hash = "sha256:e0202e37756ed09daf4b0ba64ad2c245d357659e014c3f51d8cd0681ba66940a"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] nodeenv = [ - {file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"}, - {file = "nodeenv-1.5.0.tar.gz", hash = "sha256:ab45090ae383b716c4ef89e690c41ff8c2b257b85b309f01f3654df3d084bd7c"}, + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] paramiko = [ - {file = "paramiko-2.7.2-py2.py3-none-any.whl", hash = "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898"}, - {file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"}, + {file = "paramiko-2.10.3-py2.py3-none-any.whl", hash = "sha256:ac6593479f2b47a9422eca076b22cff9f795495e6733a64723efc75dd8c92101"}, + {file = "paramiko-2.10.3.tar.gz", hash = "sha256:ddb1977853aef82804b35d72a0e597b244fa326c404c350bd00c5b01dbfee71a"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +platformdirs = [ + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pre-commit = [ - {file = "pre_commit-2.16.0-py2.py3-none-any.whl", hash = "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e"}, - {file = "pre_commit-2.16.0.tar.gz", hash = "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65"}, -] -prometheus-async = [ - {file = "prometheus_async-19.2.0-py2.py3-none-any.whl", hash = "sha256:227f516e5bf98a0dc602348381e182358f8b2ed24a8db05e8e34d9cf027bab83"}, - {file = "prometheus_async-19.2.0.tar.gz", hash = "sha256:3cc68d1f39e9bbf16dbd0b51103d87671b3cbd1d75a72cda472cd9a35cc9d0d2"}, -] -prometheus-client = [ - {file = "prometheus_client-0.9.0-py2.py3-none-any.whl", hash = "sha256:b08c34c328e1bf5961f0b4352668e6c8f145b4a087e09b7296ef62cbe4693d35"}, - {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"}, -] -py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, -] -pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, + {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, +] +psycopg2 = [ + {file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"}, + {file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"}, + {file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"}, + {file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"}, + {file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"}, + {file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"}, + {file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"}, ] pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, -] -pyflakes = [ - {file = "pyflakes-2.3.0-py2.py3-none-any.whl", hash = "sha256:910208209dcea632721cb58363d0f72913d9e8cf64dc6f8ae2e02a3609aba40d"}, - {file = "pyflakes-2.3.0.tar.gz", hash = "sha256:e59fd8e750e588358f1b8885e5a4751203a0516e0ee6d34811089ac294c8806f"}, + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pygments = [ - {file = "Pygments-2.8.1-py3-none-any.whl", hash = "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8"}, - {file = "Pygments-2.8.1.tar.gz", hash = "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pynacl = [ - {file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"}, - {file = "PyNaCl-1.4.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514"}, - {file = "PyNaCl-1.4.0-cp27-cp27m-win32.whl", hash = "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574"}, - {file = "PyNaCl-1.4.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"}, - {file = "PyNaCl-1.4.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7"}, - {file = "PyNaCl-1.4.0-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122"}, - {file = "PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d"}, - {file = "PyNaCl-1.4.0-cp35-abi3-win32.whl", hash = "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634"}, - {file = "PyNaCl-1.4.0-cp35-abi3-win_amd64.whl", hash = "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6"}, - {file = "PyNaCl-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4"}, - {file = "PyNaCl-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25"}, - {file = "PyNaCl-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4"}, - {file = "PyNaCl-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6"}, - {file = "PyNaCl-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f"}, - {file = "PyNaCl-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f"}, - {file = "PyNaCl-1.4.0-cp38-cp38-win32.whl", hash = "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96"}, - {file = "PyNaCl-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420"}, - {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"}, + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pyrsistent = [ - {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, -] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] -pytest-aiohttp = [ - {file = "pytest-aiohttp-0.3.0.tar.gz", hash = "sha256:c929854339637977375838703b62fef63528598bc0a9d451639eba95f4aaa44f"}, - {file = "pytest_aiohttp-0.3.0-py3-none-any.whl", hash = "sha256:0b9b660b146a65e1313e2083d0d2e1f63047797354af9a28d6b7c9f0726fa33d"}, -] -pytest-black = [ - {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"}, -] -pytest-cov = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] -pytest-dotenv = [ - {file = "pytest-dotenv-0.5.2.tar.gz", hash = "sha256:2dc6c3ac6d8764c71c6d2804e902d0ff810fa19692e95fe138aefc9b1aa73732"}, - {file = "pytest_dotenv-0.5.2-py3-none-any.whl", hash = "sha256:40a2cece120a213898afaa5407673f6bd924b1fa7eafce6bda0e8abffe2f710f"}, -] -pytest-flake8 = [ - {file = "pytest-flake8-1.0.7.tar.gz", hash = "sha256:f0259761a903563f33d6f099914afef339c085085e643bee8343eb323b32dd6b"}, - {file = "pytest_flake8-1.0.7-py2.py3-none-any.whl", hash = "sha256:c28cf23e7d359753c896745fd4ba859495d02e16c84bac36caa8b1eec58f5bc1"}, -] -pytest-isort = [ - {file = "pytest-isort-2.0.0.tar.gz", hash = "sha256:821a8c5c9c4f3a3c52cfa9c541fbe89ac9e28728125125af53724c4c3f129117"}, - {file = "pytest_isort-2.0.0-py3-none-any.whl", hash = "sha256:ab949c593213dad38ba75db32a0ce361fcddd11d4152be4a2c93b85104cc4376"}, -] -pytest-localserver = [ - {file = "pytest-localserver-0.5.1.post0.tar.gz", hash = "sha256:5ec7f8e6534cf03887af2cb59e577f169ac0e8b2fd2c3e3409280035f386d407"}, - {file = "pytest_localserver-0.5.1.post0-py3-none-any.whl", hash = "sha256:b3ff1b8bade571d54701bad3efd68ca1bb463ad88daa75da15cc8842809659ea"}, -] -pytest-mypy = [ - {file = "pytest-mypy-0.8.1.tar.gz", hash = "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11"}, - {file = "pytest_mypy-0.8.1-py3-none-any.whl", hash = "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b"}, + {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, + {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, + {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, + {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, ] python-dotenv = [ - {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, - {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, + {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"}, + {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"}, ] pytz = [ - {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, - {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pywin32 = [ {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, @@ -2001,68 +1793,29 @@ pyyaml = [ {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] -regex = [ - {file = "regex-2021.3.17-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b97ec5d299c10d96617cc851b2e0f81ba5d9d6248413cd374ef7f3a8871ee4a6"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cb4ee827857a5ad9b8ae34d3c8cc51151cb4a3fe082c12ec20ec73e63cc7c6f0"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:633497504e2a485a70a3268d4fc403fe3063a50a50eed1039083e9471ad0101c"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:a59a2ee329b3de764b21495d78c92ab00b4ea79acef0f7ae8c1067f773570afa"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f85d6f41e34f6a2d1607e312820971872944f1661a73d33e1e82d35ea3305e14"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4651f839dbde0816798e698626af6a2469eee6d9964824bb5386091255a1694f"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:39c44532d0e4f1639a89e52355b949573e1e2c5116106a395642cbbae0ff9bcd"}, - {file = "regex-2021.3.17-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3d9a7e215e02bd7646a91fb8bcba30bc55fd42a719d6b35cf80e5bae31d9134e"}, - {file = "regex-2021.3.17-cp36-cp36m-win32.whl", hash = "sha256:159fac1a4731409c830d32913f13f68346d6b8e39650ed5d704a9ce2f9ef9cb3"}, - {file = "regex-2021.3.17-cp36-cp36m-win_amd64.whl", hash = "sha256:13f50969028e81765ed2a1c5fcfdc246c245cf8d47986d5172e82ab1a0c42ee5"}, - {file = "regex-2021.3.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9d8d286c53fe0cbc6d20bf3d583cabcd1499d89034524e3b94c93a5ab85ca90"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:201e2619a77b21a7780580ab7b5ce43835e242d3e20fef50f66a8df0542e437f"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d47d359545b0ccad29d572ecd52c9da945de7cd6cf9c0cfcb0269f76d3555689"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ea2f41445852c660ba7c3ebf7d70b3779b20d9ca8ba54485a17740db49f46932"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:486a5f8e11e1f5bbfcad87f7c7745eb14796642323e7e1829a331f87a713daaa"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:18e25e0afe1cf0f62781a150c1454b2113785401ba285c745acf10c8ca8917df"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:a2ee026f4156789df8644d23ef423e6194fad0bc53575534101bb1de5d67e8ce"}, - {file = "regex-2021.3.17-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:4c0788010a93ace8a174d73e7c6c9d3e6e3b7ad99a453c8ee8c975ddd9965643"}, - {file = "regex-2021.3.17-cp37-cp37m-win32.whl", hash = "sha256:575a832e09d237ae5fedb825a7a5bc6a116090dd57d6417d4f3b75121c73e3be"}, - {file = "regex-2021.3.17-cp37-cp37m-win_amd64.whl", hash = "sha256:8e65e3e4c6feadf6770e2ad89ad3deb524bcb03d8dc679f381d0568c024e0deb"}, - {file = "regex-2021.3.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a0df9a0ad2aad49ea3c7f65edd2ffb3d5c59589b85992a6006354f6fb109bb18"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b98bc9db003f1079caf07b610377ed1ac2e2c11acc2bea4892e28cc5b509d8d5"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:808404898e9a765e4058bf3d7607d0629000e0a14a6782ccbb089296b76fa8fe"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:5770a51180d85ea468234bc7987f5597803a4c3d7463e7323322fe4a1b181578"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:976a54d44fd043d958a69b18705a910a8376196c6b6ee5f2596ffc11bff4420d"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:63f3ca8451e5ff7133ffbec9eda641aeab2001be1a01878990f6c87e3c44b9d5"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bcd945175c29a672f13fce13a11893556cd440e37c1b643d6eeab1988c8b209c"}, - {file = "regex-2021.3.17-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:3d9356add82cff75413bec360c1eca3e58db4a9f5dafa1f19650958a81e3249d"}, - {file = "regex-2021.3.17-cp38-cp38-win32.whl", hash = "sha256:f5d0c921c99297354cecc5a416ee4280bd3f20fd81b9fb671ca6be71499c3fdf"}, - {file = "regex-2021.3.17-cp38-cp38-win_amd64.whl", hash = "sha256:14de88eda0976020528efc92d0a1f8830e2fb0de2ae6005a6fc4e062553031fa"}, - {file = "regex-2021.3.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4c2e364491406b7888c2ad4428245fc56c327e34a5dfe58fd40df272b3c3dab3"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux1_i686.whl", hash = "sha256:8bd4f91f3fb1c9b1380d6894bd5b4a519409135bec14c0c80151e58394a4e88a"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:882f53afe31ef0425b405a3f601c0009b44206ea7f55ee1c606aad3cc213a52c"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:07ef35301b4484bce843831e7039a84e19d8d33b3f8b2f9aab86c376813d0139"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:360a01b5fa2ad35b3113ae0c07fb544ad180603fa3b1f074f52d98c1096fa15e"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:709f65bb2fa9825f09892617d01246002097f8f9b6dde8d1bb4083cf554701ba"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:c66221e947d7207457f8b6f42b12f613b09efa9669f65a587a2a71f6a0e4d106"}, - {file = "regex-2021.3.17-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c782da0e45aff131f0bed6e66fbcfa589ff2862fc719b83a88640daa01a5aff7"}, - {file = "regex-2021.3.17-cp39-cp39-win32.whl", hash = "sha256:dc9963aacb7da5177e40874585d7407c0f93fb9d7518ec58b86e562f633f36cd"}, - {file = "regex-2021.3.17-cp39-cp39-win_amd64.whl", hash = "sha256:a0d04128e005142260de3733591ddf476e4902c0c23c1af237d9acf3c96e1b38"}, - {file = "regex-2021.3.17.tar.gz", hash = "sha256:4b8a1fb724904139149a43e172850f35aa6ea97fb0545244dc0b805e0154ed68"}, -] requests = [ - {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, - {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sniffio = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, - {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sphinx = [ - {file = "Sphinx-3.5.4-py3-none-any.whl", hash = "sha256:2320d4e994a191f4b4be27da514e46b3d6b420f2ff895d064f52415d342461e8"}, - {file = "Sphinx-3.5.4.tar.gz", hash = "sha256:19010b7b9fa0dc7756a6e105b2aacd3a80f798af3c25c273be64d7beeb482cb1"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"}, - {file = "sphinx_autodoc_typehints-1.11.1-py3-none-any.whl", hash = "sha256:da049791d719f4c9813642496ee4764203e317f0697eb75446183fa2a68e3f77"}, + {file = "sphinx_autodoc_typehints-1.17.0-py3-none-any.whl", hash = "sha256:081daf53077b4ae1c28347d6d858e13e63aefe3b4aacef79fd717dd60687b470"}, + {file = "sphinx_autodoc_typehints-1.17.0.tar.gz", hash = "sha256:51c7b3f5cb9ccd15d0b52088c62df3094f1abd9612930340365c26def8629a14"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, @@ -2073,8 +1826,8 @@ sphinxcontrib-devhelp = [ {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, - {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, ] sphinxcontrib-jsmath = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, @@ -2089,121 +1842,150 @@ sphinxcontrib-qthelp = [ {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, - {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] sphinxcontrib-trio = [ {file = "sphinxcontrib-trio-1.1.2.tar.gz", hash = "sha256:9f1ba9c1d5965b534e85258d8b677dd94e9b1a9a2e918b85ccd42590596b47c0"}, {file = "sphinxcontrib_trio-1.1.2-py3-none-any.whl", hash = "sha256:1b849be08a147ef4113e35c191a51c5792613a9a54697b497cd91656d906a232"}, ] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.31-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c3abc34fed19fdeaead0ced8cf56dd121f08198008c033596aa6aae7cc58f59f"}, + {file = "SQLAlchemy-1.4.31-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8d0949b11681380b4a50ac3cd075e4816afe9fa4a8c8ae006c1ca26f0fa40ad8"}, + {file = "SQLAlchemy-1.4.31-cp27-cp27m-win32.whl", hash = "sha256:f3b7ec97e68b68cb1f9ddb82eda17b418f19a034fa8380a0ac04e8fe01532875"}, + {file = "SQLAlchemy-1.4.31-cp27-cp27m-win_amd64.whl", hash = "sha256:81f2dd355b57770fdf292b54f3e0a9823ec27a543f947fa2eb4ec0df44f35f0d"}, + {file = "SQLAlchemy-1.4.31-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4ad31cec8b49fd718470328ad9711f4dc703507d434fd45461096da0a7135ee0"}, + {file = "SQLAlchemy-1.4.31-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:05fa14f279d43df68964ad066f653193187909950aa0163320b728edfc400167"}, + {file = "SQLAlchemy-1.4.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dccff41478050e823271642837b904d5f9bda3f5cf7d371ce163f00a694118d6"}, + {file = "SQLAlchemy-1.4.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57205844f246bab9b666a32f59b046add8995c665d9ecb2b7b837b087df90639"}, + {file = "SQLAlchemy-1.4.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8210090a816d48a4291a47462bac750e3bc5c2442e6d64f7b8137a7c3f9ac5"}, + {file = "SQLAlchemy-1.4.31-cp310-cp310-win32.whl", hash = "sha256:2e216c13ecc7fcdcbb86bb3225425b3ed338e43a8810c7089ddb472676124b9b"}, + {file = "SQLAlchemy-1.4.31-cp310-cp310-win_amd64.whl", hash = "sha256:e3a86b59b6227ef72ffc10d4b23f0fe994bef64d4667eab4fb8cd43de4223bec"}, + {file = "SQLAlchemy-1.4.31-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2fd4d3ca64c41dae31228b80556ab55b6489275fb204827f6560b65f95692cf3"}, + {file = "SQLAlchemy-1.4.31-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f22c040d196f841168b1456e77c30a18a3dc16b336ddbc5a24ce01ab4e95ae0"}, + {file = "SQLAlchemy-1.4.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0c7171aa5a57e522a04a31b84798b6c926234cb559c0939840c3235cf068813"}, + {file = "SQLAlchemy-1.4.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d046a9aeba9bc53e88a41e58beb72b6205abb9a20f6c136161adf9128e589db5"}, + {file = "SQLAlchemy-1.4.31-cp36-cp36m-win32.whl", hash = "sha256:d86132922531f0dc5a4f424c7580a472a924dd737602638e704841c9cb24aea2"}, + {file = "SQLAlchemy-1.4.31-cp36-cp36m-win_amd64.whl", hash = "sha256:ca68c52e3cae491ace2bf39b35fef4ce26c192fd70b4cd90f040d419f70893b5"}, + {file = "SQLAlchemy-1.4.31-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:cf2cd387409b12d0a8b801610d6336ee7d24043b6dd965950eaec09b73e7262f"}, + {file = "SQLAlchemy-1.4.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb4b15fb1f0aafa65cbdc62d3c2078bea1ceecbfccc9a1f23a2113c9ac1191fa"}, + {file = "SQLAlchemy-1.4.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c317ddd7c586af350a6aef22b891e84b16bff1a27886ed5b30f15c1ed59caeaa"}, + {file = "SQLAlchemy-1.4.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c7ed6c69debaf6198fadb1c16ae1253a29a7670bbf0646f92582eb465a0b999"}, + {file = "SQLAlchemy-1.4.31-cp37-cp37m-win32.whl", hash = "sha256:6a01ec49ca54ce03bc14e10de55dfc64187a2194b3b0e5ac0fdbe9b24767e79e"}, + {file = "SQLAlchemy-1.4.31-cp37-cp37m-win_amd64.whl", hash = "sha256:330eb45395874cc7787214fdd4489e2afb931bc49e0a7a8f9cd56d6e9c5b1639"}, + {file = "SQLAlchemy-1.4.31-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:5e9c7b3567edbc2183607f7d9f3e7e89355b8f8984eec4d2cd1e1513c8f7b43f"}, + {file = "SQLAlchemy-1.4.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de85c26a5a1c72e695ab0454e92f60213b4459b8d7c502e0be7a6369690eeb1a"}, + {file = "SQLAlchemy-1.4.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:975f5c0793892c634c4920057da0de3a48bbbbd0a5c86f5fcf2f2fedf41b76da"}, + {file = "SQLAlchemy-1.4.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5c20c8415173b119762b6110af64448adccd4d11f273fb9f718a9865b88a99c"}, + {file = "SQLAlchemy-1.4.31-cp38-cp38-win32.whl", hash = "sha256:b35dca159c1c9fa8a5f9005e42133eed82705bf8e243da371a5e5826440e65ca"}, + {file = "SQLAlchemy-1.4.31-cp38-cp38-win_amd64.whl", hash = "sha256:b7b20c88873675903d6438d8b33fba027997193e274b9367421e610d9da76c08"}, + {file = "SQLAlchemy-1.4.31-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:85e4c244e1de056d48dae466e9baf9437980c19fcde493e0db1a0a986e6d75b4"}, + {file = "SQLAlchemy-1.4.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79e73d5ee24196d3057340e356e6254af4d10e1fc22d3207ea8342fc5ffb977"}, + {file = "SQLAlchemy-1.4.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:15a03261aa1e68f208e71ae3cd845b00063d242cbf8c87348a0c2c0fc6e1f2ac"}, + {file = "SQLAlchemy-1.4.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ddc5e5ccc0160e7ad190e5c61eb57560f38559e22586955f205e537cda26034"}, + {file = "SQLAlchemy-1.4.31-cp39-cp39-win32.whl", hash = "sha256:289465162b1fa1e7a982f8abe59d26a8331211cad4942e8031d2b7db1f75e649"}, + {file = "SQLAlchemy-1.4.31-cp39-cp39-win_amd64.whl", hash = "sha256:9e4fb2895b83993831ba2401b6404de953fdbfa9d7d4fa6a4756294a83bbc94f"}, + {file = "SQLAlchemy-1.4.31.tar.gz", hash = "sha256:582b59d1e5780a447aada22b461e50b404a9dc05768da1d87368ad8190468418"}, +] texttable = [ - {file = "texttable-1.6.3-py2.py3-none-any.whl", hash = "sha256:f802f2ef8459058736264210f716c757cbf85007a30886d8541aa8c3404f1dda"}, - {file = "texttable-1.6.3.tar.gz", hash = "sha256:ce0faf21aa77d806bbff22b107cc22cce68dc9438f97a2df32c93e9afa4ce436"}, + {file = "texttable-1.6.4-py2.py3-none-any.whl", hash = "sha256:dd2b0eaebb2a9e167d1cefedab4700e5dcbdb076114eed30b58b97ed6b37d6f2"}, + {file = "texttable-1.6.4.tar.gz", hash = "sha256:42ee7b9e15f7b225747c3fa08f43c5d6c83bc899f80ff9bae9319334824076e9"}, ] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, -] -typed-ast = [ - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, - {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, - {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, - {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, - {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, - {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, - {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, - {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, - {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, - {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, -] -typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] virtualenv = [ - {file = "virtualenv-20.4.3-py2.py3-none-any.whl", hash = "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c"}, - {file = "virtualenv-20.4.3.tar.gz", hash = "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107"}, + {file = "virtualenv-20.14.0-py2.py3-none-any.whl", hash = "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66"}, + {file = "virtualenv-20.14.0.tar.gz", hash = "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8"}, ] watchgod = [ - {file = "watchgod-0.7-py3-none-any.whl", hash = "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7"}, - {file = "watchgod-0.7.tar.gz", hash = "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29"}, + {file = "watchgod-0.8.1-py3-none-any.whl", hash = "sha256:4ba20c2fa3e63df706ab50e694b9453b05395fadb7cbbfd984d71fb1547d485d"}, + {file = "watchgod-0.8.1.tar.gz", hash = "sha256:c12d15f3df7d11e740704e45398277f75f1d78f46ad59ca9d7505bfd8b8d3086"}, ] websocket-client = [ - {file = "websocket_client-0.58.0-py2.py3-none-any.whl", hash = "sha256:44b5df8f08c74c3d82d28100fdc81f4536809ce98a17f0757557813275fbb663"}, - {file = "websocket_client-0.58.0.tar.gz", hash = "sha256:63509b41d158ae5b7f67eb4ad20fecbb4eee99434e73e140354dc3ff8e09716f"}, -] -werkzeug = [ - {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, - {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, -] -wrapt = [ - {file = "wrapt-1.12.1.tar.gz", hash = "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"}, + {file = "websocket-client-0.59.0.tar.gz", hash = "sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"}, + {file = "websocket_client-0.59.0-py2.py3-none-any.whl", hash = "sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32"}, ] yarl = [ - {file = "yarl-1.6.3-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76"}, - {file = "yarl-1.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366"}, - {file = "yarl-1.6.3-cp36-cp36m-win32.whl", hash = "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721"}, - {file = "yarl-1.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643"}, - {file = "yarl-1.6.3-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f"}, - {file = "yarl-1.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970"}, - {file = "yarl-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e"}, - {file = "yarl-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50"}, - {file = "yarl-1.6.3-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2"}, - {file = "yarl-1.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2"}, - {file = "yarl-1.6.3-cp38-cp38-win32.whl", hash = "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896"}, - {file = "yarl-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a"}, - {file = "yarl-1.6.3-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0"}, - {file = "yarl-1.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4"}, - {file = "yarl-1.6.3-cp39-cp39-win32.whl", hash = "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424"}, - {file = "yarl-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6"}, - {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"}, + {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"}, + {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"}, + {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"}, + {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"}, + {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"}, + {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"}, + {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"}, + {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"}, + {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"}, + {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"}, + {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"}, + {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"}, + {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] diff --git a/pyproject.toml b/pyproject.toml index af98e57..5a2d4bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,39 +1,30 @@ [tool.poetry] name = "os_credits" -version = "1.2.0" +version = "2.0.0" description = "" -authors = ["gilbus"] +authors = ["gilbus", "ekatchko"] [tool.poetry.dependencies] -python = "^3.9" +python = "3.10.1" aiohttp = "3.8.1" -aioinflux = "0.9.0" aiohttp-swagger = "1.0.16" -prometheus_async = {version = "19.2",extras = ["aiohttp"]} aiohttp_jinja2 = "1.5" aiosmtplib = "1.1.6" -mypy_extensions = "0.4.3" +asyncpg = "0.25.0" +SQLAlchemy = "1.4.31" +alembic = "1.7.7" +psycopg2 = "2.9.3" [tool.poetry.dev-dependencies] aiohttp-devtools = "1.0.post0" -pytest = "6.2.5" -pytest-aiohttp = "0.3.0" -pre-commit = "2.16.0" -black = {version = "20.8b1",allow-prereleases = true} -mypy = "0.800" -sphinx = "3.5.4" -sphinx-autodoc-typehints = "1.11.1" -pytest-localserver = "0.5.1.post0" -pytest-cov = "3.0.0" +pre-commit = "2.17.0" +black = {version = "22.3.0",allow-prereleases = true} +sphinx-autodoc-typehints = "1.17.0" +sphinx = "4.5.0" sphinxcontrib-trio = "1.1.2" -lxml = "4.7.1" -pytest-dotenv = "0.5.2" +lxml = "4.8.0" sphinxcontrib-programoutput = "0.17" docker-compose = "1.29.2" -pytest-black = "0.3.12" -pytest-mypy = "0.8.1" -pytest-flake8 = "1.0.7" -pytest-isort = "2.0.0" [tool.poetry.scripts] os-credits="os_credits.cli:main" diff --git a/src/os_credits/credits/__init__.py b/src/__init__.py similarity index 100% rename from src/os_credits/credits/__init__.py rename to src/__init__.py diff --git a/src/os_credits/__init__.py b/src/os_credits/__init__.py index 041d6d7..6a83f81 100644 --- a/src/os_credits/__init__.py +++ b/src/os_credits/__init__.py @@ -1,3 +1,3 @@ -__version__ = "1.0.0" +__version__ = "2.0.0" __author__ = "gilbus" __license__ = "AGPLv3" diff --git a/src/os_credits/credits/base_models.py b/src/os_credits/credits/base_models.py deleted file mode 100644 index c8e84c1..0000000 --- a/src/os_credits/credits/base_models.py +++ /dev/null @@ -1,238 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from dataclasses import field -from decimal import Decimal -from typing import Any -from typing import ClassVar -from typing import Dict -from typing import NewType -from typing import Type -from typing import TypeVar - -from os_credits.exceptions import MeasurementError -from os_credits.influx.helper import InfluxSerializer -from os_credits.influx.model import InfluxDBPoint -from os_credits.log import internal_logger -from os_credits.settings import config - -REGISTERED_MEASUREMENTS = {} - -Credits = NewType("Credits", Decimal) -"""Distinct Credits type to prevent mixing of regular Decimals and Credits, since we -MUST apply quantize to every instance of it.""" - - -class CreditsSerializer(InfluxSerializer, types=["Credits"]): - """Implementation of the :class:`~os_credits.influx.helper.InfluxSerializer` - interface to be able to store our new :class:`Credits` inside *InfluxDB*. - """ - - @staticmethod - def serialize(value: Any) -> float: - return float(value) - - @staticmethod - def deserialize(value: Any) -> Credits: - return Credits(Decimal(value).quantize(config["OS_CREDITS_PRECISION"])) - - -@dataclass(init=False, frozen=True) -class UsageMeasurement(InfluxDBPoint): - """Base data class of all usage measurements. Cannot be used directly since no - metric is attached to it which is why ``init=False``. - - Pure dataclasses which should not be extended with any kind of logic other than - holding information. - """ - - location_id: int = field(metadata={"tag": True}) - """The ID of the ``Resource`` in *Perun* representing the location from which this - measurement has been received. - - Must be present to bill a group based on this measurement. - """ - - project_name: str = field(metadata={"tag": True}) - """The name of the project to which this measurement belongs. - """ - - value: float - """Value of the measurement, stored by Prometheus inside this field when using - InfluxDB as remote storage. - """ - - metric: Type[Metric] = field(repr=False, init=False, compare=False) - """Every Measurement class must be connected with a metric, since the latter - contains the logic to bill a project based on ``value``. - - :attr:`Metric.name` is also used to determine which measurement should be created - based on the content of an incoming usage measurement in Influx Line protocol. - """ - - def __init_subclass__(cls: Type[UsageMeasurement]) -> None: - REGISTERED_MEASUREMENTS[cls.metric.name] = cls - - -# MeasurementType -MT = TypeVar("MT", bound=UsageMeasurement) - - -class Metric: - """Metrics hold the information and logic how to bill measurements. - - The essential functions of every Metric are :func:`calculate_credits` and - :func:`costs_per_hour`. - """ - - _metrics_by_name: Dict[str, Type[Metric]] = {} - metrics_by_friendly_name: Dict[str, Type[Metric]] = {} - - name: ClassVar[str] - """Corresponds to the name of a measurement stored inside *InfluxDB*. - - This name is also used by :func:`~os_credits.credits.models.measurement_by_name` to - determine whether a submitted measurement is billable or not. - """ - friendly_name: ClassVar[str] - """Human readable name of the metric. - """ - description: ClassVar[str] = "" - """Provides further information about the metric, i.e. that the - :class:`~os_credits.credits.models.RAMMetric` contains the amount of used Memory in - MiB. - """ - - def __init_subclass__(cls, name: str, friendly_name: str) -> None: - if None in (name, friendly_name): - internal_logger.debug( - "Not registering subclass %s of `Metric` since one or both of its " - "names are None." - ) - return - if name in Metric._metrics_by_name: - raise ValueError( - f"Metric with name {name} is already registered: " - f"{Metric._metrics_by_name[name]}" - ) - if friendly_name in Metric.metrics_by_friendly_name: - raise ValueError( - f"Metric with friendly_name {friendly_name} is already registered: " - f"{Metric.metrics_by_friendly_name[friendly_name]}" - ) - Metric._metrics_by_name.update({name: cls}) - Metric.metrics_by_friendly_name.update({friendly_name: cls}) - cls.name = name - cls.friendly_name = friendly_name - internal_logger.debug("Registered subclass of `Metric`: %s", cls) - - @classmethod - def calculate_credits( - cls, *, current_measurement: MT, older_measurement: MT - ) -> Credits: - """Given two measurements determine how many credits should be billed. This - function should not be called directly but rather through the high level - function :func:`~os_credits.credits.billing.calculate_credits`. - - To prevent mistakes the arguments are keyword only. Defining their type as - ``MT`` (:data:`MT`) shows a type checker that both arguments should be the same - (sub)class of :class:`UsageMeasurement`. - - :param current_measurement: The measurement submitted by *InfluxDB* which is - processed by the current task. Represents the most recent measurement of - this metric. - :param older_measurement: Measurement of the same type as - :attr:`current_measurement` which is the most recent one on whose basis - credits have been billed. - """ - raise NotImplementedError("Must be implemented by subclass.") - - @classmethod - def api_information(cls) -> Dict[str, Any]: - """ - Returns a dictionary containing the description and type information of this - metric. - - :return: Dictionary holding information about this metric, see - :func:`costs_per_hour` to understand the relevance of ``type``. - """ - return { - "type": "int", - "description": cls.description, - "name": cls.name, - "friendly_name": cls.friendly_name, - } - - @classmethod - def costs_per_hour(cls, spec: Any) -> Credits: - """Used by the :func:`~os_credits.views.costs_per_hour` endpoint to calculate - the projected costs per hour of a virtual machine. - - :param spec: Of the same type as ``type`` of :func:`api_information`, indicates - the amount of resources used by the machine to be billed, e.g. the amount of - vCPU or MiB of RAM. - """ - raise NotImplementedError("Must be implemented by subclass.") - - -class TotalUsageMetric( - Metric, - # class definition must contain the following attributes to allow 'passthrough' from - # child classes - name=None, - friendly_name=None, -): - """Base class of every metric which is only billed by its usage values, i.e. the - timestamps of any measurements are not considered at all. - - One example is the :class:`~os_credits.credits.models.VCPUMetric` which bills the - amount of used virtual CPUs, the value of its corresponding measurement represents - this time in hours. - """ - - CREDITS_PER_VIRTUAL_HOUR: ClassVar[Decimal] - """Amount of credits which have to be paid for every virtual hour of usage of this - resource. Explicitly not defined as :class:`Credits` since the price of one virtual - hour may be lower than the precision given by ``OS_CREDITS_PRECISION``, - :ref:`Settings`, which means that no :func:`~decimal.Decimal.quantize` has been - applied to it, therefore it is no ``Credits`` type.""" - - def __init_subclass__(cls, **kwargs: Any) -> None: - if cls.CREDITS_PER_VIRTUAL_HOUR <= 0: - raise ValueError( - f"Metric type {cls.__name__} has non-positive " - f"CREDITS_PER_VIRTUAL_HOUR({cls.CREDITS_PER_VIRTUAL_HOUR})." - ) - super().__init_subclass__(**kwargs) - - @classmethod - def costs_per_hour(cls, spec: Decimal) -> Credits: - return Credits( - (spec * cls.CREDITS_PER_VIRTUAL_HOUR).quantize( - config["OS_CREDITS_PRECISION"] - ) - ) - - @classmethod - def calculate_credits( - cls, *, current_measurement: MT, older_measurement: MT - ) -> Credits: - """ - Base implementation how to bill two measurements of the same type. Expected to - be overwritten by subclasses whose billing logic goes beyond subtracting usage - values, e.g. if your current_measurement values are not continuously increasing - but fluctuating, i.e. being a delta instead of a total sum. - """ - if current_measurement.metric is not older_measurement.metric: - raise TypeError("Measurements must be of same type") - if current_measurement.timestamp < older_measurement.timestamp: - raise MeasurementError( - "Passed current_measurement must be older. Use the top-level " - "`calculate_credits` function to prevent this error." - ) - return Credits( - ( - (Decimal(current_measurement.value) - Decimal(older_measurement.value)) - * cls.CREDITS_PER_VIRTUAL_HOUR - ).quantize(config["OS_CREDITS_PRECISION"]) - ) diff --git a/src/os_credits/credits/billing.py b/src/os_credits/credits/billing.py deleted file mode 100644 index 58ff304..0000000 --- a/src/os_credits/credits/billing.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -from os_credits.credits.base_models import MT -from os_credits.credits.base_models import Credits -from os_credits.exceptions import CalculationResultError -from os_credits.log import internal_logger - - -def calculate_credits(measurement1: MT, measurement2: MT) -> Credits: - """ - High-level function to calculate the credits based on the differences of the two - usage measurements. - - Will sort the two measurements according to their time and call the - :func:`~os_credits.credits.base_models.Metric.calculate_credits` method of the - **more recent** measurement's metric to calculate the credits. - - :return: Non-negative amount of credits - :raises CalculationResultError: If the amount of credits would be negative. - """ - if measurement1.timestamp < measurement2.timestamp: - older_measurement, new_measurement = measurement1, measurement2 - else: - older_measurement, new_measurement = measurement2, measurement1 - - internal_logger.debug( - "Billing older `%s` and current measurement `%s`", - older_measurement, - new_measurement, - ) - credits = new_measurement.metric.calculate_credits( - current_measurement=new_measurement, older_measurement=older_measurement - ) - internal_logger.debug("Calculated credits: %f", credits) - if credits < 0: - raise CalculationResultError( - f"Credits calculation of {measurement1} and {measurement2} returned a " - "negative amount of credits." - ) - return credits diff --git a/src/os_credits/credits/models.py b/src/os_credits/credits/models.py deleted file mode 100644 index 29d75ba..0000000 --- a/src/os_credits/credits/models.py +++ /dev/null @@ -1,90 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from dataclasses import field -from typing import AnyStr -from typing import Type - -from os_credits.influx.model import InfluxDBPoint - -from .base_models import REGISTERED_MEASUREMENTS -from .base_models import Credits -from .base_models import Metric -from .base_models import TotalUsageMetric -from .base_models import UsageMeasurement -from os_credits.settings import config - - -class VCPUMetric(TotalUsageMetric, name="project_vcpu_usage", friendly_name="cpu"): - - CREDITS_PER_VIRTUAL_HOUR = config["VCPU_CREDIT_PER_HOUR"] - description = "Amount of vCPUs." - - -class RAMMetric(TotalUsageMetric, name="project_mb_usage", friendly_name="ram"): - - # always specify the amount as string to prevent inaccuracies of builtin float - CREDITS_PER_VIRTUAL_HOUR = config["RAM_CREDIT_PER_HOUR"] - description = ( - "Amount of RAM in GiB." - ) - - -# located next to the metrics to ensure that their classes are initialized and therefore -# registered in REGISTERED_MEASUREMENTS -def measurement_by_name(name: AnyStr) -> Type[UsageMeasurement]: - """Returns the correct :class:`UsageMeasurement` subclass corresponding to the given - Influx Line. - - The measurement itself does not know its name, but its connected metric does. - - :param name: The name of the measurement or a text in InfluxDB Line Protocol from - which the name is extracted. - :return: Subclass of :class:`~os_credits.credits.base_models.UsageMeasurement` - responsible for this measurement. - :raises ValueError: No - :class:`~os_credits.credits.base_models.UsageMeasurement` - responsible/available, i.e. the passed measurement is not needed/supported. - """ - if isinstance(name, bytes): - influx_line = name.decode() - else: - influx_line = name - - measurement_name = influx_line.split(",", 1)[0] - try: - return REGISTERED_MEASUREMENTS[measurement_name] - except KeyError: - raise ValueError(f"Measurement `{name}` it not supported/needed.") - - -@dataclass(frozen=True) -class VCPUMeasurement(UsageMeasurement): - metric: Type[Metric] = VCPUMetric - - -@dataclass(frozen=True) -class RAMMeasurement(UsageMeasurement): - metric: Type[Metric] = RAMMetric - - -@dataclass(frozen=True) -class BillingHistory(InfluxDBPoint): - """Whenever a project/group is successfully billed, meaning the amount used credits - has changed, we store the relevant data of the transaction inside the *InfluxDB*. - - The name of the group/project is used as ``measurement`` and ``timestamp`` is the - timestamp of the measurement which caused the billing. - - See :ref:`Credits History`. - """ - - credits_used: Credits - """Amount of credits used for the project **after** the billing. - """ - metric_name: str = field(metadata={"tag": True}) - """Name of the metric of the measurement which caused the billing. - """ - metric_friendly_name: str = field(metadata={"tag": True}) - """Human readable name of the metric of the measurement which caused the billing. - """ diff --git a/src/os_credits/credits/tasks.py b/src/os_credits/credits/tasks.py deleted file mode 100644 index 3cfb740..0000000 --- a/src/os_credits/credits/tasks.py +++ /dev/null @@ -1,331 +0,0 @@ -""" -Performs the actual calculations concerning usage and the resulting credit 'billing' -""" -from __future__ import annotations - -from asyncio import CancelledError -from asyncio import Lock -from asyncio import Queue -from asyncio import shield -from decimal import Decimal -from typing import Dict -from typing import cast - -from aiohttp.web import Application - -from os_credits.influx.client import InfluxDBClient -from os_credits.log import TASK_ID -from os_credits.log import task_logger -from os_credits.notifications import EmailNotificationBase -from os_credits.notifications import HalfOfCreditsLeft -from os_credits.notifications import send_notification -from os_credits.perun.exceptions import DenbiCreditsUsedMissing -from os_credits.perun.exceptions import GroupNotExistsError -from os_credits.perun.group import Group -from os_credits.prometheus_metrics import worker_exceptions_counter -from os_credits.settings import config - -from .base_models import Credits -from .base_models import UsageMeasurement -from .billing import calculate_credits -from .models import BillingHistory -from .models import measurement_by_name - - -def unique_identifier(influx_line: str) -> str: - """Hashes the passed Influx Line and returns an unique ID. - - Used to uniquely identify all log messages related to one specific Influx Line. - Needed since multiple ones are processed in parallel to the logs are scattered. Used - as :ref:`Logging`. - - :param influx_line: String to hash - :return: Unique ID consisting of 12 numbers - """ - # insert leading zeros if less numbers than 12 but don't use more - return format(abs(hash(influx_line)), ">012")[:12] - - -async def worker(name: str, app: Application) -> None: - """Worker task to process Influx Lines put into the :ref:`Task Queue` by the - ``/write`` endpoint(:func:`~os_credits.views.influxdb_write`). - - Runs inside a ``while True`` loop, and blocks until it retrieves an item from the - :ref:`Task Queue`. - - #. Calls :func:`unique_identifier` to generate a unique ID for the Influx Line - #. Shields :func:`process_influx_line` by wrapping it in :func:`~asyncio.shield`. - Must be shielded since the attributes of group objects are retrieved and saved - with two separate calls to *Perun*. The task **must not** be cancelled between - the two ``save`` calls. - #. In case exceptions raised when processing the item or when sending the - notification log it properly. - #. Finally, in every case, signal to the queue that the task has been processed. - - - :param name: Name of this worker used for logging - :param app: Application instance holding the helper class instances - """ - group_locks = cast(Dict[str, Lock], app["group_locks"]) - task_queue = cast(Queue, app["task_queue"]) - while True: - try: - influx_line: str = await task_queue.get() - except CancelledError: - task_logger.info("Worker %s was cancelled when waiting for new item.", name) - raise - try: - task_id = unique_identifier(influx_line) - TASK_ID.set(task_id) - task_logger.debug("Worker %s starting task `%s`", name, task_id) - - # do not cancel a running task - await shield(process_influx_line(influx_line, app, group_locks)) - task_logger.debug( - "Worker %s finished task `%s` successfully", name, task_id - ) - # necessary since the tasks must continue working despite any exceptions that - # occurred - except CancelledError: - raise - except Exception as e: - worker_exceptions_counter.inc() - task_logger.exception( - "Worker %s exited task with unhandled exception: %s, stacktrace " - "attached", - name, - e, - ) - finally: - task_queue.task_done() - - -async def process_influx_line( - influx_line: str, app: Application, group_locks: Dict[str, Lock] -) -> None: - """Performs all preliminary task before actually billing a Group/Project. - - #. Determine whether the passed item/str/Influx Line is billable/needed - #. Deserialize it into a :class:`~os_credits.models.UsageMeasurement` by calling - :func:`~os_credits.credits.models.measurement_by_name`, see :ref:`Metrics and - Measurements`. - #. Create a :class:`~os_credits.perun.group.Group` object, see :ref:`Perun`. - #. If a project whitelist is set in :ref:`Settings`, see whether the group is part - of it - #. Calls :func:`update_credits` once the correct :ref:`lock ` could be - acquired. Catch every notification, see :ref:`Notifications`, and send it. - - :param influx_line: String/Influx Line to process - :param app: Application object holding our helper class instances - :param group_locks: Dictionary with :ref:`Group Locks` - """ - task_logger.debug("Processing Influx Line `%s`", influx_line) - # we want to end this task as quickly as possible if the InfluxDB Point is not - # needed - try: - measurement_class = measurement_by_name(influx_line) - except ValueError: - task_logger.debug("Ignoring since the measurement is not needed/billable") - return - try: - measurement = measurement_class.from_lineprotocol(influx_line) - except (KeyError, ValueError): - task_logger.exception( - "Could not convert influx line %s to UsageMeasurement. Appending " - "stacktrace", - influx_line, - ) - return - perun_group = Group(measurement.project_name, measurement.location_id) - if ( - config["OS_CREDITS_PROJECT_WHITELIST"] is not None - and perun_group.name not in config["OS_CREDITS_PROJECT_WHITELIST"] - ): - task_logger.info( - "Group `%s` is not part of given whitelist (%s). Ignoring measurement", - perun_group.name, - config["OS_CREDITS_PROJECT_WHITELIST"], - ) - return - task_logger.info( - "Processing UsageMeasurement `%s` - Group `%s`", measurement, perun_group - ) - task_logger.debug("Awaiting async lock for Group %s", perun_group.name) - try: - # since group_locks is a defaultdict a new Lock is automatically created if - # necessary - async with group_locks[perun_group.name]: - task_logger.debug("Acquired async lock for Group %s", perun_group.name) - await update_credits(perun_group, measurement, app) - except EmailNotificationBase as notification: - task_logger.info("Sending notification %s", notification) - await send_notification(notification) - - -async def update_credits( - group: Group, current_measurement: UsageMeasurement, app: Application -) -> None: - """Evaluates the measurement and decides what to do. - - #. Connect the :ref:`group ` - #. If the amount of used credits is not set yet: - - #. If no timestamp of the metric of the current measurement exists this group has - never been billed before. Initialize the used credits with 0. - #. If a timestamp exists the group **must** have been billed before and the - absence of ``credits_used`` is an error in which case - :exc:`~os_credits.exceptions.DenbiCreditsUsedMissing` is raised. - #. If the metric has not been billed before store the timestamp of the current - measurement and send the values to *Perun*. - #. Retrieve previous measurements of this group and metric especially the one whose - timestamp is stored in the group. Perform additional tests to make sure that we - can continue billing this group and metric. - #. Call :func:`~os_credits.credits.billing.calculate_credits` to let the - metric calculate how many credits should be billed for the current measurement. - #. In case of a positive amount of credits to bill do so, store the timestamp of - current measurement inside the group, create an entry for the :ref:`Credits - History` and send the changed group attributes to *Perun*. - - When taking a look at the test coverage under ``htmlcov/tests/index.html`` this file - should have a very high value. Whenever you add a corner case or just another simple - if statement **write a test for it**! - - .. todo:: - - Metrics should decide how to react to a value change, the current behaviour is - tied to TotalUsageMetrics! Idea: Something like `raise ProceedWithoutBilling` in - case of a lower value which might be related to a change of the **start** - parameter of the *OpenStack Usage Exporter*. - - :param group: Group whose measurement is processed - Unconnected - :param current_measurement: Current measurement to process - :param app: Application instance holding the helper class instances - :raises EmailNotificationBase: Subclasses of it which are actual notifications can - be raised throughout the whole codebase. - :raise DenbiCreditsUsedMissing: See documentation above. - """ - try: - await group.connect() - except GroupNotExistsError as e: - task_logger.warning( - "Could not resolve group with name `%s` against perun. %r", group.name, e - ) - return - if group.credits_used.value is None: - # let's check whether any measurement timestamps are present, if so we are - # having a problem since this means that this group has been processed before! - if group.credits_timestamps.value: - raise DenbiCreditsUsedMissing( - f"Group {group.name} has non-empty credits_timestamps and therefore " - "processed before but is missing `credits_used` now. " - "Did someone modify the values by hand? Aborting" - ) - else: - task_logger.info( - "Group %s does not have `credits_used` and hasn't been billed before: " - "Initialising with 0", - group, - ) - group.credits_used.value = Decimal(0) - try: - last_measurement_timestamp = group.credits_timestamps.value[ - current_measurement.measurement - ] - except KeyError: - task_logger.info( - "Group %s has no timestamp of most recent measurement of %s. " - "Setting it to the timestamp of the current measurement.", - group, - current_measurement.measurement, - ) - # set timestamp of current measurement so we can start billing the group once - # the next measurements are submitted - group.credits_timestamps.value[ - current_measurement.measurement - ] = current_measurement.timestamp - await group.save() - return - task_logger.debug( - "Last time credits were billed: %s", - group.credits_timestamps.value[current_measurement.measurement], - ) - - if current_measurement.timestamp <= last_measurement_timestamp: - task_logger.warning( - "Current measurement is not more recent than the last measurement. HOW? " - "Ignoring" - ) - return - - # help type checker since it can not infer the type of app['influx_client'] - # statically - influx_client = cast(InfluxDBClient, app["influx_client"]) - project_measurements = await influx_client.previous_measurements( - measurement=current_measurement, since=last_measurement_timestamp - ) - try: - last_measurement = project_measurements[last_measurement_timestamp] - except KeyError: - oldest_measurement_timestamp = list(project_measurements)[0] - - group.credits_timestamps.value[ - current_measurement.measurement - ] = oldest_measurement_timestamp - task_logger.warning( - "InfluxDB does not contains usage values for Group %s for measurement %s " - "at timestamp %s, which means that the period between the last measurement " - "and now cannot be used for credit billing. Setting the timestamp to the " - "oldest measurement between now and the last time measurements were billed " - "inside InfluxDB (%s)", - group, - current_measurement.measurement, - last_measurement_timestamp, - oldest_measurement_timestamp, - ) - await group.save() - return - - # see TODO in __doc__ - if current_measurement.value == last_measurement.value: - task_logger.info( - "Values of this and previously billed measurement do not differ, " - "dropping it." - ) - return - - credits_to_bill = calculate_credits(current_measurement, last_measurement) - group.credits_timestamps.value[ - current_measurement.measurement - ] = current_measurement.timestamp - - previous_group_credits = group.credits_used.value - group.credits_used.value = group.credits_used.value + credits_to_bill - # Comparing the actual values makes sure that this case even triggers if - # credits_to_bill is not zero but so small that its changes are dropped due to - # rounding - if previous_group_credits == group.credits_used.value: - task_logger.info( - "Measurement does not change the amount of credits left due to rounding, " - "therefore no changes will be stored inside Perun or the InfluxDB." - ) - return - task_logger.info( - "Credits billed: %f, total Usage: %s/%d", - credits_to_bill, - group.credits_used.value, - group.credits_granted.value, - ) - billing_entry = BillingHistory( - measurement=group.name, - timestamp=current_measurement.timestamp, - credits_used=Credits(group.credits_used.value), - metric_name=current_measurement.metric.name, - metric_friendly_name=current_measurement.metric.friendly_name, - ) - await influx_client.write_billing_history(billing_entry) - await group.save() - half_of_credits_granted = Decimal(group.credits_granted.value) / 2 - if ( - previous_group_credits < half_of_credits_granted <= group.credits_used.value - ): - raise HalfOfCreditsLeft(group) diff --git a/src/os_credits/db_client/__init__.py b/src/os_credits/db_client/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/os_credits/db_client/__init__.py @@ -0,0 +1 @@ + diff --git a/src/os_credits/db_client/client.py b/src/os_credits/db_client/client.py new file mode 100644 index 0000000..ea2b64f --- /dev/null +++ b/src/os_credits/db_client/client.py @@ -0,0 +1,444 @@ +from datetime import datetime +from time import sleep + +import aiohttp +from aiohttp import ClientConnectorError +from sqlalchemy import select, desc, create_engine, and_, asc +from sqlalchemy.exc import OperationalError +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker, Session +from src.os_credits.settings import config +from src.os_credits.db_client.model import Base, MetricCredits, Project, Credits, PromCatalogReflected, PromMetricReflected, \ + make_measurement_class, Metric, Label, \ + BaseMeasurement +from src.os_credits.log import timescaledb_logger + + +class TimescaleDBManager: + + def __init__(self): + # Create sync engine and session for table creation and promscale reflection, + # as sqlalchemy inspector can not yet handle async connections + _DB_URI_SYNC = "postgresql+psycopg2://{user}:{password}@{host}:{port}/{db_name}".format( + user=config["POSTGRES_USER"], + password=config["POSTGRES_PASSWORD"], + host=config["POSTGRES_HOST"], + port=config["POSTGRES_PORT"], + db_name=config["POSTGRES_DB"] + ) + self.sync_engine = create_engine(_DB_URI_SYNC) + self.sync_session = sessionmaker(self.sync_engine) + + # Create async engine and sessionmaker for crud operations + _DB_URI_ASYNC = "postgresql+asyncpg://{user}:{password}@{host}:{port}/{db_name}".format( + user=config["POSTGRES_USER"], + password=config["POSTGRES_PASSWORD"], + host=config["POSTGRES_HOST"], + port=config["POSTGRES_PORT"], + db_name=config["POSTGRES_DB"] + ) + self.engine = create_async_engine(_DB_URI_ASYNC) + self.async_session = sessionmaker(self.engine, expire_on_commit=False, class_=AsyncSession, autoflush=False) + self.client_session = aiohttp.ClientSession() + + self.measurement_classes = {} + self.base = Base + + async def initialize(self): + timescaledb_logger.info(f"Initializing database with sync engine.") + # Create tables from models.py + self.create_tables() + # Reflect all promscale tables and init classes + if not config["ENDPOINTS_ONLY"]: + self.reflect_promscale_tables() + timescaledb_logger.info(f"Initializing done. Disposing of sync engine.") + self.sync_engine.dispose() + + ######################################################### + # init functions ######################################## + + def create_tables(self): + timescaledb_logger.info("Creating tables from base.") + connection_available = False + while not connection_available: + try: + self.base.metadata.create_all(self.sync_engine) + connection_available = True + except OperationalError: + timescaledb_logger.info(f"Database not online yet. Sleeping for 10s.") + sleep(10) + + def reflect_promscale_tables(self): + timescaledb_logger.info("Reflecting _prom_catalog tables.") + PromCatalogReflected.prepare(self.sync_engine) + with self.sync_session() as session: + self.wait_for_metric_names(session) + self.create_prom_metric_classes(session) + timescaledb_logger.info("Reflecting prom_metric tables.") + PromMetricReflected.prepare(self.sync_engine) + + def wait_for_metric_names(self, session: Session): + timescaledb_logger.info("Checking if there are any metric names.") + metric_names_exist = False + while not metric_names_exist: + metric_names = self.fetch_metric_names_sync(session) + if len(metric_names) > 0: + metric_names_exist = True + else: + timescaledb_logger.info("No metric names yet. Sleeping for 10 and retrying.") + sleep(10) + + def create_prom_metric_classes(self, session: Session): + self.measurement_classes = {} + metric_names = self.fetch_metric_names_sync(session) + timescaledb_logger.info(f"Creating BaseMeasurement objects for {metric_names}.") + for metric in metric_names: + timescaledb_logger.info(f"Creating BaseMeasurement object for {metric}.") + self.measurement_classes[metric[0].metric_name] = make_measurement_class(metric[0].metric_name) + + @classmethod + def fetch_metric_names_sync(cls, session: Session): + metric_names_result = session.execute(select(Metric)) + metric_names = [row + for row in metric_names_result.all() + if row is not None + and row.Metric.metric_name in config["METRICS_TO_BILL"]] + return metric_names + + # init functions end ################################ + ##################################################### + + ##################################################### + ##################################################### + # fetch promscale data ############################## + + # get metrics ####################################### + + @classmethod + async def fetch_metric_names(cls, session: AsyncSession): + metric_names_result = await session.execute(select(Metric)) + metric_names = [row + for row in metric_names_result.all() + if row is not None + and row.Metric.metric_name in config["METRICS_TO_BILL"]] + return metric_names + + # get label data #################################### + + @classmethod + async def fetch_all_labels(cls, session: AsyncSession): + labels_result = await session.execute(select(Label)) + return labels_result.all() + + @classmethod + async def fetch_label_by_project_name(cls, project_name, session: AsyncSession): # noqa + label_result = await session.execute(select(Label).where(Label.value == project_name)) + return label_result.first() + + @classmethod + async def fetch_all_labels_for_project_name_key(cls, session: AsyncSession): + labels_result = await session.execute(select(Label).where(Label.key == "project_name")) + return labels_result.all() + + # get measurements ################################## + + async def check_measurement_classes(self, session: AsyncSession): + metrics = await self.fetch_metric_names(session) + timescaledb_logger.debug(f"Checking if {metrics} in {self.measurement_classes}.") + for metric in metrics: + if metric[0].metric_name not in self.measurement_classes: + timescaledb_logger.debug(f"{metric} not found. Reflecting and disposing sync_engine.") + self.reflect_promscale_tables() + self.sync_engine.dispose() + break + + async def get_measurement_classes(self): + return self.measurement_classes + + async def fetch_all_metric_data(self, metric_name, session: AsyncSession): + measurement_results = await session.execute(select( + self.measurement_classes[metric_name].time, + self.measurement_classes[metric_name].project_name_id, + self.measurement_classes[metric_name].value + )) + return measurement_results.all() + + async def fetch_first_metric_data_by_project_name(self, project: Project, metric_name: str, session: AsyncSession): + measurement_result = await session.execute( + select( + self.measurement_classes[metric_name].time, + self.measurement_classes[metric_name].project_name_id, + self.measurement_classes[metric_name].value + ).where( + self.measurement_classes[metric_name].project_name_id == project.project_name_label_id + ).order_by(desc("time")) + ) + return measurement_result.first() + + async def fetch_measurements_since_inclusive_last( + self, project, metric_credis, session: AsyncSession + ): + measurement_results = await session.execute( + select( + self.measurement_classes[metric_credis.metric].time, + self.measurement_classes[metric_credis.metric].project_name_id, + self.measurement_classes[metric_credis.metric].value + ).where( + and_( + self.measurement_classes[metric_credis.metric].project_name_id == project.project_name_label_id, + self.measurement_classes[metric_credis.metric].time >= metric_credis.time + ) + ).order_by(asc("time")) + ) + return measurement_results.all() + + async def get_newest_measurement( + self, project: Project, metric_credis: MetricCredits, session: AsyncSession + ): + measurement_results = await session.execute( + select( + self.measurement_classes[metric_credis.metric].time, + self.measurement_classes[metric_credis.metric].project_name_id, + self.measurement_classes[metric_credis.metric].value + ).where( + self.measurement_classes[metric_credis.metric].project_name_id == project.project_name_label_id + ).order_by(asc("time")) + ) + return measurement_results.first() + + # fetch promscale data end ########################## + ##################################################### + ##################################################### + + ##################################################### + ##################################################### + # handle own data ################################### + + # project ########################################### + + @classmethod + async def create_project_by_label(cls, label: Label, session: AsyncSession): + project: Project = Project( + project_name=label.value, + project_name_label_id=label.id + ) + session.add(project) + await session.commit() + return project + + @classmethod + async def get_project_by_label(cls, label: Label, session: AsyncSession): + project_result = await session.execute( + select(Project).where(Project.project_name == label.value) + ) + return project_result.first() + + @classmethod + async def get_project(cls, project_name: str, session: AsyncSession): + project_result = await session.execute( + select(Project).where(Project.project_name == project_name) + ) + return project_result.first() + + async def get_granted_credits_for_project(self, project: Project, session): + timeout = aiohttp.ClientTimeout(total=10, connect=5) + params = {"project_name": project.project_name} + headers = {"X-Api-Key": config["API_CONTACT_KEY"]} + try: + async with self.client_session.get( + f"{config['API_CONTACT_BASE_URL']}/secure/granted-credits/", + timeout=timeout, + params=params, + headers=headers + ) as response: + if response.status == 200: + text = await response.text() + project.granted_credits = float(text) + if project.half_limit_reached_send: + half_limit = project.granted_credits / 2.0 + if project.used_credits < half_limit: + project.half_limit_reached_send = False + await session.flush() + else: + timescaledb_logger.warning(f"Could not get granted credits for {project}") + except ClientConnectorError: + timescaledb_logger.debug(f"No connection possible to get granted credits for {project}.") + except Exception as e: + timescaledb_logger.exception(e) + + async def inform_half_limit_reached(self, last_credits_entry, project, session): + timeout = aiohttp.ClientTimeout(total=10, connect=5) + data = { + "project_name": project.project_name, + "granted_credits": project.granted_credits, + "used_credits": last_credits_entry.used_credits, + "timestamp": datetime.timestamp(last_credits_entry.time) + } + headers = {"X-Api-Key": config["API_CONTACT_KEY"]} + try: + timescaledb_logger.info(f"Sending information about half limit reached for {project} with {last_credits_entry}.") + async with self.client_session.post( + f"{config['MAIL_CONTACT_URL']}", + timeout=timeout, + data=data, + headers=headers + ) as response: + if response.status == 200: + project.half_limit_reached_send = True + await session.flush() + timescaledb_logger.info(f"Information about half limit reached send for {project} with {last_credits_entry}.") + else: + timescaledb_logger.warning(f"Could not send half limit reached mail for {project} with {last_credits_entry}") + except ClientConnectorError: + timescaledb_logger.debug(f"No connection possible to send half limit reached mail for {project} with {last_credits_entry}.") + except Exception as e: + timescaledb_logger.exception(e) + + # credits ########################################### + + @classmethod + async def initialize_first_credits_entry( + cls, project: Project, metric_credits: MetricCredits, session: AsyncSession + ): + last_credits = Credits( + # time=metric_credits.time, + used_credits=metric_credits.used_credits, + granted_credits=project.granted_credits, + project_name=project.project_name, + by_metric=metric_credits.metric, + metric_time=metric_credits.time + ) + session.add(last_credits) + await session.flush() + return last_credits + + @classmethod + async def add_credits( + cls, project: Project, metric_credits: MetricCredits, session: AsyncSession, credits_value: float + ): + last_credits = Credits( + # time=metric_credits.time, + used_credits=credits_value, + granted_credits=project.granted_credits, + project_name=project.project_name, + by_metric=metric_credits.metric, + metric_time=metric_credits.time + ) + project.used_credits = last_credits.used_credits + session.add(last_credits) + await session.flush() + return last_credits + + @classmethod + async def get_credits_history(cls, project_name: str, since, end, session: AsyncSession): + credits_result = await session.execute( + select(Credits).where( + and_( + Credits.project_name == project_name, + Credits.time <= end, + Credits.time >= since + ) + ).order_by(asc(Credits.time)) + ) + return credits_result.all() + + @classmethod + async def get_latest_credits(cls, project_name: str, session: AsyncSession): + last_credits_result = await session.execute( + select(Credits.used_credits).where(Credits.project_name == project_name).order_by(desc(Credits.time)) + ) + return last_credits_result.first() + + # metric credits #################################### + + @classmethod + async def get_latest_metric_credits(cls, project: Project, metric: str, session: AsyncSession): + last_value_result = await session.execute( + select(MetricCredits).where( + and_( + MetricCredits.project_name == project.project_name, + MetricCredits.metric == metric + ) + ).order_by(desc(MetricCredits.time)) + ) + return last_value_result.first() + + async def initialize_first_metric_credits_entry(self, project: Project, metric: str, session: AsyncSession): + first_metric_row = await self.fetch_first_metric_data_by_project_name(project, metric, session) + first_metric_credits_value: MetricCredits = MetricCredits( + time=first_metric_row.time, + used_credits=0, + granted_credits=project.granted_credits, + metric=metric, + project_name=project.project_name + ) + session.add(first_metric_credits_value) + await session.flush() + return first_metric_credits_value + + @classmethod + async def add_metric_credits( + cls, project: Project, last_metric_credits: MetricCredits, + credits_value: float, measurement: BaseMeasurement, session: AsyncSession + ): + metric_credits: MetricCredits = MetricCredits( + time=measurement.time, + used_credits=last_metric_credits.used_credits + credits_value, + granted_credits=project.granted_credits, + metric=last_metric_credits.metric, + project_name=project.project_name + ) + session.add(metric_credits) + await session.flush() + return metric_credits + + # handle own data end ############################### + ##################################################### + ##################################################### + + # general functions end ############################# + ##################################################### + + ##################################################### + # compute credits functions ######################### + + @classmethod + async def calculate_credits_with_two_measurements( + cls, current_measurement: BaseMeasurement, next_measurement: BaseMeasurement, metric: str + ) -> float: + if next_measurement.value <= current_measurement.value: + timescaledb_logger.debug( + f"Next measurement value {next_measurement} is lower or equal to current " + f"{current_measurement}, returning 0." + ) + return 0.0 + value_difference = next_measurement.value - current_measurement.value + if value_difference > 0: + return value_difference * config["METRICS_TO_BILL"][metric] + else: + return 0.0 + + # compute credits functions end ##################### + ##################################################### + +# @staticmethod +# def sanitize_parameter(parameter: str) -> str: +# """Sanitizes the provided parameter to prevent SQL Injection when querying with +# user provided content. +# +# :param parameter: Content to sanitize +# :return: Sanitized string +# """ +# # TODO: probably way too restrictive/wrong, but works for now, better fail than +# # SQL injection +# critical_chars = {"'", '"', "\\", ";", " ", ","} +# sanitized_param_chars: List[str] = [] +# for char in parameter: +# if char in critical_chars: +# sanitized_param_chars.append(f"\\{char}") +# else: +# sanitized_param_chars.append(char) +# sanitized_param = "".join(sanitized_param_chars) +# if sanitized_param != parameter: +# influxdb_logger.debug("Sanitized %s to %s", parameter, sanitized_param) +# return sanitized_param diff --git a/src/os_credits/db_client/model.py b/src/os_credits/db_client/model.py new file mode 100644 index 0000000..a5fdc3e --- /dev/null +++ b/src/os_credits/db_client/model.py @@ -0,0 +1,147 @@ +from sqlalchemy import Column, String, Boolean, event, DDL, func, BigInteger, ForeignKey, Float, text +from sqlalchemy.dialects.postgresql import TIMESTAMP +from sqlalchemy.ext.declarative import DeferredReflection +from sqlalchemy.orm import declarative_base + +Base = declarative_base() + + +class Credits(Base): + __tablename__ = "credits" + time = Column(TIMESTAMP(timezone=True), server_default=text('statement_timestamp()'), primary_key=True) + used_credits = Column(Float) + granted_credits = Column(Float) + project_name = Column(String, ForeignKey("project.project_name"), primary_key=True) + by_metric = Column(String, primary_key=True) + metric_time = Column(TIMESTAMP(timezone=True)) + + def __repr__(self): + return f"credits(time={self.time!r}, used_credits={self.used_credits!r}, " \ + f"granted_credits={self.granted_credits!r}, project_name={self.project_name!r}, " \ + f"by_metric={self.by_metric!r})" + + +event.listen( + Credits.__table__, + 'after_create', + DDL(f"SELECT create_hypertable('{Credits.__tablename__}', 'time');") +) + + +class MetricCredits(Base): + __tablename__ = "metric_credits" + time = Column(TIMESTAMP(timezone=True), server_default=text('statement_timestamp()'), primary_key=True) + used_credits = Column(Float) + granted_credits = Column(Float) + project_name = Column(String, ForeignKey("project.project_name"), primary_key=True) + metric = Column(String, primary_key=True) + + def __repr__(self): + return f"metric_credits(time={self.time!r}, used_credits={self.used_credits!r}, " \ + f"granted_credits={self.granted_credits!r}, project_name={self.project_name!r}, " \ + f"metric={self.metric!r})" + + +event.listen( + MetricCredits.__table__, + 'after_create', + DDL(f"SELECT create_hypertable('{MetricCredits.__tablename__}', 'time');") +) + + +class Project(Base): + __tablename__ = 'project' + project_name = Column(String, primary_key=True) + project_name_label_id = Column(BigInteger) + used_credits = Column(Float, default=0) + granted_credits = Column(Float, default=0) + half_limit_reached_send = Column(Boolean, default=False) + full_limit_reached_send = Column(Boolean, default=False) + + def __repr__(self): + return f"project(project_name={self.project_name!r}, project_name_label_id={self.project_name_label_id!r}, " \ + f"granted_credits={self.granted_credits!r}, " \ + f"half_limit_reached_send={self.half_limit_reached_send!r}, full_limit_reached_send={self.full_limit_reached_send!r})" + + def __hash__(self): + return hash(self.project_name) + + def __eq__(self, other): + return hasattr(other, "project_name") and self.project_name == other.project_name + + +class PromCatalogReflected(DeferredReflection): + """ + Baseclass for deferred promscale prom_catalog reflection. + By calling PromCatalogReflected.prepare(sync_engine instance from SQLAlchemy) all child classes will be reflected. + """ + __abstract__ = True + + +class Metric(PromCatalogReflected, Base): + """ + Reflected promscale prom_catalog.metric table. + Once reflected this class can be used in SQLAlchemy queries, e.g. select(Metric).where(...). + Used to get prom_metric. views. + """ + __tablename__ = "metric" + metric_name = Column(String) + + def __repr__(self): + return f"metric(metric_name={self.metric_name!r})" + + +class Label(PromCatalogReflected, Base): + """ + Reflected promscale prom_catalog.label table. + Once reflected this class can be used in SQLAlchemy queries, e.g. select(Label).where(Label.key == 'project_name'). + """ + __tablename__ = "label" + id = Column(BigInteger, primary_key=True) + key = Column(String) + value = Column(String) + + def __repr__(self): + return f"label(id={self.id!r}, key={self.key!r}, value={self.value!r})" + + +class PromMetricReflected(DeferredReflection): + """ + Baseclass for deferred promscale prom_metric reflection. + By calling PromMetricReflected.prepare(sync_engine instance from SQLAlchemy) all child classes will be reflected. + """ + __abstract__ = True + + +class BaseMeasurement: + """ + Baseclass for reflected promscale prom_metric. view. + """ + time = Column(TIMESTAMP(timezone=True), primary_key=True) + project_name_id = Column(BigInteger) + value = Column(Float) + + def __repr__(self): + return f"measurement(time={self.time!r}, project_name_id={self.project_name_id!r}, value={self.value!r})" + + +def make_measurement_class(table_name): + """ + Creates a measurement class. + Once instantiated and reflected this class be used in SQLAlchemy queries. + :param table_name: Name of the prom_metric view. + :return: A Measurement class. + """ + DynamicBase = declarative_base(class_registry=dict()) + + class Measurement(PromMetricReflected, DynamicBase, BaseMeasurement): + __tablename__ = table_name + + def __repr__(self): + return f"measurement(time={self.time!r}, project_name_id={self.project_name_id!r}, value={self.value!r})" + + @classmethod + def _type(cls): + return BaseMeasurement.__class__.__name__ + + return Measurement diff --git a/src/os_credits/db_client/tasks.py b/src/os_credits/db_client/tasks.py new file mode 100644 index 0000000..4c518ff --- /dev/null +++ b/src/os_credits/db_client/tasks.py @@ -0,0 +1,179 @@ +import asyncio +from asyncio import CancelledError, shield, Queue, Lock +from typing import cast, Dict + +from aiohttp.web import Application +from sqlalchemy.ext.asyncio import AsyncSession + +from src.os_credits.db_client.client import TimescaleDBManager +from src.os_credits.log import producer_logger, task_logger, TASK_ID +from src.os_credits.settings import config + + +def unique_identifier(project_name: str) -> str: + """Hashes the passed project name and returns an unique ID. + + Used to uniquely identify all log messages related to one specific project name. + Needed since multiple ones are processed in parallel to the logs are scattered. Used + as :ref:`Logging`. + + :param project_name: String to hash + :return: Unique ID consisting of 12 numbers + """ + # insert leading zeros if less numbers than 12 but don't use more + return format(abs(hash(project_name)), ">012")[:12] + + +async def put_projects_into_queue(app: Application) -> None: + """ + Worker putting labels from promscale into queue. + :param app: Application. + :return: None + """ + db_client: TimescaleDBManager = app["database_client"] + queue: Queue = app["task_queue"] + while True: + q_size = queue.qsize() + if q_size > 0: + producer_logger.warning(f"There are still {q_size} objects in queue. Sleeping for 60s and then retrying.") + await asyncio.sleep(60) + continue + producer_logger.info("Producer starting to put all projects into queue.") + async with db_client.async_session() as session: + labels_with_project_names_as_value = await db_client.fetch_all_labels_for_project_name_key(session) + await db_client.check_measurement_classes(session) + for label in labels_with_project_names_as_value: + producer_logger.debug("Processing label {0}.".format(label)) + if config["OS_CREDITS_PROJECT_WHITELIST"] and label[0].value not in config["OS_CREDITS_PROJECT_WHITELIST"]: + producer_logger.debug( + "Project {0} is not in the given whitelist. Not putting into queue.".format(label.value) + ) + else: + await queue.put(label) + producer_logger.debug("Queue size now {0}.".format(queue.qsize())) + producer_logger.info("Producer sleeping now for 5 minutes.") + await asyncio.sleep(5 * 60) + + +async def consumer_worker(name: str, app: Application) -> None: + task_queue: Queue = app["task_queue"] + db_client: TimescaleDBManager = app["database_client"] + group_locks = cast(Dict[str, Lock], app["group_locks"]) + while True: + async with db_client.async_session() as session: + try: + label = await task_queue.get() + label = label[0] + except CancelledError: + task_logger.info(f"Worker {name} was cancelled when waiting for new item.") + raise + try: + task_id = unique_identifier(label.value) + TASK_ID.set(task_id) + task_logger.info("Worker {0} starting task `{1}` for project {2}".format( + name, task_id, label.value + )) + # do not cancel a running task + await shield(process_project(label, db_client, session, group_locks)) + task_logger.info( + "Worker {0} finished task `{1}` successfully".format( + name, task_id + ) + ) + # necessary since the tasks must continue working despite any exceptions that + # occurred + except CancelledError: + raise + except Exception as e: + task_logger.exception( + f"Worker {name} exited task with unhandled exception: {e}, stacktrace " + f"attached", + ) + finally: + task_queue.task_done() + + +async def process_project( + label, db_client: TimescaleDBManager, session: AsyncSession, group_locks: Dict[str, Lock] +): + task_logger.debug("Going to process {0}".format(label.value)) + async with group_locks[label.value]: + await compute_credits_by_label(label, session, db_client) + await session.commit() + + +async def compute_credits_by_label( + label, session: AsyncSession, db_client: TimescaleDBManager +): + task_logger.debug(f"Updating credits for {label.value}.") + + project = await db_client.get_project_by_label(label, session) + if project is None: + project = await db_client.create_project_by_label(label, session) + else: + project = project[0] + session.add(project) + await db_client.get_granted_credits_for_project(project, session) + half_credits = project.granted_credits / 2 + measurements = await db_client.get_measurement_classes() + for measurement_name, measurement_class in measurements.items(): + last_metric_credits = await db_client.get_latest_metric_credits( + project=project, metric=measurement_name, session=session + ) + if last_metric_credits is None: + last_metric_credits = await db_client.initialize_first_metric_credits_entry( + project=project, metric=measurement_name, session=session + ) + else: + last_metric_credits = last_metric_credits[0] + + last_credits_entry = await db_client.get_latest_credits( + project_name=project.project_name, session=session + ) + if last_credits_entry is None: + last_credits_entry = await db_client.initialize_first_credits_entry( + project=project, metric_credits=last_metric_credits, session=session + ) + + measurments_since = await db_client.fetch_measurements_since_inclusive_last( + project, last_metric_credits, session + ) + if len(measurments_since) < 2: + task_logger.debug("No new metric rows for {0} for {1}.".format( + project, measurement_name + )) + continue + metric_num = 0 + while metric_num < len(measurments_since) - 1: + current_measurement = measurments_since[metric_num] + next_measurement = measurments_since[metric_num + 1] + task_logger.debug( + "Processing current: {0} and next: {1} for {2} for {3}.".format( + current_measurement, next_measurement, project, measurement_name + ) + ) + credits_value: float = await db_client.calculate_credits_with_two_measurements( + current_measurement, next_measurement, measurement_name + ) + if credits_value == 0.0: + metric_num += 1 + continue + task_logger.debug( + "Got a credits value of {0} for {1} and {2}.".format( + credits_value, project, measurement_name + ) + ) + last_metric_credits = await db_client.add_metric_credits( + project=project, last_metric_credits=last_metric_credits, credits_value=credits_value, measurement=next_measurement, + session=session + ) + last_credits_entry = await db_client.add_credits( + project=project, metric_credits=last_metric_credits, session=session, + credits_value=(last_credits_entry.used_credits + credits_value) + ) + if project.granted_credits > 0 and last_credits_entry.used_credits >= half_credits and not project.half_limit_reached_send: + await db_client.inform_half_limit_reached(last_credits_entry, project, session) + task_logger.debug("Last metric now: {0}".format(last_metric_credits)) + task_logger.debug("Last credits now: {0}".format(last_credits_entry)) + metric_num += 1 + task_logger.debug("Updated credits for {0} with {1}.".format(project, measurement_name)) diff --git a/src/os_credits/exceptions.py b/src/os_credits/exceptions.py deleted file mode 100644 index 746e7c1..0000000 --- a/src/os_credits/exceptions.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Exceptions which might be raised during the communication with Perun or processing data -received by it. -""" - - -class CreditsError(Exception): - "Base Exception for any exceptions defined by this module" - pass - - -class MissingInfluxDatabase(CreditsError): - "Raised if a required database inside the InfluxDB does not exist" - pass - - -class MissingConfigError(CreditsError): - "Raised if a non-set config value without default value is requested" - pass - - -class MeasurementError(CreditsError): - "Raised if the given measurements cannot be used to calculate credits usage" - pass - - -class CalculationResultError(CreditsError): - "Raised if the result of credits calculation does not meet constraints" - pass - - -class MissingTemplateError(CreditsError): - "Raised if a Notification class does not define a template" - pass - - -class BrokenTemplateError(CreditsError): - "Raised if a template of a Notification class contains errors" - pass - - -class MissingToError(CreditsError): - "Raised if a Notification class does not defined any ``To`` recipients." diff --git a/src/os_credits/influx/__init__.py b/src/os_credits/influx/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/os_credits/influx/client.py b/src/os_credits/influx/client.py deleted file mode 100644 index 6da27ed..0000000 --- a/src/os_credits/influx/client.py +++ /dev/null @@ -1,312 +0,0 @@ -"""This module contains our :class:`InfluxDBClient` which is a subclass of the one -provided by the ``aioinflux`` package. - -Ours does not rewrite any functionality, it only contains additional functions to ease -writing and querying our custom data models which are all based on -:class:`~os_credits.influx.model.InfluxDBPoint`. - -When querying :class:`~collections.abc.AsyncGenerator` are used whenever possible, to -prevent excessive loading data when only e.g. the last 5 entries are needed. -""" -from __future__ import annotations - -from datetime import datetime -from itertools import chain -from textwrap import shorten -from typing import AsyncGenerator -from typing import Dict -from typing import Iterable -from typing import List -from typing import Optional -from typing import Type -from typing import Union -from warnings import catch_warnings -from warnings import filterwarnings - -from os_credits.credits.base_models import UsageMeasurement -from os_credits.credits.models import BillingHistory -from os_credits.log import influxdb_logger -from os_credits.settings import config - -from .exceptions import InfluxDBError -from .model import PT - -# Suppress warning about missing support for DataFrames since pandas is not installed -# Would increase the size of the application too much -with catch_warnings(): - filterwarnings("ignore", category=UserWarning) - from aioinflux import iterpoints - from aioinflux.client import InfluxDBClient as _InfluxDBClient - from aioinflux.client import InfluxDBError as _InfluxDBError - -INFLUX_QUERY_DATE_FORMAT = "%Y-%m-%d %H:%M:%S.%f" - -_DEFINITELY_PAST = datetime.fromtimestamp(0) - - -class InfluxDBClient(_InfluxDBClient): - def __init__(self, loop=None) -> None: - super().__init__( - host=config["INFLUXDB_HOST"], - port=config["INFLUXDB_PORT"], - username=config["INFLUXDB_USER"], - password=config["INFLUXDB_USER_PASSWORD"], - database=config["INFLUXDB_DB"], - loop=loop, - output="json", - ) - - async def delete_credits_left_measurements(self) -> Dict: - deleted_from_list = {"deleted_from": [], "not_deleted_from": []} - show_measurements_template = "show measurements" - credits_history = config["CREDITS_HISTORY_DB"] - all_measurements = await self.query( - show_measurements_template, - db=credits_history) - get_time_template = "SELECT first(credits_used) FROM {measurement} " \ - "where credits_used >= 0" - delete_template = "DELETE FROM {measurement} where time < {time}" - for measurement_point in iterpoints(all_measurements, - lambda *x, - meta: dict(zip(meta['columns'], x)) - ): - try: - measurement_name = measurement_point["name"] - time = await self.query( - get_time_template.format(measurement=measurement_name), - db=credits_history) - for time_point in iterpoints( - time, - lambda *x, - meta: dict(zip(meta['columns'], x)) - ): - await self.query( - delete_template.format( - measurement=measurement_name, - time=time_point["time"] - ) - ) - deleted_from_list["deleted_from"].append(measurement_name) - except (ValueError, KeyError, Exception) as e: - influxdb_logger.exception(e) - deleted_from_list["not_deleted_from"].append(measurement_point) - return deleted_from_list - - async def delete_mb_and_vcpu_measurements( - self, - project_name_to_delete, - since_date - ) -> Dict: - project_info_mb_template = f"SELECT LAST(value)," \ - f"project_name, location_id " \ - f"FROM project_mb_usage " \ - f"where project_name='{project_name_to_delete}';" - project_info_vcpu_template = f"SELECT LAST(value)," \ - f"project_name, location_id " \ - f"FROM project_vcpu_usage " \ - f"where project_name='{project_name_to_delete}';" - mb_info = await self.query(project_info_mb_template) - vcpu_info = await self.query(project_info_vcpu_template) - returned_timestamps = {} - for i in iterpoints(mb_info, lambda *x, meta: dict(zip(meta['columns'], x))): - returned_timestamps["project_name"] = i["project_name"] - returned_timestamps["location_id"] = i["location_id"] - for i in iterpoints(vcpu_info, lambda *x, meta: dict(zip(meta['columns'], x))): - returned_timestamps["project_name"] = i["project_name"] - returned_timestamps["location_id"] = i["location_id"] - if not returned_timestamps: - return returned_timestamps - - delete_mb_template = f"delete from project_mb_usage " \ - f"where project_name='{project_name_to_delete}' " \ - f"and time > {since_date}000000000;" - delete_vcpu_template = f"delete from project_vcpu_usage " \ - f"where project_name='{project_name_to_delete}' " \ - f"and time > {since_date}000000000;" - await self.query(delete_mb_template) - await self.query(delete_vcpu_template) - - last_mb_timestamp_template = f"SELECT LAST(value), time, " \ - f"project_name, location_id " \ - f"FROM project_mb_usage " \ - f"where project_name='{project_name_to_delete}';" - last_vcpu_timestamp_template = f"SELECT LAST(value), time, " \ - f"project_name, location_id " \ - f"FROM project_vcpu_usage " \ - f"where project_name='{project_name_to_delete}';" - last_mb = await self.query(last_mb_timestamp_template) - last_vcpu = await self.query(last_vcpu_timestamp_template) - for i in iterpoints(last_mb, lambda *x, meta: dict(zip(meta['columns'], x))): - returned_timestamps["last_mb"] = {} - returned_timestamps["last_mb"]["time"] = i["time"]/1e9 - for i in iterpoints(last_vcpu, lambda *x, meta: dict(zip(meta['columns'], x))): - returned_timestamps["last_vcpu"] = {} - returned_timestamps["last_vcpu"]["time"] = i["time"]/1e9 - - return returned_timestamps - - async def ensure_history_db_exists(self) -> bool: - """Checks whether the required database for credits history exists. - - The name of the database is stored inside the - :class:`~os_credits.config.Config`. - - :return: Whether the database exists - """ - r = await self.show_databases() - return config["CREDITS_HISTORY_DB"] in chain.from_iterable( - r["results"][0]["series"][0]["values"] - ) - - @staticmethod - def sanitize_parameter(parameter: str) -> str: - """Sanitizes the provided parameter to prevent SQL Injection when querying with - user provided content. - - :param parameter: Content to sanitize - :return: Sanitized string - """ - # TODO: probably way too restrictive/wrong, but works for now, better fail than - # SQL injection - critical_chars = {"'", '"', "\\", ";", " ", ","} - sanitized_param_chars: List[str] = [] - for char in parameter: - if char in critical_chars: - sanitized_param_chars.append(f"\\{char}") - else: - sanitized_param_chars.append(char) - sanitized_param = "".join(sanitized_param_chars) - if sanitized_param != parameter: - influxdb_logger.debug("Sanitized %s to %s", parameter, sanitized_param) - return sanitized_param - - async def query_points( - self, - measurement: str, - point_class: Type[PT], - db: str, - query_constraints: Optional[List[str]] = None, - ) -> AsyncGenerator[PT, None]: - """Asynchronously yields all queried points which in turn are streamed in chunks - from the InfluxDB, where the points are sorted by their timestamp descending. - - :param measurement: Which table to run the query against. - :param point_class: Subclass of ``InfluxDBPoint`` whose ``from_iterpoint`` - method will be used to deserialize the returned points. - :param db: Which database to run the query against. - :param query_constraints: WHERE-constraints to add to the query, will be AND-ed - if more than one is given. - :return: Instances of ``point_class`` ordered by their timestamp descending. - """ - query_template = """\ - SELECT * - FROM {measurement} - {constraints} - ORDER BY time DESC - """ - constraints = "" - if query_constraints and len(query_constraints) > 0: - constraints = f"WHERE {' AND '.join(query_constraints)}" - query = query_template.format(constraints=constraints, measurement=measurement) - influxdb_logger.debug( - "Sending query `%s` to InfluxDB", - shorten(query.replace("\n", ""), len(query)), - ) - result = await self.query(query, chunked=True, db=db) - try: - # If an error occurs it is raised here due to ``chunked=True`` - async for chunk in result: - for point in iterpoints(chunk, point_class.from_iterpoint): - yield point - except _InfluxDBError as e: - influxdb_logger.exception("Exception when querying InfluxDB") - raise InfluxDBError(*e.args) - - async def query_points_since( - self, - measurement: str, - point_class: Type[PT], - db: str, - since: datetime = _DEFINITELY_PAST, - query_constraints: Optional[List[str]] = None, - ) -> AsyncGenerator[PT, None]: - """Wrapper around ``query_points`` to emulate ``WHERE time >= since`` constraint - of InfluxDB. - - Necessary since it returns wrong results (for me), fixing definitely - appreciated. - - :param since: Only return Points whose timestamp is >=, i.e. which are not older - """ - async for point in self.query_points( - measurement=measurement, - point_class=point_class, - db=db, - query_constraints=query_constraints, - ): - if point.timestamp >= since: - yield point - else: - return - - async def previous_measurements( - self, measurement: UsageMeasurement, since: datetime = _DEFINITELY_PAST - ) -> Dict[datetime, UsageMeasurement]: - """Return previous measurements for the same project and metric as the provided - measurement. - - :param measurement: Must be initialized since its project and metric values are - required. - :param since: Passed through to ``query_points_since``. - :return: Dictionary of measurements accessible by their timestamp which are - sorted descending. - """ - sanitized_project_name = InfluxDBClient.sanitize_parameter( - measurement.project_name - ) - previous_measurements = self.query_points_since( - measurement=measurement.metric.name, - point_class=type(measurement), - db=config["INFLUXDB_DB"], - since=since, - query_constraints=[f"project_name = '{sanitized_project_name}'"], - ) - return {point.timestamp: point async for point in previous_measurements} - - async def write_billing_history( - self, point: Union[BillingHistory, Iterable[BillingHistory]] - ) -> None: - await self.write(point, db=config["CREDITS_HISTORY_DB"]) - - async def project_has_history(self, project_name: str) -> bool: - """Checks whether a project has any history stored or not - - :param project_name: Project name to check - :return: History present or not - """ - r = await self.show_series(db=config["CREDITS_HISTORY_DB"]) - for project_measurement in chain.from_iterable( - r["results"][0]["series"][0]["values"] - ): - if project_measurement.startswith(project_name): - return True - - return False - - async def query_billing_history( - self, project_name: str, since: datetime = _DEFINITELY_PAST - ) -> AsyncGenerator[BillingHistory, None]: - """Return the billing history of the specified project. - - :param project_name: Project whose history should be queried. - :param since: Passed through to ``query_points_since``. - :return: Asynchronously yielded instances of ``BillingHistory`` sorted by their - timestamp descending. - """ - sanitized_project_name = InfluxDBClient.sanitize_parameter(project_name) - return self.query_points_since( - measurement=sanitized_project_name, - db=config["CREDITS_HISTORY_DB"], - point_class=BillingHistory, - since=since, - ) diff --git a/src/os_credits/influx/exceptions.py b/src/os_credits/influx/exceptions.py deleted file mode 100644 index a6614dc..0000000 --- a/src/os_credits/influx/exceptions.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Contains exceptions to wrap the one used by the ``aioinflux`` package. -""" - - -class InfluxDBError(Exception): - "Wrapper around :exc:`aioinflux.client.InfluxDBError` in our functions." - pass diff --git a/src/os_credits/influx/helper.py b/src/os_credits/influx/helper.py deleted file mode 100644 index f23620f..0000000 --- a/src/os_credits/influx/helper.py +++ /dev/null @@ -1,205 +0,0 @@ -"""We use helper classes and functions to serialize and deserialize python data types -into basic ones support by *InfluxDB*. The default ones are implemented in -:mod:`os_credits.influx.helper` module. -For example if we'd like to store :class:`~fractions.Fraction` one basic way could be -the following: - ->>> from os_credits.influx.helper import InfluxSerializer, serialize, deserialize ->>> from fractions import Fraction ->>> class FractionSerializer(InfluxSerializer, types=["Fraction"]): -... @staticmethod -... def serialize(value: Fraction) -> str: -... return f"{value.numerator}/{value.denominator}" -... @staticmethod -... def deserialize(value: str) -> Fraction: -... numerator_str, denominator_str = value.split('/') -... return Fraction(int(numerator_str), int(denominator_str)) ->>> f = Fraction(1, 4) ->>> serialize(f) -'1/4' ->>> deserialize('1/4', Fraction) -Fraction(1, 4) - -See :class:`~os_credits.credits.base_models.CreditsSerializer` for another example. The -high level functions :func:`~os_credits.influx.helper.serialize` and -:func:`~os_credits.influx.helper.deserialize` determine which serializer is responsible -for the provided value. - -""" -from __future__ import annotations - -from dataclasses import Field -from datetime import datetime -from decimal import Decimal -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import Type -from typing import Union - -InfluxDataTypes = Union[bool, float, str, int] -_registered_serializers: Dict[str, Type[InfluxSerializer]] = {} - - -def serialize( - value: Any, field_or_type: Optional[Union[Field, Type[Any]]] = None -) -> InfluxDataTypes: - """Determines the correct subclass of :class:`InfluxSerializer` with the help of - :attr:`field_or_type` and calls its :func:`encode` method to convert the given value - into a datatype natively supported by *InfluxDB*. - - One usage is the conversion of python datetime objects into timestamps with - (simulated) nanosecond precision. This methods are automatically invoked by - :class:`~os_credits.influx.model.InfluxDBPoint`. - - :param value: Value to encode. - :param field_or_type: Either a :class:`~dataclasses.Field` object of which we - require the `type` information, which should be a string since we use ``from - __future__ import annotations`` (available for Python3.7+) but the older case of - classes is also supported. Alternatively the raw type to use for encoding. If - not specified ``type(value)`` is used instead. - :return: Encoded value - """ - # TODO: This prevents `None` from being specified as type - if field_or_type is None: - type_name = type(value).__name__ - else: - if isinstance(field_or_type, Field): - if not isinstance(field_or_type.type, str): - type_name = field_or_type.type.__name__ - else: - type_name = field_or_type.type - else: - type_name = field_or_type.__name__ - try: - serializer = _registered_serializers[type_name] - except KeyError: - raise TypeError(f"No converter registered for type {type_name}.") - return serializer.serialize(value) - - -def deserialize(value: Any, field_or_type: Union[Field, Type[Any]]) -> Any: - """Determines the correct subclass of :class:`InfluxSerializer` with the help of - :attr:`field_or_type` and calls its :func:`decode` method to convert the given value - into the python datatype indicated by ``field.type``. - - If we are decoding an *InfluxDB Line* :attr:`value` will always be a :class:`str` - but if we are called from - :func:`~os_credits.influx.model.InfluxDBPoint.from_iterpoint` it can be any value of - :attr:`InfluxDataTypes`. Additional decoding allows for storing more abstract data - types inside the *InfluxDB* such as datetime objects. - - :param value: Value to encode. - :param field_or_type: Either a :class:`~dataclasses.Field` object of which we - require the `type` information, which should be a string since declare ``from - __future__ import annotations`` but the older case of classes is also supported. - Alternatively the raw type to use for encoding. - :return: Encoded value - """ - if isinstance(field_or_type, Field): - if not isinstance(field_or_type.type, str): - type_name = field_or_type.type.__name__ - else: - type_name = field_or_type.type - else: - type_name = field_or_type.__name__ - try: - serializer = _registered_serializers[type_name] - except KeyError: - raise TypeError(f"No converter registered for type {type_name}.") - return serializer.deserialize(value) - - -class InfluxSerializer: - """Base class for all serializers. Subclass in your own application to support your - custom data types. """ - - def __init_subclass__(cls, types: List[str]): - """Used to register new serializers with their supported type. - - :param types: Types as reported by :func:`dataclasses.fields` for which ``cls`` - provides decode and encode support. - """ - for type_ in types: - _registered_serializers[type_] = cls - - @staticmethod - def serialize(value: Any) -> InfluxDataTypes: - """Encodes the given value to a type which is supported natively by InfluxDB. - """ - raise NotImplementedError("Must be implemented by subclass") - - @staticmethod - def deserialize(value: InfluxDataTypes) -> Any: - """Decodes a value stored inside the InfluxDB or from the InfluxDB Line Protocol - to its proper python type. - """ - return value - - -class _StringSerializer(InfluxSerializer, types=["str"]): - @staticmethod - def serialize(value) -> str: - return str(value) - - -class _IntSerializer(InfluxSerializer, types=["int"]): - @staticmethod - def serialize(value: int) -> int: - return value - - @staticmethod - def deserialize(value: InfluxDataTypes) -> int: - return int(value) - - -class _FloatSerializer(InfluxSerializer, types=["float"]): - @staticmethod - def serialize(value: float) -> float: - return value - - @staticmethod - def deserialize(value: InfluxDataTypes) -> float: - return float(value) - - -class _DecimalSerializer(InfluxSerializer, types=["Decimal"]): - @staticmethod - def serialize(value: Decimal) -> float: - return float(value) - - @staticmethod - def deserialize(value: InfluxDataTypes) -> Decimal: - return Decimal(value) - - -class _BoolSerializer(InfluxSerializer, types=["bool"]): - @staticmethod - def serialize(value: bool) -> bool: - return value - - @staticmethod - def deserialize(value: InfluxDataTypes) -> bool: - """InfluxDB knows multiple ways to express a boolean value""" - # also including True and False since ``value`` will already be a bool when - # using the input from ``iterpoints`` - true_values = {"t", "T", "true", "True", "TRUE", True} - false_values = {"f", "F", "false", "False", "FALSE", False} - if value in true_values: - return True - elif value in false_values: - return False - else: - raise ValueError("Unknown bool representation") - - -class _DatetimeSerializer(InfluxSerializer, types=["datetime"]): - @staticmethod - def serialize(value: datetime) -> int: - return int(value.timestamp() * 1e9) - - @staticmethod - def deserialize(value: InfluxDataTypes) -> datetime: - # does lose some preciseness unfortunately, but only nanoseconds - return datetime.fromtimestamp(int(value) / 1e9) diff --git a/src/os_credits/influx/model.py b/src/os_credits/influx/model.py deleted file mode 100644 index 4ea1b84..0000000 --- a/src/os_credits/influx/model.py +++ /dev/null @@ -1,202 +0,0 @@ -from __future__ import annotations - -from dataclasses import MISSING -from dataclasses import dataclass -from dataclasses import fields -from datetime import datetime -from typing import Any -from typing import AnyStr -from typing import Dict -from typing import List -from typing import Type -from typing import TypeVar - -from os_credits.log import internal_logger - -from .helper import deserialize -from .helper import serialize - -INFLUX_QUERY_DATE_FORMAT = "%Y-%m-%d %H:%M:%S.%f" - -_DEFINITELY_PAST = datetime.fromtimestamp(0) - -# PointType -PT = TypeVar("PT", bound="InfluxDBPoint") - - -@dataclass(frozen=True) -class InfluxDBPoint: - """Base class of all data models whose content is written or read from the InfluxDB. - - To define a data model as shown in the official InfluxDB Line Tutorial extend in the - following way - - >>> from dataclasses import dataclass, field - >>> from os_credits.influx.model import InfluxDBPoint - >>> @dataclass(frozen=True) - ... class Weather(InfluxDBPoint): - ... temperature: int - ... location: str = field(metadata={'tag': True}) - ... static_id: int = 5 - >>> from datetime import datetime - >>> timestamp = datetime(2016, 6, 13, 19, 43, 50, 100400) - >>> # the first two parameters are defined inside ``InfluxDBPoint`` - >>> weather = Weather('weather', timestamp, 82, 'us-midwest') - >>> print(weather.to_lineprotocol()) - b'weather,location=us-midwest temperature=82 1465839830100399872' - >>> Weather.from_lineprotocol(weather.to_lineprotocol()) == weather - True - - We are using the ``metadata`` field of :class:`~dataclasses.dataclass` to indicate - whether to store a date as field or as tag. The difference between them is that tags - are indexed by InfluxDB. Attributes with a default value are currently ignored, if a - change to this should be necessary, whether to skip an attribute or not should be - indicated via the ``metadata``. - - All subclasses must also be frozen, since this base class is. Use the - :func:`dataclasses.replace` method instead. Allows us to use the instances as - dictionary keys. - - Unfortunately *InfluxDB* does store all timestamps as nanoseconds which are not - natively supported by python. We are therefore losing some precision but this is - negligible since the timestamps of *Prometheus* are only milliseconds. - """ - - measurement: str - """The name of this measurement""" - timestamp: datetime - - @classmethod - def from_iterpoint(cls: Type[PT], *values: List[Any], meta: Dict[str, str]) -> PT: - """Only intended to be passed to the ``iterpoints`` method of ``aioinflux`` to - parse the points and construct valid InfluxDBPoint instances. See its - documentation for a description of the contents of ``values`` and ``meta``. - - The metadata of dataclass attributes are used to parse and convert the necessary - information, unknown values and tags are dropped. - - :param cls: Subclass of :class:`InfluxDBPoint` on which this method is called. - Instances of this class will be tried to be constructed given the returned - data from the InfluxDB and returned. - """ - measurement_name = meta["name"] - combined_dict = dict(zip(meta["columns"], values)) - args: Dict[str, Any] = { - "measurement": measurement_name, - "timestamp": deserialize(combined_dict["time"], datetime), - } - for f in fields(cls): - if f.default is not MISSING: - continue - # values of this fields are already known - if f.name in args: - continue - try: - args[f.name] = deserialize(combined_dict[f.name], f) - except Exception as e: - internal_logger.info(str(e) + "values were: %s, %s", combined_dict[f.name], f) - new_point = cls(**args) - internal_logger.debug("Constructed %s", new_point) - return new_point - - @classmethod - def from_lineprotocol(cls: Type[PT], influx_line_: AnyStr) -> PT: - """ - Creates a point from an InfluxDB Line, see - https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial/ - - Deliberate usage of ``cls`` to allow and support potential subclassing. If the - line contains more information than defined by ``cls`` the rest is simply - ignored. - - >>> from os_credits.influx.model import InfluxDBPoint - >>> line = b'weather,location=us-midwest temperature=82 1465839830100399872' - >>> InfluxDBPoint.from_lineprotocol(line) - InfluxDBPoint(measurement='weather', timestamp=datetime.datetime(2016, 6, 13, 19, 43, 50, 100400)) - - :param cls: Subclass on which this method is called. Instances of this class - will be the return type. - :param influx_line_: Influx Line to parse, either ``string`` or ``bytes``. - :return: Instances of `cls`. - :raises KeyError: Attribute of ``cls`` without default value not present in line - """ - if isinstance(influx_line_, bytes): - influx_line = influx_line_.decode() - else: - influx_line = influx_line_ - internal_logger.debug("Converting InfluxDB Line `%s`", influx_line) - measurement_and_tag, field_set, timestamp_str = influx_line.strip().split() - measurement_name, tag_set = measurement_and_tag.split(",", 1) - tag_field_dict: Dict[str, str] = {} - for tag_pair in tag_set.split(","): - tag_name, tag_value = tag_pair.split("=", 1) - tag_field_dict.update({tag_name: tag_value}) - for field_pair in field_set.split(","): - field_name, field_value = field_pair.split("=", 1) - tag_field_dict.update({field_name: field_value}) - # we know how to deserialize those - args: Dict[str, Any] = { - "measurement": measurement_name, - "timestamp": deserialize(timestamp_str, datetime), - } - for f in fields(cls): - # currently not serialized, see class documentation - if f.default is not MISSING: - continue - # values of this fields are already known - if f.name in args: - continue - is_tag = False - if f.metadata and f.metadata.get("tag", False): - is_tag = True - if f.name not in tag_field_dict: - raise KeyError( - f"InfluxDB Line does not contain {'tag' if is_tag else 'field'} " - "`{f.name}`" - ) - value = tag_field_dict[f.name] - # string field values are quoted, strip them - if not is_tag and isinstance(value, str): - value = value.strip('"') - args[f.name] = deserialize(value, f) - new_point = cls(**args) - internal_logger.debug("Constructed %s", new_point) - return new_point - - def to_lineprotocol(self) -> bytes: - """Serializes this (subclass of) :class:`InfluxDBPoint` to its representation in - Influx Line Protocol. - - Not called directly by our code but by ``aioinflux``. Whenever an object should - be stored inside an InfluxDB and this object defines a ``to_lineprotocol`` - method it is used for serialization. Duck-typing for the win! - - :return: Serialization in Influx Line Protocol. - """ - tag_dict: Dict[str, str] = {} - field_dict: Dict[str, str] = {} - measurement = self.measurement - timestamp = format(serialize(self.timestamp), ".0f") - for f in fields(self): - # we know how to serialize those - if f.name in {"measurement", "timestamp"}: - continue - # currently not serialized, see class documentation - if f.default is not MISSING: - continue - value = getattr(self, f.name) - if f.metadata and f.metadata.get("tag", False): - tag_dict[f.name] = str(serialize(value, f)) - else: - component_value = serialize(value, f) - # string field values must be quoted - if isinstance(component_value, str): - field_dict[f.name] = str(serialize(f'"{component_value}"', f)) - else: - field_dict[f.name] = str(serialize(component_value, f)) - tag_str = ",".join(f"{key}={value}" for key, value in tag_dict.items()) - field_str = ",".join(f"{key}={value}" for key, value in field_dict.items()) - influx_line = " ".join( - [",".join([measurement, tag_str]), field_str, str(timestamp)] - ) - return influx_line.encode() diff --git a/src/os_credits/log.py b/src/os_credits/log.py index b9884da..39262b1 100644 --- a/src/os_credits/log.py +++ b/src/os_credits/log.py @@ -7,7 +7,7 @@ TASK_ID: ContextVar[str] = ContextVar("TASK_ID", default="") """For every task context this holds the ID generated by -:func:`~os_credits.credits.tasks.unique_identifier`. In conjunction with +:func:`~os_credits.cb_client.tasks.unique_identifier`. In conjunction with :class:`TaskIdFilter` this allows us to prefix all logging calls with the ID of the task's context. With this approach the logging calls **do not** have to pass their task's ID to the call (would be forgotten in most cases expectedly). @@ -44,15 +44,19 @@ def filter(self, record: LogRecord) -> bool: """Logs all the communication with *Perun*, should only be used for debugging purposes since it logs **all** exchanged data. """ -influxdb_logger = getLogger("os_credits.influxdb") -"""Logs all the communication with the *InfluxDB*. +timescaledb_logger = getLogger("os_credits.timescaledb") +"""Logs all the communication with the *timescaledb*. """ +producer_logger = getLogger("os_credits.producer") +views_logger = getLogger("os_credits.views") DEFAULT_LOG_LEVEL = { "os_credits.tasks": "INFO", "os_credits.internal": "INFO", "os_credits.requests": "INFO", - "os_credits.influxdb": "INFO", + "os_credits.timescaledb": "INFO", + "os_credits.producer": "INFO", + "os_credits.views": "INFO" } """Default logging level of all loggers. @@ -64,13 +68,13 @@ def filter(self, record: LogRecord) -> bool: "disable_existing_loggers": False, "formatters": { "task_handler": { - "format": "[%(task_id)s] %(levelname)-8s %(asctime)s: %(message)s" + "format": "[%(levelname)s] %(asctime)s %(name)s %(funcName)s:%(lineno)d: [%(task_id)s] %(message)s" }, "simple_handler": { - "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s" + "format": "[%(levelname)s] %(asctime)s %(name)s %(funcName)s:%(lineno)d: %(message)s" }, }, - "filters": {"task_id_filter": {"()": "os_credits.log.TaskIdFilter"}}, + "filters": {"task_id_filter": {"()": "src.os_credits.log.TaskIdFilter"}}, "handlers": { "with_task_id": { "class": "logging.StreamHandler", @@ -93,18 +97,23 @@ def filter(self, record: LogRecord) -> bool: }, "os_credits.internal": { "level": DEFAULT_LOG_LEVEL["os_credits.internal"], - "handlers": ["with_task_id"], - "filters": ["task_id_filter"], + "handlers": ["simple"], }, "os_credits.requests": { "level": DEFAULT_LOG_LEVEL["os_credits.requests"], - "handlers": ["with_task_id"], - "filters": ["task_id_filter"], + "handlers": ["simple"], }, - "os_credits.influxdb": { - "level": DEFAULT_LOG_LEVEL["os_credits.influxdb"], - "handlers": ["with_task_id"], - "filters": ["task_id_filter"], + "os_credits.timescaledb": { + "level": DEFAULT_LOG_LEVEL["os_credits.timescaledb"], + "handlers": ["simple"], + }, + "os_credits.producer": { + "level": DEFAULT_LOG_LEVEL["os_credits.producer"], + "handlers": ["simple"], + }, + "os_credits.views": { + "level": DEFAULT_LOG_LEVEL["os_credits.views"], + "handlers": ["simple"], }, }, } diff --git a/src/os_credits/main.py b/src/os_credits/main.py index 7d3e1f8..16d41d4 100644 --- a/src/os_credits/main.py +++ b/src/os_credits/main.py @@ -1,79 +1,23 @@ -from __future__ import annotations - -from asyncio import Lock -from asyncio import Queue +import asyncio +from asyncio import Queue, Lock from collections import defaultdict from datetime import datetime from logging.config import dictConfig from pathlib import Path from pprint import pformat -from typing import Optional - -from aiohttp import BasicAuth -from aiohttp import ClientSession from aiohttp import web from aiohttp_jinja2 import setup from aiohttp_swagger import setup_swagger from jinja2 import FileSystemLoader -from prometheus_async import aio - -from os_credits.exceptions import MissingInfluxDatabase -from os_credits.influx.client import InfluxDBClient -from os_credits.log import internal_logger -from os_credits.perun.requests import client_session -from os_credits.prometheus_metrics import projects_processed_counter -from os_credits.prometheus_metrics import tasks_queued_gauge -from os_credits.views import application_stats, \ - delete_mb_and_vcpu_since, \ - delete_credits_left -from os_credits.views import costs_per_hour -from os_credits.views import credits_history -from os_credits.views import credits_history_api -from os_credits.views import get_metrics -from os_credits.views import influxdb_write -from os_credits.views import ping -from os_credits.views import update_logging_config -from os_credits.worker_helper import stop_worker, create_worker +from src.os_credits.worker_helper import create_consumer_worker, stop_consumer_worker, create_producer +from src.os_credits.db_client.client import TimescaleDBManager +from src.os_credits.log import internal_logger +from src.os_credits.views import ping, credits_history_api, costs_per_hour, get_metrics, get_current_credits APP_ROOT = Path(__file__).parent -async def create_client_session(app: web.Application) -> None: - client_session.set( - ClientSession( - auth=BasicAuth( - app["config"]["OS_CREDITS_PERUN_LOGIN"], - app["config"]["OS_CREDITS_PERUN_PASSWORD"], - ) - ) - ) - - -async def setup_prometheus_metrics(app: web.Application) -> None: - tasks_queued_gauge.set_function(lambda: app["task_queue"].qsize()) - - -async def close_client_sessions(app: web.Application) -> None: - try: - await client_session.get().close() - await app["influx_client"].close() - except LookupError: - # no session: no need to close a session - pass - - -def create_new_group_lock() -> Lock: - """Creates a new instance of our :ref:`Group Locks`. - - :return: New lock - """ - projects_processed_counter.inc() - return Lock() - - -async def create_app( - _existing_influxdb_client: Optional[InfluxDBClient] = None -) -> web.Application: +async def create_app() -> web.Application: """Entry point of the whole service. #. Setup the logging config to be able to log as much as possible, see @@ -101,13 +45,12 @@ async def create_app( - :func:`stop_worker` - :func:`close_client_session` - :param _existing_influxdb_client: Only used when testing the code :return: Created `aiohttp `_ Application instance. """ # imported inside the function to allow pytest to set environment variables and have # them applied - from os_credits.settings import config - from os_credits.log import DEFAULT_LOGGING_CONFIG + from src.os_credits.settings import config + from src.os_credits.log import DEFAULT_LOGGING_CONFIG dictConfig(DEFAULT_LOGGING_CONFIG) internal_logger.info("Applied default logging config") @@ -115,8 +58,6 @@ async def create_app( app = web.Application() app.add_routes( [ - web.get("/delete", delete_mb_and_vcpu_since), - web.get("/delete_credits_left", delete_credits_left), web.get( "/api/credits_history/{project_name}", credits_history_api, @@ -125,45 +66,47 @@ async def create_app( web.get("/api/metrics", get_metrics, name="get_metrics"), web.post("/api/costs_per_hour", costs_per_hour, name="costs_per_hour"), web.get( - "/credits_history/{project_name}", - credits_history, - name="credits_history", + "/api/current_credits/{project_name}", + get_current_credits, + name="get_current_credits", ), + # web.get( + # "/credits_history/{project_name}", + # credits_history, + # name="credits_history", + # ), # not naming this route since it also used as health check by Docker web.get("/ping", ping, name="ping"), - web.get("/stats", application_stats), - # not naming this route since the endpoint is defined by InfluxDB and - # therefore fixed - web.post("/write", influxdb_write), - web.post("/logconfig", update_logging_config), - web.get("/metrics", aio.web.server_stats, name="metrics"), - web.static("/static", APP_ROOT / "static"), + # web.get("/stats", application_stats), + # web.post("/logconfig", update_logging_config), + # web.get("/metrics", aio.web.server_stats, name="metrics"), + # web.static("/static", APP_ROOT / "static"), ] ) app.update( name="os-credits", - influx_client=_existing_influxdb_client or InfluxDBClient(), + database_client=TimescaleDBManager(), task_queue=Queue(), - group_locks=defaultdict(create_new_group_lock), + group_locks=defaultdict(Lock), start_time=datetime.now(), config=config, ) - - if not await app["influx_client"].ensure_history_db_exists(): - raise MissingInfluxDatabase( - f"Required database {config['CREDITS_HISTORY_DB']} does not exist inside " - "InfluxDB. Must be created externally since this code runs without admin " - "access." - ) - + connected = False + while not connected: + try: + await app["database_client"].initialize() + internal_logger.info("Got a database connection and database is initialized.") + connected = True + except ConnectionRefusedError: + internal_logger.info("Got no database connection, sleeping for 10 and retrying.") + await asyncio.sleep(10) # setup jinja2 template engine setup(app, loader=FileSystemLoader(str(APP_ROOT / "templates"))) - app.on_startup.append(create_client_session) - app.on_startup.append(create_worker) - app.on_startup.append(setup_prometheus_metrics) - app.on_cleanup.append(stop_worker) - app.on_cleanup.append(close_client_sessions) + if not config["ENDPOINTS_ONLY"]: + app.on_startup.append(create_consumer_worker) + app.on_startup.append(create_producer) + app.on_cleanup.append(stop_consumer_worker) setup_swagger(app) diff --git a/src/os_credits/notifications.py b/src/os_credits/notifications.py deleted file mode 100644 index 96e8592..0000000 --- a/src/os_credits/notifications.py +++ /dev/null @@ -1,273 +0,0 @@ -""" -Notifications are modeled as exceptions so you are able to raise them whenever necessary -and don't have to worry about stopping further execution. All notifications are based on -:class:`EmailNotificationBase`. See :class:`HalfOfCreditsLeft` for a working example. -""" -from __future__ import annotations - -from asyncio import AbstractEventLoop -from asyncio import get_event_loop -from email.mime.text import MIMEText -from enum import Enum -from enum import auto -from string import Template -from typing import ClassVar -from typing import Dict -from typing import Optional -from typing import Set -from typing import Union - -from aiosmtplib import SMTP - -from os_credits.exceptions import BrokenTemplateError -from os_credits.exceptions import MissingTemplateError -from os_credits.exceptions import MissingToError -from os_credits.log import internal_logger -from os_credits.perun.group import Group -from os_credits.settings import config - - -class EmailRecipient(Enum): - """Defines placeholder values to use inside ``to``, ``cc``, ``bcc`` of Notification - classes which will replaced dynamically when the message is constructed. - """ - - CLOUD_GOVERNANCE = auto() - """Will be replaced with the value of ``CLOUD_GOVERNANCE_MAIL``, see :ref:`Settings` - """ - - PROJECT_MAINTAINERS = auto() - """Will be replaced with the ``ToEmail`` addresses of this project stored inside - Perun - """ - - -EmailRecipientType = Union[str, EmailRecipient] - - -class EmailNotificationBase(Exception): - """Base class of all exceptions whose cause should not only be logged but also send - to the Cloud Governance, the Group maintainers and/or other recipients. - - Subclasses are checked at creation time to make sure that we can always send - notifications and do not fail due to broken templates or missing ``To``. - """ - - to: ClassVar[Set[EmailRecipientType]] - """Set of recipients which will be set as ``To``. Must be set by subclasses, - otherwise a :exc:`~os_credits.exceptions.MissingToError` is raised.""" - - cc: ClassVar[Set[EmailRecipientType]] = set() - """Set of recipients which will be set as ``Cc``.""" - - bcc: ClassVar[Set[EmailRecipientType]] = set() - """Set of recipients which will be set as ``Bcc``.""" - - _subject: ClassVar[Template] - """:class:`string.Template` object created at class creatiom from the content of - :attr:`subject_template`.""" - - subject_template: ClassVar[str] - """String which will be parsed as :class:`string.Template` and used as the subject - of the mail. See :attr:`custom_placeholders` and :func:`construct_message` to know - which placeholders inside the templates are supported and how they can be - customized.""" - - _body: ClassVar[Template] - """:class:`string.Template` object created on class instantiation from the content - of :attr:`body_template`.""" - - body_template: ClassVar[str] - """String which will be parsed as :class:`string.Template` and used as the body of - the mail. See :attr:`custom_placeholders` and :func:`construct_message` to know - which placeholders inside the templates are supported and how they can be - customized.""" - - custom_placeholders: Dict[str, str] = {} - """Custom mapping of additional placeholders and values which can be used inside - :attr:`body_template` and :attr:`subject_template` templates. Intended to be used by - subclasses which can add values either as class variable or at runtime in their - constructor. - See :func:`construct_message` for default mappings provided by the base class whose - values are shadowed by values defined in this attribute. - """ - - def __init_subclass__(cls) -> None: - """Automatically constructs a template from the body provided by the subclass. - - Before construction whitespace at beginning and end of the body is removed. - - :raises ValueError: In case any template construction fails or the class defines - no `To`. - """ - if "body_template" not in dir(cls) or not cls.body_template.strip(): - raise MissingTemplateError( - f"Body template of {cls.__name__} is not defined or empty." - ) - if "subject_template" not in dir(cls) or not cls.subject_template.strip(): - raise MissingTemplateError( - f"Subject template of {cls.__name__} is not defined or empty." - ) - if "to" not in dir(cls) or not cls.to: - raise MissingToError( - f"{cls.__name__} does any define any ``To`` recipients." - ) - cls._body = Template(cls.body_template.strip()) - cls._subject = Template(cls.subject_template.strip()) - - def __init__(self, group: Group, message: str) -> None: - self.group = group - self.message = message - - def __str__(self) -> str: - return f"{type(self)}@{self.group.name}" - - def construct_message(self) -> MIMEText: - """Constructs a :class:`~email.mime.text.MIMEText` object from the - notification's attributes. - - The recipient placeholders are resolved, body and subject templates are rendered - with the following default placeholders: - - ``project`` - Name of the Project as stored in Perun. - ``credits_used`` - The current value of :class:`~os_credits.perun.attributes.DenbiCreditsUsed`. - ``credits_granted`` - The current value of - :class:`~os_credits.perun.attributes.DenbiCreditsGranted`. - - Subclasses are advised to add their own placeholders to - :attr:`custom_placeholders` instead of overwriting this method. If any - placeholder of a template cannot be resolved it will be left in place to ensure - that the message can be constructed and sent. - """ - - placeholders = { - "project": self.group.name, - "credits_used": str(self.group.credits_used.value), - "credits_granted": str(self.group.credits_granted.value), - **self.custom_placeholders, - } - try: - rendered_subject = self._subject.substitute(placeholders) - except KeyError as e: - internal_logger.error( - "Subject of Notification %s contains unknown placeholder %s. Sending " - "partially unformatted mail.", - type(self).__name__, - e, - ) - rendered_subject = self._subject.safe_substitute(placeholders) - except ValueError as e: - internal_logger.error( - "Subject of Notification %s contains invalid placeholder %s.", - type(self).__name__, - e, - ) - raise BrokenTemplateError(f"Subject of Notification {type(self).__name__}") - try: - rendered_body = self._body.substitute(placeholders) - except KeyError as e: - internal_logger.error( - "Body of Notification %s contains unknown placeholder %s. Sending " - "partially unformatted mail.", - type(self).__name__, - e, - ) - rendered_body = self._body.safe_substitute(placeholders) - except ValueError as e: - internal_logger.error( - "Body of Notification %s contains invalid placeholder %s.", - type(self).__name__, - e, - ) - raise BrokenTemplateError(f"Body of Notification {type(self).__name__}") - message = MIMEText(rendered_body) - message["Subject"] = rendered_subject - message["From"] = config["MAIL_FROM"] - if config["NOTIFICATION_TO_OVERWRITE"].strip(): - internal_logger.info( - "Applying `NOTIFICATION_TO_OVERWRITE` setting to notification `%s`", - self, - ) - message["To"] = config["NOTIFICATION_TO_OVERWRITE"] - else: - message["To"] = self._resolve_recipient_placeholders(self.to) - message["Cc"] = self._resolve_recipient_placeholders(self.cc) - message["Bcc"] = self._resolve_recipient_placeholders(self.bcc) - internal_logger.debug( - "Recipients of notification `%s`: To=%s, Cc=%s, Bcc=%s", - self, - message["To"], - message["Cc"], - message["Bcc"], - ) - - return message - - def _resolve_recipient_placeholders( - self, recipient_placeholders: Set[EmailRecipientType] - ) -> str: - recipients = set() - for r in recipient_placeholders: - if r is EmailRecipient.CLOUD_GOVERNANCE: - recipients.add(config["CLOUD_GOVERNANCE_MAIL"]) - elif r is EmailRecipient.PROJECT_MAINTAINERS: - for mail in self.group.email.value: - recipients.add(mail) - elif isinstance(r, str): - recipients.add(r) - return ",".join(recipients) - - -class HalfOfCreditsLeft(EmailNotificationBase): - subject_template = "50% Credits left for Project ${project}" - to = {EmailRecipient.PROJECT_MAINTAINERS} - cc = {EmailRecipient.CLOUD_GOVERNANCE} - body_template = """ -Dear user, - -Your project ${project} in the de.NBI Cloud has used $credits_used Cloud Credits of its granted $credits_granted Cloud Credits -and therefore less than 50% of its granted Cloud Credits left. -To view a history of your credits please login at the Cloud Portal under https://cloud.denbi.de/portal. - -For more information about Cloud Credits, its calculation and how to request more Cloud Credits please visit: - - https://cloud.denbi.de/wiki/portal/credits/ - -We also offer a Cloud Credits calculator which you may use to test different flavor/lifetime/CCs configurations which you will find at: - - https://cloud.denbi.de/creditscalculator/ - -Have a nice day, -Your de.NBI Cloud Governance - -""" - - def __init__(self, group: Group) -> None: - super().__init__( - group, - message=f"Group {group.name} has only 50% of their credits left. Sending " - "notification.", - ) - self.group = group - - -async def send_notification( - notification: EmailNotificationBase, loop: Optional[AbstractEventLoop] = None -) -> None: - loop = loop or get_event_loop() - async with SMTP( - hostname=config["MAIL_SMTP_SERVER"], port=config["MAIL_SMTP_PORT"], loop=loop - ) as smtp: - if not config["MAIL_NOT_STARTTLS"]: - internal_logger.debug("Not connecting via STARTTLS as requested") - await smtp.starttls() - if config["MAIL_SMTP_USER"] and config["MAIL_SMTP_PASSWORD"]: - internal_logger.debug("Authenticating against smtp server") - await smtp.login(config["MAIL_SMTP_USER"], config["MAIL_SMTP_PASSWORD"]) - else: - internal_logger.debug( - "Not authenticating against smtp server since neither user and/nor " - "password are specified." - ) - await smtp.send_message(notification.construct_message()) diff --git a/src/os_credits/perun/__init__.py b/src/os_credits/perun/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/os_credits/perun/attributes.py b/src/os_credits/perun/attributes.py deleted file mode 100644 index c114ac7..0000000 --- a/src/os_credits/perun/attributes.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Contains all leaf subclasses of -:class:`~os_credits.perun.base_attributes.PerunAttribute`. -""" -from __future__ import annotations - -from datetime import datetime -from decimal import Decimal -from typing import Any -from typing import Dict -from typing import List -from typing import Optional - -from .base_attributes import PERUN_DATETIME_FORMAT -from .base_attributes import ContainerPerunAttribute -from .base_attributes import CreditTimestamps -from .base_attributes import ReadOnlyScalarPerunAttribute -from .base_attributes import ScalarPerunAttribute -from .base_attributes import ToEmails -from .exceptions import DenbiCreditsGrantedMissing - -PERUN_NAMESPACE_OPT = "urn:perun:group:attribute-def:opt" -PERUN_NAMESPACE_DEF = "urn:perun:group:attribute-def:def" -PERUN_NAMESPACE_GROUP_RESOURCE_OPT = "urn:perun:group_resource:attribute-def:opt" - - -class DenbiCreditsUsed( - ScalarPerunAttribute[Optional[Decimal]], - perun_id=3382, - perun_friendly_name="denbiCreditsCurrent", - perun_type="java.lang.String", - perun_namespace=PERUN_NAMESPACE_OPT, -): - """Stores the amount of used credits per Project. - - Does currently not use the :class:`~os_credits.credits.base_models.Credits` type, - which would pin its precision to the value defined in the :ref:`Settings`. - The amount of credits to bill is rounded to this precision and therefore any change - in precision of this value must have been caused directly in *Perun* and not by us. - - Stored as string inside *Perun* which is good since the floats **must never be - stored as such** since it will lead to loss in precision! - - .. todo:: - - Fix friendly_name once changed on Perun site, if the change is not performed - synchronously things will break! - We used to track spent credits instead of used ones, therefore the current name - of the attribute inside Perun. - """ - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - def perun_deserialize(self, value: Optional[str]) -> Optional[Decimal]: - return Decimal(value) if value else None - - def perun_serialize(self, value: Optional[Decimal]) -> Optional[str]: - return None if value is None else str(value) - - -class DenbiCreditsGranted( - ReadOnlyScalarPerunAttribute[int], - perun_id=3383, - perun_friendly_name="denbiCreditsGranted", - perun_type="java.lang.String", - perun_namespace=PERUN_NAMESPACE_OPT, -): - """Contains the amount of credits granted to the project when its application was - confirmed. - - Expected to be an integer and implemented read-only since we **MUST** never change - it since the Cloud portal owns this value and will change it without telling us. - """ - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - def perun_deserialize(self, value: Optional[str]) -> int: - """Stored as str inside perun, unfortunately""" - if value is None: - raise DenbiCreditsGrantedMissing() - return int(float(value)) - - def perun_serialize(self, value: int) -> str: - """Stored as str inside perun, unfortunately""" - return str(value) - - -class ToEmail( - ContainerPerunAttribute[ToEmails], - perun_id=2020, - perun_friendly_name="toEmail", - perun_type="java.util.ArrayList", - perun_namespace=PERUN_NAMESPACE_DEF, -): - """Contains the mail addresses of the project administrators. - - Used to send :ref:`Notifications` in case of expired credits or other events. - """ - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - def perun_deserialize(self, value: Optional[List[str]]) -> ToEmails: - return value if value else [] - - -class DenbiCreditTimestamps( - ContainerPerunAttribute[CreditTimestamps], - perun_id=3386, - perun_friendly_name="denbiCreditTimestamps", - perun_type="java.util.LinkedHashMap", - perun_namespace=PERUN_NAMESPACE_GROUP_RESOURCE_OPT, -): - """This attribute is the most important one when it comes to billing projects. It is - not directly associated to group but to a combination of resource and group where - the former is associated with the latter. - - The contained dictionary is a mapping between a metric's name and a timestamp of a - measurement, see :ref:`Metrics and Measurements`. The measurement, whose timestamp - is stored, is the most recent one used to bill this metric. - """ - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - def perun_deserialize(self, value: Optional[Dict[str, str]]) -> CreditTimestamps: - measurement_timestamps = {} - if value is not None: - for measurement_str, timestamp_str in value.items(): - measurement_timestamps.update( - { - measurement_str: datetime.strptime( - timestamp_str, PERUN_DATETIME_FORMAT - ) - } - ) - return measurement_timestamps - - def perun_serialize(self, value: CreditTimestamps) -> Dict[str, str]: - return { - measurement: timestamp.strftime(PERUN_DATETIME_FORMAT) - for measurement, timestamp in value.items() - } diff --git a/src/os_credits/perun/attributesManager.py b/src/os_credits/perun/attributesManager.py deleted file mode 100644 index cb89f65..0000000 --- a/src/os_credits/perun/attributesManager.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Implements the RPC-Calls of the AttributesManager -https://perun-aai.org/documentation/technical-documentation/rpc-api/rpc-javadoc-AttributesManager.html -""" - -from __future__ import annotations - -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import cast - -from .base_attributes import PerunAttribute -from .requests import perun_get -from .requests import perun_set - -_URL = "attributesManager" - - -async def get_resource_bound_attributes( - group_id: int, resource_id: int, attribute_full_names: Optional[List[str]] = None -) -> List[Dict[str, Any]]: - params: Dict[str, Any] = {"group": group_id, "resource": resource_id} - if attribute_full_names: - params.update({"attrNames": attribute_full_names}) - # cast is only for type checking purposes - return cast( - List[Dict[str, Any]], await perun_get(f"{_URL}/getAttributes", params=params) - ) - - -async def get_attributes( - group_id: int, attribute_full_names: Optional[List[str]] = None -) -> List[Dict[str, Any]]: - params: Dict[str, Any] = {"group": group_id} - if attribute_full_names: - params.update({"attrNames": attribute_full_names}) - # cast is only for type checking purposes - return cast( - List[Dict[str, Any]], await perun_get(f"{_URL}/getAttributes", params=params) - ) - - -async def set_attribute(group_id: int, attribute: PerunAttribute[Any]) -> None: - await perun_set( - f"{_URL}/setAttribute", - {"group": group_id, "attribute": attribute.to_perun_dict()}, - ) - - -async def set_resource_bound_attributes( - group_id: int, resource_id: int, attributes: List[PerunAttribute[Any]] -) -> None: - await perun_set( - f"{_URL}/setAttributes", - { - "group": group_id, - "resource": resource_id, - "attributes": [attr.to_perun_dict() for attr in attributes], - }, - ) - - -async def set_attributes(group_id: int, attributes: List[PerunAttribute[Any]]) -> None: - await perun_set( - f"{_URL}/setAttributes", - { - "group": group_id, - "attributes": [attr.to_perun_dict() for attr in attributes], - }, - ) diff --git a/src/os_credits/perun/base_attributes.py b/src/os_credits/perun/base_attributes.py deleted file mode 100644 index 4b3659f..0000000 --- a/src/os_credits/perun/base_attributes.py +++ /dev/null @@ -1,299 +0,0 @@ -"""Contains all subclasses of and -:class:`~os_credits.perun.base_attributes.PerunAttribute` itself but not any leaf -subclasses. -""" -from __future__ import annotations - -from datetime import datetime -from typing import Any -from typing import Dict -from typing import Generic -from typing import List -from typing import Type -from typing import TypeVar - -PERUN_DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f" -# ValueType -VT = TypeVar("VT") - - -class PerunAttribute(Generic[VT]): - """Base class of all *Perun* attributes. The :class:`~typing.Generic` base class is - used to allow attributes to specify the type of their deserialized value. This - allows a static type checker to check for correct usage. - - Should not be extended or called directly but rather one of the other base classes - defined in this file which extend this one. Its main task is to collect all - available attributes via :ref:`Subclass Hooks` and make them available to - :func:`os_credits.perun.group.Group.get_perun_attributes`. - - The actual value of an attribute is stored inside :data:`_value` and must be - provided as :class:`property` by a subclass. Subclasses are also responsible for - overwriting :func:`perun_serialize` and :func:`perun_deserialize` if necessary. When - doing so they have to make sure that their deserialized version of ``value=None``, - i.e. the attribute has currently no value inside *Perun*, such as ``0`` or ``[]`` - evaluates to false when calling ``bool(attr.value)``. - - Information such as :data:`id`, :data:`friendlyName`, :data:`type`, - :data:`namespace` have to known inside the code since they must be sent to *Perun* - when initially setting the value of an attribute. - - """ - - _updated = False - _value: VT - - registered_attributes: Dict[str, Type["PerunAttribute"[Any]]] = {} - """Mapping between the name of a subclass of PerunAttribute and the actual class - object, needed to determine the class of a requested attribute of a group, see - :func:`~os_credits.perun.group.Group.get_perun_attributes`. - """ - - friendlyName: str - """Human readable name of the attribute inside Perun. - """ - - id: int - """ID of the attribute inside Perun. - """ - - type: str - """Type of the attribute inside Perun. - """ - - namespace: str - """Namespace of the attribute inside Perun. - """ - - def __init_subclass__( - cls, - perun_id: int, - perun_friendly_name: str, - perun_type: str, - perun_namespace: str, - ) -> None: - # Only process real attribute, not intermediate base classes - if not perun_id: - return - cls.friendlyName = perun_friendly_name - cls.id = perun_id - cls.type = perun_type - cls.namespace = perun_namespace - PerunAttribute.registered_attributes[cls.__name__] = cls - - def __init__(self, value: Any, **kwargs: Any) -> None: - """Using kwargs since :func:`~os_credits.perun.group.Group.connect` calls us - with all *subattributes* received from *Perun* but we are currently only - interested in ``value``. - - Once other *subattributes* are needed by the code, e.g. ``value_modified_at``, - they should be added to the function signature and set inside this constructor. - """ - self._value = self.perun_deserialize(value) - - def to_perun_dict(self) -> Dict[str, Any]: - """Serialize the attribute into a dictionary which can passed as JSON content to - the perun API. - """ - return { - "value": self.perun_serialize(self._value), - "namespace": self.namespace, - "id": self.id, - "friendlyName": self.friendlyName, - "type": self.type, - } - - @classmethod - def get_full_name(cls) -> str: - """Needed when querying specific attributes of a group instead of all of them. - - :return: Full name of the attribute inside *Perun*. - """ - return f"{cls.namespace}:{cls.friendlyName}" - - def perun_deserialize(self, value: Any) -> Any: - """Deserialize from the type/format used by *Perun* when converting into JSON to - transmit the value of this attribute. - - :param value: Serialized value received by *Perun* via JSON. - :return: Deserialized value used by application. - """ - return value - - def perun_serialize(self, value: Any) -> Any: - """Serialize to the type/format used by *Perun* when converting into JSON to - transmit the value of this attribute. - - :param value: Deserialized value used by application. - :return: Serialized value for transmission to *Perun*. - """ - return value - - @classmethod - def is_resource_bound(cls) -> bool: - """ - Whether this attribute is not only bound to one specific group but a combination - of group and resource. - """ - return "group_resource" in cls.namespace.split(":") - - @property - def has_changed(self) -> bool: - """ - Whether the ``value`` of this attribute has changed since creation. - """ - return self._updated - - @has_changed.setter - def has_changed(self, value: bool) -> None: - if not isinstance(value, bool): - raise TypeError("`has_changed` must be of type bool.") - self._updated = value - - def __str__(self) -> str: - return str(self._value) - - def __repr__(self) -> str: - # This assumes that either the container value evaluates to false or in the - # scalar case that the attribute is None - if not self._value: - return f"{type(self).__name__}(value=None)" - param_repr: List[str] = [f"value={self._value}"] - for attribute in filter( - lambda attribute: not attribute.startswith("_"), self.__annotations__.keys() - ): - # None of the values are set in offline mode - if attribute in dir(self): - param_repr.append(f"{attribute}={repr(getattr(self, attribute))}") - - return f"{type(self).__name__}({','.join(param_repr)})" - - def __bool__(self) -> bool: - return bool(self._value) - - -class ReadOnlyScalarPerunAttribute( - PerunAttribute[VT], - # class definition must contain the following attributes to allow 'passthrough' from - # child classes - perun_id=None, - perun_friendly_name=None, - perun_type=None, - perun_namespace=None, -): - """Base class for all read-only scalar attributes, where `value` only contains a - scalar value, e.g. a `float` or `str`, in contrast to container attributes. - """ - - def __init_subclass__(cls, **kwargs: Any) -> None: - super().__init_subclass__(**kwargs) - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - @property - def value(self) -> VT: - return self._value - - -class ScalarPerunAttribute( - PerunAttribute[VT], - # class definition must contain the following attributes to allow 'passthrough' from - # child classes - perun_id=None, - perun_friendly_name=None, - perun_type=None, - perun_namespace=None, -): - """ - Identical to :class:`ReadOnlyScalarPerunAttribute` but provides a setter method for - :attr:`value` which makes sure that type of non-empty content does not change. - """ - - def __init_subclass__(cls, **kwargs: Any) -> None: - super().__init_subclass__(**kwargs) - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - - @property - def value(self) -> VT: - return self._value - - @value.setter - def value(self, value: Any) -> None: - # only check for type correctness if we already have a value - if self._value and not isinstance(value, type(self._value)): - raise TypeError( - f"Value must be of the same type as current one ({type(self.value)})" - ) - if self.value != value: - self._updated = True - self._value = value - - -ToEmails = List[str] -CreditTimestamps = Dict[str, datetime] -# "ContainerValueType", used by type checker, to ensure that the classes defines a -# `.copy` method -CVT = TypeVar("CVT", ToEmails, CreditTimestamps) - - -class ContainerPerunAttribute( - PerunAttribute[CVT], - # class definition must contain the following attributes to allow 'passthrough' from - # child classes - perun_id=None, - perun_friendly_name=None, - perun_type=None, - perun_namespace=None, -): - """ - Base class for container attributes, e.g. :data:`ToEmails` where `value` is a list - of the actual mail addresses. - - The `has_changed` logic of PerunAttribute has to be overwritten for this classes - since any changes are not reflected by updating `value` as an attribute but by - updating its contents. Therefore `value` does not have a setter. - - If Perun does not have any value yet for this attribute - :func:`~Perun.perun_deserialize` must set not it to ``None`` but to an empty - container. This eases the handling since no checks for ``None`` are needed. - - In addition ``None`` cannot be used since we create a shallow copy of the attribute - once it has been serialized to be able to detect changes to its values; ``None`` has - no ``copy`` function but all several container data structures such as ``list``, - ``dict`` and ``set`` support it. Finally it allows to us skip defining a setter for - :attr:`value` since only its contents are allowed to change. - """ - - _value: CVT - _value_copy: CVT - - def __init_subclass__(cls, **kwargs: Any) -> None: - super().__init_subclass__(**kwargs) - - def __init__(self, **kwargs: Any) -> None: - super().__init__(**kwargs) - self._value_copy = self._value.copy() - - @property - def has_changed(self) -> bool: - """ - Since the value of this attribute is a container the setter approach of the - superclass to detect changes does not work. Instead we compare the current - values with the initial ones. - """ - return self._value_copy != self._value - - @has_changed.setter - def has_changed(self, value: bool) -> None: - if not value: - # reset changed indicator - self._value_copy = self._value.copy() - return - raise ValueError("Manually setting to true not supported") - - @property - def value(self) -> CVT: - return self._value diff --git a/src/os_credits/perun/exceptions.py b/src/os_credits/perun/exceptions.py deleted file mode 100644 index b01ef74..0000000 --- a/src/os_credits/perun/exceptions.py +++ /dev/null @@ -1,55 +0,0 @@ -from os_credits.exceptions import CreditsError - - -class PerunBaseException(CreditsError): - "Base exception class for all errors occurring during communication with Perun" - pass - - -class BadCredentialsException(PerunBaseException): - "Raised if Perun returns an 'Unauthorized' status code" - pass - - -class GroupNotExistsError(PerunBaseException): - "Python mapping of Perun's GroupNotExistsException" - pass - - -class InternalError(PerunBaseException): - "Python mapping of Perun's InternalErrorException" - pass - - -class ConsistencyError(PerunBaseException): - "Python mapping of Perun's ConsistencyErrorException" - pass - - -class RequestError(PerunBaseException): - "Generic Exception in case no specific exception has been thrown" - pass - - -class GroupResourceNotAssociatedError(PerunBaseException): - "Raised if a group and a resource are not associated but should be" - pass - - -class GroupAttributeError(CreditsError): - "Base Exception if any Group-Attribute has an invalid value" - pass - - -class DenbiCreditsUsedMissing(GroupAttributeError): - """Raised if a group does not have any value for - :class:`~os_credits.perun.attributes.DenbiCreditsUsed` and has been billed before""" - - pass - - -class DenbiCreditsGrantedMissing(GroupAttributeError): - """Raised if a group does not have any credits granted in which case we cannot - operate on it.""" - - pass diff --git a/src/os_credits/perun/group.py b/src/os_credits/perun/group.py deleted file mode 100644 index cbe43ed..0000000 --- a/src/os_credits/perun/group.py +++ /dev/null @@ -1,285 +0,0 @@ -from __future__ import annotations - -from functools import lru_cache -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import Type -from typing import TypeVar - -from os_credits.log import internal_logger - -from .attributes import DenbiCreditsGranted -from .attributes import DenbiCreditsUsed -from .attributes import DenbiCreditTimestamps -from .attributes import ToEmail -from .attributesManager import get_attributes -from .attributesManager import get_resource_bound_attributes -from .attributesManager import set_attributes -from .attributesManager import set_resource_bound_attributes -from .base_attributes import PerunAttribute -from .exceptions import GroupResourceNotAssociatedError -from .groupsManager import get_group_by_name -from .resourcesManager import get_assigned_resources - -GTV = TypeVar("GTV", bound="Group") - - -class Group: - """ - Represents a Group object inside Perun. - - Every attribute requested via class variable annotations, where the annotation is a - subclass of :class:`~os_credits.perun.base_attributes.PerunAttribute`, will be set - on the class with the chosen name. This happens during `connect()` which tries to - fill the Attributes with information from Perun. - """ - - name: str - """Name of this group. Always set on instantiated objects since it is required by - the constructor. - """ - - resource_id: int - """ID of the resource to use when retrieving resource bound attributes. Also - required by constructor. - """ - - assigned_resource: Optional[bool] = None - """Indicator whether this group is actually assigned to resource with - ``resource_id``. Initially None, set to bool when :func:`connect` is called, but - only if any of the annotated - :class:`~os_credits.perun.base_attributes.PerunAttribute` subclasses are *resource - bound* attributes. - """ - - id: int - """ID of this group inside Perun. Initially unset, also populated by - :func:`connect`. - """ - - email: ToEmail - """Initially unset, populated on :func:`connect`. See - :class:`~os_credits.perun.attributes.ToEmail`. - """ - credits_granted: DenbiCreditsGranted - """Initially unset, populated on :func:`connect`. See - :class:`~os_credits.perun.attributes.DenbiCreditsGranted`. - """ - credits_used: DenbiCreditsUsed - """Initially unset, populated on :func:`connect`. See - :class:`~os_credits.perun.attributes.DenbiCreditsUsed`. - """ - credits_timestamps: DenbiCreditTimestamps - """Initially unset, populated on :func:`connect`. See - :class:`~os_credits.perun.attributes.DenbiCreditTimestamps`. - """ - - def __init__(self, name: str, resource_id: int = 0) -> None: - """ - Calling this method only creates the object, but does not query perun yet. Call - :func:`connect` to populate all Perun attributes with content. - - :param name: Name of group inside Perun. Unique inside the de.NBI VO - :param resource_id: ID of the resource to use when retrieving resource bound - attributes. - """ - self.name = name - self.resource_id = resource_id - - async def connect(self: GTV) -> GTV: - """Retrieve all required values from *Perun* and populate the rest of the - variables of this instance. - - #. The ID of this group is determined by calling - :func:`~os_credits.perun.groupsManager.get_group_by_name` since it is needed - by the next methods. - #. :func:`get_perun_attributes` is used to determine - which attributes of the class must be retrieved from *Perun*. - - #. If any of these attributes are *resource bound* we check whether the - resource whose ID is stored in :attr:`resource_id` is actually associated - with this group. This check is necessary since *Perun* is happy to return - and store attributes of *invalid* combinations of groups and resources. - The result is stored in :attr:`assigned_resource` and performed by - :func:`is_assigned_resource`. - - #. All attributes of the group are retrieved by calling - :func:`~os_credits.perun.attributesManager.get_attributes` and - :func:`~os_credits.perun.attributesManager.get_resource_bound_attributes` if - necessary and stored inside this group. - - :return: Self, to allow chaining such as ``g=await Group([...]).connect()`` - :raises GroupResourceNotAssociatedError: In case group :attr:`name` is not - assigned to resource with :attr:`resource_id` inside *Perun*. - :raises ~os_credits.perun.exceptions.PerunBaseException: Or any other subclass - of exception indicating errors during communication with perun. - """ - group_response = await get_group_by_name(self.name) - self.id = int(group_response["id"]) - - # Mappings between the names of perun attributes needed for this instance and - # the friendlyName of the actual attributes - # friendlyName -> name_used_in_instance - friendly_name_to_group_attr_name: Dict[str, str] = {} - - requested_attributes: List[str] = [] - requested_resource_bound_attributes: List[str] = [] - - for (attr_name, attr_class) in type(self).get_perun_attributes().items(): - friendly_name_to_group_attr_name[attr_class.friendlyName] = attr_name - if attr_class.is_resource_bound(): - requested_resource_bound_attributes.append(attr_class.get_full_name()) - else: - requested_attributes.append(attr_class.get_full_name()) - # will hold the contents of all retrieved attributes - attributes: Dict[str, Dict[str, Any]] = {} - if requested_attributes: - for attr in await get_attributes( - self.id, attribute_full_names=requested_attributes - ): - attributes[attr["friendlyName"]] = attr - - if requested_resource_bound_attributes: - self.assigned_resource = await self.is_assigned_resource() - if not self.assigned_resource: - raise GroupResourceNotAssociatedError( - f"Group `{self.name}` is not associated with resource with id " - f"`{self.resource_id}` but resource bound attributes have been " - "requested " - ) - for attr in await get_resource_bound_attributes( - self.id, - self.resource_id, - attribute_full_names=requested_resource_bound_attributes, - ): - attributes[attr["friendlyName"]] = attr - internal_logger.debug( - "Retrieved attributes Group %s: %s", - self, - {attr_name: attr["value"] for attr_name, attr in attributes.items()}, - ) - for friendly_name, group_attr_name in friendly_name_to_group_attr_name.items(): - attr_class = type(self).get_perun_attributes()[group_attr_name] - - try: - setattr(self, group_attr_name, attr_class(**attributes[friendly_name])) - except KeyError: - # in case we got no content for this attribute by perun - setattr(self, group_attr_name, attr_class(value=None)) - - return self - - async def is_assigned_resource(self) -> bool: - """Explicit check whether the resource :attr:`resource_id` is associated with - group :attr:`name`. - - Does so by testing whether :attr:`resource_id` is part of the response of - :func:`~os_credits.perun.resourcesManager.get_assigned_resources` - """ - # using a generator expression saves time - return self.resource_id in ( - resource["id"] for resource in await get_assigned_resources(self.id) - ) - - async def save(self, _save_all: bool = False) -> None: - """Collects all annotated - :class:`~os_credits.perun.base_attributes.PerunAttribute` of this group and - sends/saves them to *Perun* in case their value has changed since retrieval. - - Uses the :attr:`~os_credits.perun.base_attributes.PerunAttribute.has_changed` - attribute. - - :param _save_all: Save all attributes regardless whether their value was - actually changed since retrieval. Also used for testing. - """ - internal_logger.debug("Save of Group %s called", self) - changed_attrs: List[PerunAttribute[Any]] = [] - changed_resource_bound_attrs: List[PerunAttribute[Any]] = [] - # collect all attributes whose value has changed since retrieval - for attribute_name in type(self).get_perun_attributes(): - attr = getattr(self, attribute_name) - # save all attributes in offline/dummy since we will not get non-stored back - # from Perun - if attr.has_changed or _save_all: - if not attr.is_resource_bound(): - changed_attrs.append(attr) - else: - changed_resource_bound_attrs.append(attr) - if changed_attrs: - internal_logger.debug( - "Sending modified regular attributes to perun %s", changed_attrs - ) - await set_attributes(self.id, changed_attrs) - if changed_resource_bound_attrs: - if getattr(self, "assigned_resource", False): - internal_logger.debug( - "Sending modified resource bound attributes to perun %s", - changed_resource_bound_attrs, - ) - await set_resource_bound_attributes( - self.id, self.resource_id, changed_resource_bound_attrs - ) - else: - internal_logger.warning( - "Not sending modified attribute to perun, since Group %s is not " - "associated with resource with id %s. How did we even retrieve any " - "such attributes?", - self.name, - self.resource_id, - ) - - def __repr__(self) -> str: - # in case Group has not been connected yet - if getattr(self, "id", None) is None: - return f"Group({self.name},{self.resource_id})" - param_repr: List[str] = [] - for attribute in type(self).get_perun_attributes(): - param_repr.append(f"{attribute}={repr(self.__getattribute__(attribute))}") - - # using square instead of regular brackets to indicate that you cannot copy - # paste this output to construct a group - return f"Group[{','.join(param_repr)}]" - - def __str__(self) -> str: - return f"{self.name}@{self.resource_id}" - - def __setattr__(self, name: str, value: Any) -> None: - if name in type(self).get_perun_attributes() and not isinstance( - value, PerunAttribute - ): - raise AttributeError( - "PerunAttributes must not be replaced by non-PerunAttributes. Update " - "the attribute's `value` attribute instead." - ) - object.__setattr__(self, name, value) - - @classmethod - @lru_cache() - def get_perun_attributes(cls) -> Dict[str, Type[PerunAttribute[Any]]]: - """ - Return all class attributes which are annotated with subclasses of - :class:`~os_credits.perun.base_attributes.PerunAttribute`. - - Since the content of the response cannot change at runtime a - :func:`~functools.lru_cache` is used. - - :return: Dictionary of the attribute names of this class and the corresponding - :class:`~os_credits.perun.base_attributes.PerunAttribute` subclass. - """ - attributes = {} - for attr_name, attr_class_name in cls.__annotations__.items(): - try: - attributes[attr_name] = PerunAttribute.registered_attributes[ - attr_class_name - ] - internal_logger.debug( - "Connected group attribute `%s` with PerunAttribute `%s`", - attr_name, - attr_class_name, - ) - except KeyError: - # this will fail for any non-Perun attribute, such as name or id - pass - return attributes diff --git a/src/os_credits/perun/groupsManager.py b/src/os_credits/perun/groupsManager.py deleted file mode 100644 index 6658380..0000000 --- a/src/os_credits/perun/groupsManager.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Implements the RPC-Calls of the `GroupsManager -`_. -""" - -from __future__ import annotations - -from typing import Any -from typing import Dict -from typing import cast - -from os_credits.settings import config - -from .requests import perun_get - -__url = "groupsManager" - - -async def get_group_by_name(name: str) -> Dict[str, Any]: - """Retrieve all information of a Group from *Perun*. - - Name of the function is chosen to match the one from the `Perun documentation - `_. - - :param name: Name of the Group/Project whose information should be retrieved. - :return: Dictionary of attributes of the requested Group.""" - return cast( - Dict[str, Any], - await perun_get( - f"{__url}/getGroupByName", - params={"vo": config["OS_CREDITS_PERUN_VO_ID"], "name": name}, - ), - ) diff --git a/src/os_credits/perun/requests.py b/src/os_credits/perun/requests.py deleted file mode 100644 index 64cb704..0000000 --- a/src/os_credits/perun/requests.py +++ /dev/null @@ -1,71 +0,0 @@ -from contextvars import ContextVar -from typing import Any -from typing import Dict -from typing import Optional - -from aiohttp import BasicAuth -from aiohttp import ClientSession - -from os_credits.log import requests_logger -from os_credits.settings import config - -from .exceptions import BadCredentialsException -from .exceptions import ConsistencyError -from .exceptions import GroupNotExistsError -from .exceptions import InternalError -from .exceptions import RequestError - -# will be instantiated/set it in the context of the aiohttp.web.application on -# startup to have its lifetime bound to the application -client_session: ContextVar[ClientSession] = ContextVar("client_session") - -RPC_BASE_URL = "https://perun.elixir-czech.cz/krb/rpc/json" - - -async def perun_set(url: str, params: Optional[Dict[str, Any]] = None) -> None: - await _perun_rpc(url, params) - - -async def perun_get(url: str, params: Optional[Dict[str, Any]] = None) -> Any: - return await _perun_rpc(url, params) - - -async def _perun_rpc(url: str, params: Optional[Dict[str, Any]] = None) -> Any: - request_url = f"{RPC_BASE_URL}/{url}" - requests_logger.debug( - "Sending POST request `%s` with data `%s`", request_url, params - ) - try: - _client = client_session.get() - except LookupError: - # in case of not running inside the application, i.e. inside an ipython console - # for testing purposes - # not nice to create a new session per request but ok for testing - _client = ClientSession( - auth=BasicAuth( - config["OS_CREDITS_PERUN_LOGIN"], config["OS_CREDITS_PERUN_PASSWORD"] - ) - ) - - async with _client.post(request_url, json=params) as response: - if response.status == 401: - raise BadCredentialsException( - "Perun returned 'Unauthorized' status code for user " - f"`{config['OS_CREDITS_PERUN_LOGIN']}`." - ) - response_content = await response.json() - requests_logger.debug( - "Received response %r with content %r", response, response_content - ) - if response_content and "errorId" in response_content: - # Some kind of error has occured - if response_content["name"] == "GroupNotExistsException": - raise GroupNotExistsError(response_content["message"]) - elif response_content["name"] == "InternalErrorException": - raise InternalError(response_content["message"]) - elif response_content["name"] == "ConsistencyErrorException": - raise ConsistencyError(response_content["message"]) - else: - raise RequestError(response_content["message"]) - - return response_content diff --git a/src/os_credits/perun/resourcesManager.py b/src/os_credits/perun/resourcesManager.py deleted file mode 100644 index ff7f797..0000000 --- a/src/os_credits/perun/resourcesManager.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Implements the RPC-Calls of the ResourcesManager -https://perun-aai.org/documentation/technical-documentation/rpc-api/rpc-javadoc-ResourcesManager.html -""" - -from __future__ import annotations - -from typing import Any -from typing import Dict -from typing import List -from typing import cast - -from .requests import perun_get - -_URL = "resourcesManager" - - -async def get_assigned_resources(group_id: int) -> List[Dict[str, Any]]: - """ - https://perun-aai.org/documentation/technical-documentation/rpc-api/rpc-javadoc-ResourcesManager.html#ResourcesManagergetAssignedResources1 - """ - params = {"group": group_id} - # cast is only for type checking purposes - return cast( - List[Dict[str, Any]], - await perun_get(f"{_URL}/getAssignedResources", params=params), - ) diff --git a/src/os_credits/prometheus_metrics.py b/src/os_credits/prometheus_metrics.py deleted file mode 100644 index f09bd46..0000000 --- a/src/os_credits/prometheus_metrics.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -from prometheus_client import Counter -from prometheus_client import Gauge - -projects_processed_counter = Counter( - "projects_processed", "Number of projects that have been processed" -) -tasks_queued_gauge = Gauge("tasks_queued", "Number of tasks currently queued") -worker_exceptions_counter = Counter( - "worker_exceptions", "Number of exceptions raised by all worker tasks" -) diff --git a/src/os_credits/settings.py b/src/os_credits/settings.py index 19b7cf7..380035e 100644 --- a/src/os_credits/settings.py +++ b/src/os_credits/settings.py @@ -10,20 +10,18 @@ from __future__ import annotations +import json from collections import ChainMap from collections import UserDict from decimal import Decimal from os import environ -from typing import Any +from typing import Any, TypedDict from typing import Dict from typing import Optional from typing import Set from typing import cast -from mypy_extensions import TypedDict - -from os_credits.exceptions import MissingConfigError -from os_credits.log import internal_logger +from .log import internal_logger class Config(TypedDict): @@ -37,7 +35,7 @@ class Config(TypedDict): .. envvar:: CREDITS_HISTORY_DB - Name of the database inside the InfluxDB in which to store the + Name of the database inside the TimescaleDB in which to store the :class:`~os_credits.credits.models.BillingHistory` objects. The database must already exist when the application is launched and correct permissions have to be set. @@ -158,56 +156,38 @@ class Config(TypedDict): X-API-KEY to use for authentication protected endpoints. """ - CLOUD_GOVERNANCE_MAIL: str - CREDITS_HISTORY_DB: str - # named this way to match environment variable used by the influxdb docker image - INFLUXDB_DB: str - INFLUXDB_HOST: str - INFLUXDB_PORT: int - INFLUXDB_USER: str - INFLUXDB_USER_PASSWORD: str - MAIL_FROM: str - MAIL_NOT_STARTTLS: bool - MAIL_SMTP_PASSWORD: str - MAIL_SMTP_PORT: int - MAIL_SMTP_USER: str - MAIL_SMTP_SERVER: str - NOTIFICATION_TO_OVERWRITE: str - OS_CREDITS_PERUN_LOGIN: str - OS_CREDITS_PERUN_PASSWORD: str - OS_CREDITS_PERUN_VO_ID: int - OS_CREDITS_PRECISION: Decimal + # named this way to match environment variable used by the timescaledb docker image + POSTGRES_DB: str + POSTGRES_HOST: str + POSTGRES_PORT: int + POSTGRES_USER: str + POSTGRES_PASSWORD: str OS_CREDITS_PROJECT_WHITELIST: Optional[Set[str]] OS_CREDITS_WORKERS: int - VCPU_CREDIT_PER_HOUR: Decimal - RAM_CREDIT_PER_HOUR: Decimal + METRICS_TO_BILL: Dict API_KEY: str + API_CONTACT_KEY: str + API_CONTACT_BASE_URL: str + MAIL_CONTACT_URL: str + ENDPOINTS_ONLY: bool + OS_CREDITS_PRECISION: int default_config = Config( - CLOUD_GOVERNANCE_MAIL="", - CREDITS_HISTORY_DB="credits_history", - INFLUXDB_DB="", - INFLUXDB_HOST="localhost", - INFLUXDB_PORT=8086, - INFLUXDB_USER="", - INFLUXDB_USER_PASSWORD="", - MAIL_FROM="cloud@denbi.de", - MAIL_NOT_STARTTLS=False, - MAIL_SMTP_PASSWORD="", - MAIL_SMTP_PORT=25, - MAIL_SMTP_SERVER="localhost", - MAIL_SMTP_USER="", - NOTIFICATION_TO_OVERWRITE="", - OS_CREDITS_PERUN_LOGIN="", - OS_CREDITS_PERUN_PASSWORD="", - OS_CREDITS_PERUN_VO_ID=0, - OS_CREDITS_PRECISION=Decimal(10) ** -2, + POSTGRES_DB="credits_db", + POSTGRES_HOST="localhost", + POSTGRES_PORT=5432, + POSTGRES_USER="postgres", + POSTGRES_PASSWORD="password", OS_CREDITS_PROJECT_WHITELIST=None, OS_CREDITS_WORKERS=10, - VCPU_CREDIT_PER_HOUR=Decimal(1), - RAM_CREDIT_PER_HOUR=Decimal("0.3"), - API_KEY="" + API_KEY="", + API_CONTACT_KEY="", + METRICS_TO_BILL={}, + API_CONTACT_BASE_URL="", + MAIL_CONTACT_URL="", + ENDPOINTS_ONLY=False, + OS_CREDITS_PRECISION=2 ) @@ -226,16 +206,18 @@ def parse_config_from_environment() -> Config: except KeyError: # Environment variable not set, that's ok pass - for bool_value in ["MAIL_NOT_STARTTLS"]: - if bool_value in environ: - PROCESSED_ENV_CONFIG.update({bool_value: True}) + + try: + PROCESSED_ENV_CONFIG.update({ + "METRICS_TO_BILL": json.loads(environ["METRICS_TO_BILL"]) + }) + except KeyError: + pass for int_value_key in [ - "OS_CREDITS_PRECISION", "OS_CREDITS_WORKERS", - "INFLUXDB_PORT", - "OS_CREDITS_PERUN_VO_ID", - "MAIL_SMTP_PORT", + "POSTGRES_PORT", + "OS_CREDITS_PRECISION" ]: try: int_value = int(environ[int_value_key]) @@ -251,6 +233,7 @@ def parse_config_from_environment() -> Config: internal_logger.debug(f"Added {int_value_key} to procssed env") except KeyError: # Environment variable not set, that's ok + pass except ValueError: internal_logger.warning( @@ -264,52 +247,6 @@ def parse_config_from_environment() -> Config: # value from the environment but rather the default one del environ[int_value_key] - for decimal_value_key in [ - "VCPU_CREDIT_PER_HOUR", - "RAM_CREDIT_PER_HOUR", - ]: - try: - decimal_value = Decimal(environ[decimal_value_key]) - if decimal_value < 0: - internal_logger.warning( - "Decimal value (%s) must not be negative, falling back to default " - "value", - decimal_value_key, - ) - del environ[decimal_value_key] - continue - PROCESSED_ENV_CONFIG.update({decimal_value_key: decimal_value}) - internal_logger.debug(f"Added {decimal_value_key} to procssed env") - except KeyError: - # Environment variable not set, that's ok - pass - except ValueError: - internal_logger.warning( - "Could not convert value of $%s('%s') to Decimal", - decimal_value_key, - environ[decimal_value_key], - ) - # since we cannot use a subset of the actual environment, see below, we have - # to remove invalid keys from environment to make sure that if such a key is - # looked up inside the config the chainmap does not return the unprocessed - # value from the environment but rather the default one - del environ[decimal_value_key] - - if "OS_CREDITS_PRECISION" in PROCESSED_ENV_CONFIG: - PROCESSED_ENV_CONFIG["OS_CREDITS_PRECISION"] = ( - Decimal(10) ** -PROCESSED_ENV_CONFIG["OS_CREDITS_PRECISION"] - ) - - if "VCPU_CREDIT_PER_HOUR" in PROCESSED_ENV_CONFIG: - PROCESSED_ENV_CONFIG["VCPU_CREDIT_PER_HOUR"] = ( - Decimal(PROCESSED_ENV_CONFIG["VCPU_CREDIT_PER_HOUR"]) - ) - - if "RAM_CREDIT_PER_HOUR" in PROCESSED_ENV_CONFIG: - PROCESSED_ENV_CONFIG["RAM_CREDIT_PER_HOUR"] = ( - Decimal(PROCESSED_ENV_CONFIG["RAM_CREDIT_PER_HOUR"]) - ) - # this would be the right way but makes pytest hang forever -.-' # use the workaround explained above and add the raw process environment to the # chainmap although this is not really nice :( @@ -336,7 +273,7 @@ def __getitem__(self, key): internal_logger.exception( "Config value %s was requested but not known. Appending stacktrace", key ) - raise MissingConfigError(f"Missing value for key {key}") + raise f"Missing value for key {key}" config = cast( diff --git a/src/os_credits/views.py b/src/os_credits/views.py index 416e1eb..604d6de 100644 --- a/src/os_credits/views.py +++ b/src/os_credits/views.py @@ -3,128 +3,19 @@ The endpoint can also be explored via the *Swagger UI*, usually ``/api/doc``. """ -import logging.config -from datetime import datetime from decimal import Decimal from json import JSONDecodeError -from json import loads -from traceback import format_stack -from typing import Any -from typing import Dict +from datetime import datetime from typing import Optional from aiohttp import web -from aiohttp_jinja2 import template -from os_credits.auth import auth_required - -from os_credits.credits.base_models import Metric -from os_credits.influx.client import InfluxDBClient -from os_credits.influx.exceptions import InfluxDBError -from os_credits.log import internal_logger -from os_credits.perun.exceptions import GroupNotExistsError -from os_credits.settings import config -from os_credits.worker_helper import stop_worker, create_worker -from os_credits.perun.group import Group - - -@auth_required -async def delete_credits_left(request: web.Request) -> web.Response: - internal_logger.info(f"Called: {request.rel_url}") - influx_client: InfluxDBClient = request.app["influx_client"] - deleted_from_projects = await influx_client.delete_credits_left_measurements() - return web.json_response(deleted_from_projects) +from src.os_credits.db_client.client import TimescaleDBManager +from src.os_credits.log import views_logger +from src.os_credits.settings import config -@auth_required -async def delete_mb_and_vcpu_since(request: web.Request) -> web.Response: - internal_logger.info(f"Called: {request.rel_url}") - await stop_worker(request.app, 500) - try: - influx_client: InfluxDBClient = request.app["influx_client"] - since_date = request.query["since_date"] - datetime_format = "%Y-%m-%d %H:%M:%S" - since_date = datetime.strptime(since_date, datetime_format) - project_names = request.query["project_names"] - project_names = project_names.split(",") - since_date = int(since_date.timestamp()) - except KeyError as e: - internal_logger.exception(f"Exception when getting request information for " - f"deleting value history:\n" - f"{e}") - await create_worker(request.app) - return web.HTTPException(text="Key Error.") - except ValueError as e: - internal_logger.exception(f"Exception when getting request information for " - f"deleting value history:\n" - f"{e}") - await create_worker(request.app) - return web.HTTPException(text="Value Error.") - except Exception as e: - internal_logger.exception(f"Exception when getting request information for " - f"deleting value history:\n" - f"{e}") - await create_worker(request.app) - return web.HTTPException(text="Exception.") - - return_list = [] - internal_logger.info(f"Trying to delete usage values for project: {project_names} " - f"since {since_date}.") - for project_name in project_names: - try: - last_timestamps = await influx_client.delete_mb_and_vcpu_measurements( - project_name, - since_date - ) - - if "project_name" not in last_timestamps \ - or "location_id" not in last_timestamps: - internal_logger.info(f"Could not find group {project_name} in " - f"influxdb!") - return_list.append({"error": f"Could not find '{project_name}' " - f"in influxdb."}) - continue - perun_group = Group(last_timestamps["project_name"], - int(last_timestamps["location_id"])) - await perun_group.connect() - last_mb = last_timestamps.get("last_mb", None) - if last_mb: - perun_group.credits_timestamps.value[ - "project_mb_usage" - ] = datetime.fromtimestamp(last_timestamps["last_mb"]["time"]) - else: - perun_group.credits_timestamps.value[ - "project_mb_usage" - ] = datetime.now() - last_vcpu = last_timestamps.get("last_vcpu", None) - if last_vcpu: - perun_group.credits_timestamps.value[ - "project_vcpu_usage" - ] = datetime.fromtimestamp(last_timestamps["last_vcpu"]["time"]) - else: - perun_group.credits_timestamps.value[ - "project_vcpu_usage" - ] = datetime.now() - await perun_group.save() - internal_logger.info(f"Deleted values and set timestamps for " - f"'{project_name}' with {last_timestamps}.") - return_list.append(last_timestamps) - except GroupNotExistsError as e: - internal_logger.warning( - "Could not resolve group with name `%s` against perun. %r", - project_name, e - ) - return_list.append({"error": f"Could not find perun group " - f"'{project_name}'."}) - continue - except Exception as e: - internal_logger.exception(f"Exception when deleting value history:\n" - f"{e}") - return_list.append({"error": f"Could not delete values for " - f"'{project_name}'."}) - continue - - await create_worker(request.app) - return web.json_response(return_list) +_DEFINITELY_PAST = datetime.min +_DEFINITELY_END = datetime.max async def ping(_: web.Request) -> web.Response: @@ -146,7 +37,7 @@ async def ping(_: web.Request) -> web.Response: 405: description: invalid HTTP Method """ - internal_logger.info(f"Called: {_.rel_url}") + views_logger.info(f"Called: {_.rel_url}") return web.Response(text="Pong") @@ -211,12 +102,12 @@ async def credits_history_api(request: web.Request) -> web.Response: 404: description: Could not find any history data. """ - internal_logger.info(f"Called: {request.rel_url}") + views_logger.info(f"Called: {request.rel_url}") datetime_format = "%Y-%m-%d %H:%M:%S" try: start_date = datetime.strptime(request.query["start_date"], datetime_format) except KeyError: - start_date = datetime.fromtimestamp(0) + start_date = _DEFINITELY_PAST except ValueError: raise web.HTTPBadRequest(reason="Invalid content for ``start_date``") try: @@ -224,7 +115,7 @@ async def credits_history_api(request: web.Request) -> web.Response: request.query["end_date"], datetime_format ) except KeyError: - end_date = None + end_date = _DEFINITELY_END except ValueError: raise web.HTTPBadRequest(reason="Invalid content for ``end_date``") if end_date and end_date <= start_date: @@ -238,187 +129,161 @@ async def credits_history_api(request: web.Request) -> web.Response: raise KeyError except KeyError: raise web.HTTPBadRequest(reason="No non-empty ``project_name`` provided") - influx_client: InfluxDBClient = request.app["influx_client"] + database_client: TimescaleDBManager = request.app["database_client"] time_column = [] credits_column = [] metric_column = [] - result = await influx_client.query_billing_history(project_name, since=start_date) - try: - async for point in result: - # entries are sorted by timestamp descending - if end_date: - if point.timestamp > end_date: - continue - time_column.append(point.timestamp.strftime(datetime_format)) - credits_column.append(float(point.credits_used)) - metric_column.append(point.metric_friendly_name) - except InfluxDBError: - raise web.HTTPBadRequest(reason="Invalid project name") + async with database_client.async_session() as session: + result = await database_client.get_credits_history(project_name, since=start_date, end=end_date, session=session) + for credits_entry in result: + time_column.append(credits_entry[0].time.strftime(datetime_format)) + credits_column.append(float(credits_entry[0].used_credits)) + metric_column.append(credits_entry[0].by_metric) # check whether any data were retrieved if not credits_column: # let's check whether the project has history at all - if await influx_client.project_has_history(project_name): - raise web.HTTPNoContent(reason="Try changing *_date parameters") raise web.HTTPNotFound(reason="No data available for given parameters.") return web.json_response( {"timestamps": time_column, "credits": credits_column, "metrics": metric_column} ) +# +# +# @template("credits_history.html.j2") +# async def credits_history(request: web.Request) -> Dict[str, Any]: +# """Shows a functional draft for visualization of a project's credits history. +# +# To generate test entries take a look at ``bin/generate_credits_history.py`` at the +# root of this project. +# """ +# internal_logger.info(f"Called: {request.rel_url}") +# return {"project_name": request.match_info["project_name"]} +# +# +# async def influxdb_write(request: web.Request) -> web.Response: +# """ +# Consumes the `Line Protocol +# `_ +# of InfluxDB. +# +# :param request: Incoming request with one or multiple *InfluxDB Line* instances. +# --- +# description: Used by InfluxDB to post subscription updates +# tags: +# - Service +# consumes: +# - text/plain +# parameters: +# - in: body +# +# name: line +# +# description: Point in Line Protocol format (https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial) +# schema: +# type: string +# +# example: weather,location=us-midwest temperature=82 1465839830100400200 +# required: true +# responses: +# 202: +# description: A corresponding task object will be created. See application log +# for further information +# """ # noqa (cannot fix long url) +# # .text() performs automatic decoding from bytes +# internal_logger.info(f"Called: {request.rel_url}") +# influxdb_lines = await request.text() +# # an unknown number of lines will be send, put them all into the queue +# for influx_line in influxdb_lines.splitlines(): +# await request.app["task_queue"].put(influx_line) +# internal_logger.debug( +# "Put %s into queue (%s elements)", +# influx_line, +# request.app["task_queue"].qsize(), +# ) +# # always answer 202 +# return web.HTTPAccepted() +# +# +# async def application_stats(request: web.Request) -> web.Response: +# """ +# API-Endpoint returning current stats of the running application +# --- +# description: Allows querying the application state. Should not be public accessible. +# tags: +# - Health check +# - Monitoring +# produces: +# - application/json +# parameters: +# - in: query +# name: verbose +# type: boolean +# default: false +# description: Include extended (computationally expensive) information +# responses: +# 200: +# description: Stats object +# schema: +# type: object +# required: [number_of_workers, queue_size, number_of_locks, uptime] +# properties: +# number_of_workers: +# type: integer +# description: Number of worker tasks as specified in config file +# queue_size: +# type: integer +# description: Number of tasks currently pending +# number_of_locks: +# type: integer +# description: Number of group/project locks inside the application, should +# correspond to the number of billed/groups/projects +# uptime: +# type: string +# description: Uptime, string representation of a python +# [`timedelta`]( +# https://docs.python.org/3/library/datetime.html#timedelta-objects) +# object +# task_stacks: +# type: object +# required: [worker-n] +# properties: +# worker-n: +# type: str +# description: Stack of the worker task +# group_locks: +# type: object +# required: [group_name] +# properties: +# group_name: +# type: str +# description: State of the group/project async-lock +# """ +# internal_logger.info(f"Called: {request.rel_url}") +# stats = { +# "number_of_workers": config["OS_CREDITS_WORKERS"], +# "queue_size": request.app["task_queue"].qsize(), +# "number_of_locks": len(request.app["group_locks"]), +# "uptime": str(datetime.now() - request.app["start_time"]), +# } +# if ( +# "verbose" in request.query +# and request.query["verbose"] +# and request.query["verbose"] != "false" +# ): +# stats.update( +# { +# "task_stacks": { +# name: [format_stack(stack)[0] for stack in task.get_stack()][0] +# for name, task in request.app["task_workers"].items() +# }, +# "group_locks": { +# key: repr(lock) for key, lock in request.app["group_locks"].items() +# }, +# } +# ) +# return web.json_response(stats) -@template("credits_history.html.j2") -async def credits_history(request: web.Request) -> Dict[str, Any]: - """Shows a functional draft for visualization of a project's credits history. - - To generate test entries take a look at ``bin/generate_credits_history.py`` at the - root of this project. - """ - internal_logger.info(f"Called: {request.rel_url}") - return {"project_name": request.match_info["project_name"]} - -async def influxdb_write(request: web.Request) -> web.Response: - """ - Consumes the `Line Protocol - `_ - of InfluxDB. - - :param request: Incoming request with one or multiple *InfluxDB Line* instances. - --- - description: Used by InfluxDB to post subscription updates - tags: - - Service - consumes: - - text/plain - parameters: - - in: body - - name: line - - description: Point in Line Protocol format (https://docs.influxdata.com/influxdb/v1.7/write_protocols/line_protocol_tutorial) - schema: - type: string - - example: weather,location=us-midwest temperature=82 1465839830100400200 - required: true - responses: - 202: - description: A corresponding task object will be created. See application log - for further information - """ # noqa (cannot fix long url) - # .text() performs automatic decoding from bytes - internal_logger.info(f"Called: {request.rel_url}") - influxdb_lines = await request.text() - # an unknown number of lines will be send, put them all into the queue - for influx_line in influxdb_lines.splitlines(): - await request.app["task_queue"].put(influx_line) - internal_logger.debug( - "Put %s into queue (%s elements)", - influx_line, - request.app["task_queue"].qsize(), - ) - # always answer 202 - return web.HTTPAccepted() - - -async def application_stats(request: web.Request) -> web.Response: - """ - API-Endpoint returning current stats of the running application - --- - description: Allows querying the application state. Should not be public accessible. - tags: - - Health check - - Monitoring - produces: - - application/json - parameters: - - in: query - name: verbose - type: boolean - default: false - description: Include extended (computationally expensive) information - responses: - 200: - description: Stats object - schema: - type: object - required: [number_of_workers, queue_size, number_of_locks, uptime] - properties: - number_of_workers: - type: integer - description: Number of worker tasks as specified in config file - queue_size: - type: integer - description: Number of tasks currently pending - number_of_locks: - type: integer - description: Number of group/project locks inside the application, should - correspond to the number of billed/groups/projects - uptime: - type: string - description: Uptime, string representation of a python - [`timedelta`]( - https://docs.python.org/3/library/datetime.html#timedelta-objects) - object - task_stacks: - type: object - required: [worker-n] - properties: - worker-n: - type: str - description: Stack of the worker task - group_locks: - type: object - required: [group_name] - properties: - group_name: - type: str - description: State of the group/project async-lock - """ - internal_logger.info(f"Called: {request.rel_url}") - stats = { - "number_of_workers": config["OS_CREDITS_WORKERS"], - "queue_size": request.app["task_queue"].qsize(), - "number_of_locks": len(request.app["group_locks"]), - "uptime": str(datetime.now() - request.app["start_time"]), - } - if ( - "verbose" in request.query - and request.query["verbose"] - and request.query["verbose"] != "false" - ): - stats.update( - { - "task_stacks": { - name: [format_stack(stack)[0] for stack in task.get_stack()][0] - for name, task in request.app["task_workers"].items() - }, - "group_locks": { - key: repr(lock) for key, lock in request.app["group_locks"].items() - }, - } - ) - return web.json_response(stats) - - -async def update_logging_config(request: web.Request) -> web.Response: - """ - Possibility to update logging configuration without restart - """ - internal_logger.info(f"Called: {request.rel_url}") - logging_json_text = await request.text() - try: - logging_config = loads(logging_json_text) - except JSONDecodeError as e: - raise web.HTTPBadRequest(reason=str(e)) - try: - logging.config.dictConfig(logging_config) - except Exception as e: - raise web.HTTPBadRequest(reason=str(e)) - return web.HTTPNoContent() - - -# Usage of class-based views would be nicer, unfortunately not yet supported by -# aiohttp-swagger async def get_metrics(_: web.Request) -> web.Response: """ Returns a JSON object describing the currently supported metrics and their per-hour @@ -456,10 +321,10 @@ async def get_metrics(_: web.Request) -> web.Response: type: str description: Human readable name of the metric. """ - internal_logger.info(f"Called: {_.rel_url}") + views_logger.info(f"Called: {_.rel_url}") metric_information = { - friendly_name: metric.api_information() - for friendly_name, metric in Metric.metrics_by_friendly_name.items() + metric_name: float(cost) + for metric_name, cost in config["METRICS_TO_BILL"].items() } return web.json_response(metric_information) @@ -471,13 +336,8 @@ async def costs_per_hour(request: web.Request) -> web.Response: $ curl localhost:8000/api/costs_per_hour \\ -H "Content-Type: application/json" \\ - -d '{"cpu":16,"ram":32768}' - - Or if you have `httpie `_ installed + -d '{"project_vcpu_usage":16,"project_mb_usage":32768}' - .. code-block:: console - - $ http -j :8000/api/costs_per_hour cpu:=16 ram:=32768 --- description: Given the submitted specs of one or multiple machines combined calculate the expected costs per hour. See the ``GET /api/metrics`` to retrieve @@ -496,24 +356,41 @@ async def costs_per_hour(request: web.Request) -> web.Response: schema: type: float """ - internal_logger.info(f"Called: {request.rel_url}") + views_logger.info(f"Called: {request.rel_url}") try: machine_specs = await request.json() except JSONDecodeError: raise web.HTTPBadRequest(reason="Invalid JSON") returned_costs_per_hour = Decimal(0) - for friendly_name, spec in machine_specs.items(): + for metric_name, spec in machine_specs.items(): try: + cost = Decimal(config["METRICS_TO_BILL"][metric_name]) spec = Decimal(spec) - returned_costs_per_hour += Metric.metrics_by_friendly_name[ - friendly_name - ].costs_per_hour(spec) + returned_costs_per_hour += (spec * cost).quantize(config["OS_CREDITS_PRECISION"]) except KeyError: - raise web.HTTPNotFound(reason=f"Unknown measurement `{friendly_name}`.") + raise web.HTTPNotFound(reason=f"Unknown measurement `{metric_name}`.") except TypeError: raise web.HTTPBadRequest( - reason=f"Parameter {friendly_name} had wrong type." + reason=f"Parameter {metric_name} had wrong type." ) return web.json_response( float(returned_costs_per_hour.quantize(config["OS_CREDITS_PRECISION"])) ) + + +async def get_current_credits(request: web.Request) -> web.Response: + try: + project_name = request.match_info["project_name"] + # Swagger UI sends '{project_name}' if none is specified -.-' + if project_name == "{project_name}" or not project_name.strip(): + raise KeyError + except KeyError: + raise web.HTTPBadRequest(reason="No non-empty ``project_name`` provided") + database_client: TimescaleDBManager = request.app["database_client"] + async with database_client.async_session() as session: + project = await database_client.get_project(project_name, session) + if not project: + raise web.HTTPNotFound(reason=f"No credits found for {project_name}.") + return web.json_response( + {"current_credits": float(project.used_credits)} + ) diff --git a/src/os_credits/worker_helper.py b/src/os_credits/worker_helper.py index ba8c858..9b5a44e 100644 --- a/src/os_credits/worker_helper.py +++ b/src/os_credits/worker_helper.py @@ -1,25 +1,33 @@ -from os_credits.credits.tasks import worker +import asyncio + +from src.os_credits.db_client.tasks import consumer_worker, put_projects_into_queue from aiohttp import web from asyncio import TimeoutError from asyncio import create_task from asyncio import gather from asyncio import wait_for -from os_credits.log import internal_logger +from .log import internal_logger + + +async def create_producer(app: web.Application) -> None: + app["producer_worker"] = { + f"producer_worker-0": create_task(put_projects_into_queue(app)) + } -async def create_worker(app: web.Application) -> None: +async def create_consumer_worker(app: web.Application) -> None: """Creates :ref:`Task Workers` to process items put into the :ref:`Task Queue`. The amount of them can configured via ``OS_CREDITS_WORKERS``, see :ref:`Settings`. """ - app["task_workers"] = { - f"worker-{i}": create_task(worker(f"worker-{i}", app)) + app["consumer_workers"] = { + f"consumer_worker-{i}": create_task(consumer_worker(f"consumer_worker-{i}", app)) for i in range(app["config"]["OS_CREDITS_WORKERS"]) } - internal_logger.info("Created %d workers", len(app["task_workers"])) + internal_logger.info("Created %d consumer workers", len(app["consumer_workers"])) -async def stop_worker(app: web.Application, queue_timeout: int = 120) -> None: +async def stop_consumer_worker(app: web.Application, queue_timeout: int = 120) -> None: """Tries to shutdown all :ref:`Task Workers` gracefully by first emptying the :ref:`Task Queue` before cancelling the workers. @@ -35,9 +43,9 @@ async def stop_worker(app: web.Application, queue_timeout: int = 120) -> None: except TimeoutError: internal_logger.warning( "Waited %d seconds for all remaining tasks to be processed, killing " - "workers now.", + "consumer workers now.", queue_timeout, ) - for task in app["task_workers"].values(): + for task in app["consumer_workers"].values(): task.cancel() - await gather(*app["task_workers"].values(), return_exceptions=True) + await gather(*app["consumer_workers"].values(), return_exceptions=True) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 9ae4102..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,79 +0,0 @@ -from asyncio import sleep -from importlib import reload -from logging import getLogger - -from aiohttp.client_exceptions import ClientOSError -from aiohttp.client_exceptions import ServerDisconnectedError -from pytest import fixture - -from os_credits.influx.client import InfluxDBClient -from os_credits.perun.group import Group -from os_credits.settings import config - -# how many credits does every group, created during test runs, have -TEST_INITIAL_CREDITS_GRANTED = 200 - - -@fixture(name="perun_test_group") -def fixture_perun_test_group() -> Group: - # these are real objects inside Perun so do not change them, otherwise all perun - # tests will fail - group_id = 11482 - group_name = "os_credits_test" - resource_id = 8676 - # resource_name = "test" - - group = Group(group_name, resource_id) - # set the group_id already - group.id = group_id - return group - - -@fixture(name="settings_reload_after_use", autouse=True) -def fixture_settings_reload_after_use(): - "Make sure that settings are reset after every run" - from os_credits import settings - - yield - reload(settings) - - -@fixture(name="smtpserver") -def fixture_smtpserver(smtpserver, monkeypatch): - from os_credits import settings - - monkeypatch.setenv("MAIL_SMTP_SERVER", str(smtpserver.addr[0])) - monkeypatch.setenv("MAIL_SMTP_PORT", str(smtpserver.addr[1])) - monkeypatch.setenv("MAIL_NOT_STARTTLS", "1") - reload(settings) - return smtpserver - - -@fixture(name="influx_client") -async def fixture_influx_client(loop): - - influx_client = InfluxDBClient(loop=loop) - influx_client.db = config["INFLUXDB_DB"] - getLogger("aioinflux").level = 0 - await influx_client.ping() - # in production the application cannot create any databases since it does not admin - # access to the InfluxDB and HTTP_AUTH is enabled, see the `project_usage` repo - await influx_client.query(f"create database {config['CREDITS_HISTORY_DB']}") - while True: - try: - await influx_client.ping() - break - except (ClientOSError, ServerDisconnectedError): - print("Sleeping for 1 second until InfluxDB is up") - await sleep(1) - yield influx_client - # clear all data from pytest and credits_history_db - await influx_client.query("drop series from /.*/", db=config["INFLUXDB_DB"]) - # fails sometimes, ignore - try: - await influx_client.query( - "drop series from /.*/", db=config["CREDITS_HISTORY_DB"] - ) - except Exception: - pass - await influx_client.close() diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml deleted file mode 100644 index 57b3ca7..0000000 --- a/tests/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- -version: "3.5" -services: - influxdb: - image: "influxdb:1.7-alpine" - env_file: - - test.env - ports: - - "11184:8086" - healthcheck: - test: wget http://localhost:8086/ping -qO /dev/null || return 1 - interval: 2m - timeout: 10s - retries: 3 - start_period: 5s diff --git a/tests/patches.py b/tests/patches.py deleted file mode 100644 index 26f28c5..0000000 --- a/tests/patches.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import annotations - -from collections import defaultdict -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple - -from os_credits.perun.attributes import DenbiCreditsGranted -from os_credits.perun.base_attributes import PerunAttribute - -from .conftest import TEST_INITIAL_CREDITS_GRANTED - - -# replaces `os_credits.perun.groupsManager.get_group_by_name` -async def get_group_by_name(name: str) -> Dict[str, Any]: - # create fake 8 digit id from name, must not be random since used as key in - # _test_mode_group_attributes - group_id = abs(hash(name) % (10 ** 8)) - return { - "id": group_id, - "createdAt": "2000-01-01 00:00:00.000000", - "createdBy": "unknown@example.com", - "modifiedAt": "2000-01-01 00:00:00.000000", - "modifiedBy": "unknown@example.com", - "createdByUid": 0, - "modifiedByUid": 0, - "voId": 0, - "parentGroupId": None, - "name": name, - "description": f"Dummy offline group ({group_id})", - "shortName": name, - "beanName": "Group", - } - - -# Used during test runs to emulate Perun's storage capabilities -_test_mode_resource_attributes: Dict[ - Tuple[int, int], Dict[str, Dict[str, Any]] -] = defaultdict(lambda: {}) -# Insert any initial values by using a defaultdict -_test_mode_group_attributes: Dict[int, Dict[str, Dict[str, Any]]] = defaultdict( - lambda: { - DenbiCreditsGranted.get_full_name(): DenbiCreditsGranted( - value=TEST_INITIAL_CREDITS_GRANTED - ).to_perun_dict() - } -) - - -# replaces `os_credits.perun.attributesManager.get_resource_bound_attributes` -async def get_resource_bound_attributes( - group_id: int, resource_id: int, attribute_full_names: Optional[List[str]] = None -) -> List[Dict[str, Any]]: - return [ - attribute - for attribute in _test_mode_resource_attributes[ - (group_id, resource_id) - ].values() - ] - - -async def get_attributes( - group_id: int, attribute_full_names: Optional[List[str]] = None -) -> List[Dict[str, Any]]: - return [attribute for attribute in _test_mode_group_attributes[group_id].values()] - - -async def is_assigned_resource(self) -> bool: - """ - """ - return True - - -# Original function currently not in use -async def set_attribute(group_id: int, attribute: PerunAttribute[Any]) -> None: - _test_mode_group_attributes[group_id][ - attribute.friendlyName - ] = attribute.to_perun_dict() - - -async def set_resource_bound_attributes( - group_id: int, resource_id: int, attributes: List[PerunAttribute[Any]] -) -> None: - for attribute in attributes: - _test_mode_resource_attributes[(group_id, resource_id)][ - attribute.friendlyName - ] = attribute.to_perun_dict() - - -async def set_attributes(group_id: int, attributes: List[PerunAttribute[Any]]) -> None: - for attribute in attributes: - _test_mode_group_attributes[group_id][ - attribute.friendlyName - ] = attribute.to_perun_dict() diff --git a/tests/test.env b/tests/test.env deleted file mode 100644 index b194910..0000000 --- a/tests/test.env +++ /dev/null @@ -1,8 +0,0 @@ -OS_CREDITS_WORKERS=1 -INFLUXDB_PORT=11184 -INFLUXDB_HOST=localhost -INFLUXDB_USER_PASSWORD='' -INFLUXDB_USER='' -INFLUXDB_DB=pytest -INFLUXDB_REPORTING_DISABLED=true -INFLUXDB_HTTP_AUTH_ENABLED=false diff --git a/tests/test_application.py b/tests/test_application.py deleted file mode 100644 index 181ede1..0000000 --- a/tests/test_application.py +++ /dev/null @@ -1,529 +0,0 @@ -""" -Contains test which launch the whole application and simulate incoming data or external -requests against it. They all require the aiohttp_client and influx_client fixture, the -latter even if they do not the InfluxDB functionality since the app does check the -existence of certain databases at startup. -""" -from dataclasses import dataclass -from dataclasses import replace -from datetime import datetime -from datetime import timedelta -from decimal import Decimal -from importlib import reload -from typing import Type - -from pytest import fixture - -import os_credits.perun.attributesManager -import os_credits.perun.group -from os_credits.credits.models import BillingHistory - -from . import patches -from .patches import get_attributes -from .patches import get_group_by_name -from .patches import get_resource_bound_attributes -from .patches import is_assigned_resource -from .patches import set_attributes -from .patches import set_resource_bound_attributes - - -@fixture -def os_credits_offline(monkeypatch): - """Applies patches to emulate *Perun* offline. Online tests are done in test_perun. - """ - - monkeypatch.setattr( - os_credits.perun.group, - "get_resource_bound_attributes", - get_resource_bound_attributes, - ) - monkeypatch.setattr( - os_credits.perun.group, - "set_resource_bound_attributes", - set_resource_bound_attributes, - ) - monkeypatch.setattr(os_credits.perun.group, "get_attributes", get_attributes) - monkeypatch.setattr(os_credits.perun.group, "set_attributes", set_attributes) - monkeypatch.setattr( - os_credits.perun.group.Group, "is_assigned_resource", is_assigned_resource - ) - monkeypatch.setattr(os_credits.perun.group.Group.save, "__defaults__", (True,)) - - monkeypatch.setattr(os_credits.perun.group, "get_group_by_name", get_group_by_name) - yield - # reset internal storage of group values by reloading the module - reload(patches) - - -@fixture(name="start_date") -def fixture_start_date(): - return datetime(2019, 1, 1) - - -@fixture(name="usage_delta") -def fixture_usage_delta(): - return 5 - - -@fixture(name="MeasurementClass", scope="session") -def fixture_measurement_class(): - from os_credits.credits.base_models import ( - Metric, - TotalUsageMetric, - UsageMeasurement, - ) - - test_metric_name = "whole_run_test_1" - - class _TestMetric( - TotalUsageMetric, name=test_metric_name, friendly_name=test_metric_name - ): - CREDITS_PER_VIRTUAL_HOUR = Decimal("1") - description = "Test Metric 1 for whole run test" - - @dataclass(frozen=True) - class _TestMeasurement(UsageMeasurement): - metric: Type[Metric] = _TestMetric - - return _TestMeasurement - - -@fixture -async def app_with_first_write( - aiohttp_client, - os_credits_offline, - influx_client, - perun_test_group, - MeasurementClass, - start_date, -): - from os_credits.main import create_app - - app = await create_app(_existing_influxdb_client=influx_client) - http_client = await aiohttp_client(app) - - measurement = MeasurementClass( - measurement=MeasurementClass.metric.name, - timestamp=start_date, - location_id=perun_test_group.resource_id, - project_name=perun_test_group.name, - value=100, - ) - - await write_and_mirror(app, http_client, influx_client, measurement) - await perun_test_group.connect() - assert ( - perun_test_group.credits_used.value == 0 - ), "Initial value of credits_used was not set" - assert ( - perun_test_group.credits_timestamps.value[measurement.measurement] == start_date - ), "Timestamp from measurement was not stored correctly in group" - return app, http_client, influx_client, measurement - - -async def write_and_mirror(app, http_client, influx_client, measurement): - # async def write_and_mirror(http_client, influx_client, measurement): - if influx_client: - await influx_client.write(measurement) - resp = await http_client.post("/write", data=measurement.to_lineprotocol()) - assert resp.status == 202 - # wait until request has been processed, indicated by the task finally calling - # `task_done` - await app["task_queue"].join() - - -async def get_billing_history(perun_test_group, influx_client): - return [ - p - async for p in await influx_client.query_billing_history(perun_test_group.name) - ] - - -async def test_startup(aiohttp_client, influx_client): - "Test startup of the application and try to connect to its `/ping` endpoint" - from os_credits.main import create_app - - app = await create_app(_existing_influxdb_client=influx_client) - client = await aiohttp_client(app) - resp = await client.get("/ping") - text = await resp.text() - assert (200, "Pong") == (resp.status, text), "/ping endpoint failed" - - -async def test_credits_endpoint(aiohttp_client, influx_client): - """Test the `get_metrics` endpoint responsible for calculating expected costs per - hour of given resources""" - from os_credits.main import create_app - from os_credits.credits.base_models import TotalUsageMetric - - app = await create_app(_existing_influxdb_client=influx_client) - client = await aiohttp_client(app) - - class _MetricA(TotalUsageMetric, name="metric_a", friendly_name="metric_a"): - CREDITS_PER_VIRTUAL_HOUR = Decimal("1.3") - description = "Test metric A" - - @classmethod - def api_information(cls): - return { - "type": "str", - "description": cls.description, - "name": cls.name, - "friendly_name": cls.friendly_name, - } - - class _MetricB(TotalUsageMetric, name="metric_b", friendly_name="metric_b"): - CREDITS_PER_VIRTUAL_HOUR = Decimal("1") - description = "Test metric B" - - get_metrics_url = app.router["get_metrics"].url_for() - resp = await client.get(get_metrics_url) - metrics = await resp.json() - assert resp.status == 200 - assert metrics["metric_a"] == { - "description": "Test metric A", - "type": "str", - "name": "metric_a", - "friendly_name": "metric_a", - }, "Returned wrong body" - assert metrics["metric_b"] == { - "description": "Test metric B", - "type": "int", - "name": "metric_b", - "friendly_name": "metric_b", - }, "Returned wrong body" - - costs_per_hour_url = app.router["costs_per_hour"].url_for() - resp = await client.post(costs_per_hour_url, json={"DefinitelyNotExisting": "test"}) - assert resp.status == 404, "Accepted invalid data" - - resp = await client.post(costs_per_hour_url, json={"metric_a": 3, "metric_b": 2}) - assert ( - resp.status == 200 and await resp.json() == 2 * 1 + 3 * 1.3 - ), "Rturned wrong result" - - -async def test_regular_run( - app_with_first_write, perun_test_group, MeasurementClass, usage_delta, start_date -): - """Tests the complete workflow of the application without any expected errors. - - Incoming data of the InfluxDB are simulated two times to trigger different - scenarios (first measurement vs second measurement)""" - app, http_client, influx_client, measurement1 = app_with_first_write - assert ( - perun_test_group.credits_timestamps.value[MeasurementClass.metric.name] - == start_date - ), "Timestamp from measurement was not stored correctly in group" - # let's send the second measurement - measurement2 = replace( - measurement1, - timestamp=start_date + timedelta(days=7), - value=measurement1.value + usage_delta, - ) - billing_point = BillingHistory( - measurement=perun_test_group.name, - timestamp=measurement2.timestamp, - credits_left=perun_test_group.credits_granted.value - usage_delta, - metric_name=measurement2.metric.name, - metric_friendly_name=measurement2.metric.friendly_name, - ) - await write_and_mirror(app, http_client, influx_client, measurement2) - await perun_test_group.connect() - billing_points = await get_billing_history(perun_test_group, influx_client) - assert perun_test_group.credits_timestamps.value[ - MeasurementClass.metric.name - ] == start_date + timedelta( - days=7 - ), "Timestamp from measurement was not stored correctly in group" - # since our CREDITS_PER_VIRTUAL_HOUR are 1 - assert perun_test_group.credits_used.value == usage_delta - assert [billing_point] == billing_points - - -async def test_50_percent_notification( - app_with_first_write, - perun_test_group, - smtpserver, - usage_delta, - start_date, - MeasurementClass, -): - """Tests the complete workflow of the application without any expected errors but - let the group fall under 50% of its granted credits. - """ - app, http_client, influx_client, measurement1 = app_with_first_write - # let's send the second measurement - measurement2 = replace( - measurement1, - timestamp=start_date + timedelta(days=7), - value=measurement1.value + usage_delta, - ) - half_of_granted_credits = Decimal(perun_test_group.credits_granted.value / 2) - perun_test_group.credits_used.value = half_of_granted_credits - await perun_test_group.save() - - await write_and_mirror(app, http_client, influx_client, measurement2) - await perun_test_group.connect() - billing_point = BillingHistory( - measurement=perun_test_group.name, - timestamp=measurement2.timestamp, - credits_left=half_of_granted_credits - usage_delta, - metric_name=measurement2.metric.name, - metric_friendly_name=measurement2.metric.friendly_name, - ) - billing_points = await get_billing_history(perun_test_group, influx_client) - assert perun_test_group.credits_timestamps.value[ - MeasurementClass.metric.name - ] == start_date + timedelta( - days=7 - ), "Timestamp from measurement was not stored correctly in group" - # since our CREDITS_PER_VIRTUAL_HOUR are 1 - assert perun_test_group.credits_used.value == half_of_granted_credits + usage_delta - assert [ - billing_point - ] == billing_points, "Billing history has been stored incorrectly" - assert len(smtpserver.outbox) == 1, "No notification has been send" - - -async def test_exception_during_send_notification( - perun_test_group, - aiohttp_client, - os_credits_offline, - influx_client, - monkeypatch, - MeasurementClass, - start_date, -): - """Makes sure that any error during sending of the notification does not crash the - worker. Should also guarantee that no exception raised inside the original - :func:`process_influx_line` can crash the worker. - - The sending fails because no smtpserver can be reached since the fixture is not - requested. - """ - from os_credits.notifications import EmailNotificationBase - from os_credits.main import create_app - import os_credits.credits.tasks - - # cannot use `app_with_first_write` since we have to monkeypatch first - def fake_process_influx_line(*args, **kwargs): - raise EmailNotificationBase(None, "test") - - measurement1 = MeasurementClass( - measurement=MeasurementClass.metric.name, - timestamp=start_date, - location_id=perun_test_group.resource_id, - project_name=perun_test_group.name, - value=100, - ) - - monkeypatch.setattr( - os_credits.credits.tasks, "process_influx_line", fake_process_influx_line - ) - - app = await create_app(_existing_influxdb_client=influx_client) - http_client = await aiohttp_client(app) - - resp = await http_client.post("/write", data=measurement1.to_lineprotocol()) - assert resp.status == 202 - await app["task_queue"].join() - # ugly code to access only worker task independent of its name - # done() does also return true if the task exited with an exception - assert not app["task_workers"][list(app["task_workers"].keys())[0]].done() - - -async def test_measurement_from_the_past( - app_with_first_write, perun_test_group, usage_delta, start_date, MeasurementClass -): - """Tests the correct behaviour in case of incoming measurements whose timestamps are - not older than the stored one, which should never happen... But you never know""" - - app, http_client, influx_client, measurement1 = app_with_first_write - # let's send the second measurement - measurement2 = replace(measurement1, value=measurement1.value + usage_delta) - - await write_and_mirror(app, http_client, influx_client, measurement2) - await influx_client.write(measurement2) - resp = await http_client.post("/write", data=measurement2.to_lineprotocol()) - assert resp.status == 202 - # wait until request has been processed, indicated by the task finally calling - # `task_done` - await app["task_queue"].join() - await perun_test_group.connect() - billing_points = await get_billing_history(perun_test_group, influx_client) - assert ( - perun_test_group.credits_timestamps.value[MeasurementClass.metric.name] - == start_date - ), "Timestamp of metric was updated although the measurement was invalid" - assert perun_test_group.credits_used.value == 0 - assert [] == billing_points - - -async def test_equal_usage_values( - app_with_first_write, perun_test_group, start_date, MeasurementClass -): - """In contrast to :func:`test_regular_run` the second measurement does not have a - higher usage value than the first one""" - - app, http_client, influx_client, measurement1 = app_with_first_write - # let's send the second measurement - measurement2 = replace(measurement1, timestamp=start_date + timedelta(days=7)) - - await write_and_mirror(app, http_client, influx_client, measurement2) - await perun_test_group.connect() - assert ( - perun_test_group.credits_timestamps.value[MeasurementClass.metric.name] - == start_date - ), "Timestamp was updated although the measurement did not cause any billing" - assert ( - perun_test_group.credits_used.value == 0 - ), "Group has been billed incorrectly, no changes expected" - - -async def test_no_billing_due_to_rounding( - aiohttp_client, os_credits_offline, influx_client, perun_test_group, start_date -): - """The measurements are all valid but no credits are billed and no timestamps - updated when the second measurement is processed since the costs of the metric are - so low they get lost when rounding according to given precision. - - Depending on the chosen rounding strategy multiple measurements have to processed - before their accumulated usage delta leads to a billing.""" - from os_credits.main import create_app - from os_credits.settings import config - from os_credits.credits.base_models import ( - Metric, - TotalUsageMetric, - UsageMeasurement, - ) - - test_metric_name = "whole_run_test_cheap_1" - - class _TestMetricCheap( - TotalUsageMetric, name=test_metric_name, friendly_name=test_metric_name - ): - # by setting the costs per hour this way we can be sure that the first billings - # will be rounded to zero - CREDITS_PER_VIRTUAL_HOUR = config["OS_CREDITS_PRECISION"] * Decimal("10") ** -1 - description = "Test Metric 1 for whole run test" - - @dataclass(frozen=True) - class _TestMeasurementCheap(UsageMeasurement): - metric: Type[Metric] = _TestMetricCheap - - measurement = _TestMeasurementCheap( - measurement=test_metric_name, - timestamp=start_date, - location_id=perun_test_group.resource_id, - project_name=perun_test_group.name, - value=100, - ) - usage_delta = 1 - - app = await create_app(_existing_influxdb_client=influx_client) - http_client = await aiohttp_client(app) - - await write_and_mirror(app, http_client, influx_client, measurement=measurement) - # with default rounding Strategy ROUND_TO_HALF_EVEN - # https://en.wikipedia.org/wiki/Rounding#Round_half_to_even - # the following measurements will not cause any bills - # choosing the ranges according to the amount of the credits to bill they accumulate - for i in range(1, 6): - measurement = replace( - measurement, - timestamp=measurement.timestamp + timedelta(days=7), - value=measurement.value + usage_delta, - ) - - await write_and_mirror(app, http_client, influx_client, measurement) - await perun_test_group.connect() - billing_points = await get_billing_history(perun_test_group, influx_client) - assert ( - perun_test_group.credits_timestamps.value[test_metric_name] == start_date - ), "Timestamp from measurement was updated although no credits were billed" - assert ( - perun_test_group.credits_used.value == 0 - ), """Credits were billed although this should not have happened given the required - precision""" - assert billing_points == [] - # this measurement should lead to a bill - measurement = replace( - measurement, - timestamp=measurement.timestamp + timedelta(days=7), - value=measurement.value + usage_delta, - ) - await write_and_mirror(app, http_client, influx_client, measurement) - await perun_test_group.connect() - billing_points = await get_billing_history(perun_test_group, influx_client) - expected_credits = config["OS_CREDITS_PRECISION"] - billing_point = BillingHistory( - measurement=perun_test_group.name, - timestamp=measurement.timestamp, - credits_left=( - Decimal(perun_test_group.credits_granted.value) - - config["OS_CREDITS_PRECISION"] - ), - metric_name=measurement.metric.name, - metric_friendly_name=measurement.metric.friendly_name, - ) - assert ( - perun_test_group.credits_timestamps.value[test_metric_name] - == measurement.timestamp - ), "Timestamp from measurement was updated although no credits were billed" - assert ( - perun_test_group.credits_used.value == expected_credits - ), """Credits were billed although this should not have happened given the required - precision""" - assert billing_points == [billing_point] - - -async def test_missing_previous_values( - aiohttp_client, - os_credits_offline, - influx_client, - perun_test_group, - MeasurementClass, - start_date, - usage_delta, -): - """Tests the complete workflow of the application without any expected errors. - - Incoming data of the InfluxDB are simulated two times to trigger different - scenarios (first measurement vs second measurement)""" - from os_credits.main import create_app - - app = await create_app(_existing_influxdb_client=influx_client) - http_client = await aiohttp_client(app) - measurement1 = MeasurementClass( - measurement=MeasurementClass.metric.name, - timestamp=start_date, - location_id=perun_test_group.resource_id, - project_name=perun_test_group.name, - value=100, - ) - - # do not store the measurement in the InfluxDB (in contrast to `test_regular_run`) - # since we want to test the behaviour where the entry corresponding to the timestamp - # stored inside the group does not exist inside the InfluxDB (anymore) - await write_and_mirror( - app, http_client, influx_client=None, measurement=measurement1 - ) - # let's send the second measurement - measurement2 = replace( - measurement1, - timestamp=start_date + timedelta(days=7), - value=measurement1.value + usage_delta, - ) - - await write_and_mirror(app, http_client, influx_client, measurement2) - await perun_test_group.connect() - assert perun_test_group.credits_timestamps.value[ - MeasurementClass.metric.name - ] == start_date + timedelta( - days=7 - ), "Timestamp from measurement was not stored correctly in group" - assert ( - perun_test_group.credits_used.value == 0 - ), """Group has been billed although the values of the previous measurement could - not be retrieved""" diff --git a/tests/test_attribute.py b/tests/test_attribute.py deleted file mode 100644 index 0df29ed..0000000 --- a/tests/test_attribute.py +++ /dev/null @@ -1,100 +0,0 @@ -from typing import List -from typing import Optional - -import pytest - -from os_credits.perun.attributes import ContainerPerunAttribute -from os_credits.perun.attributes import DenbiCreditsGranted -from os_credits.perun.attributes import DenbiCreditTimestamps -from os_credits.perun.attributes import ScalarPerunAttribute -from os_credits.perun.base_attributes import PerunAttribute - - -@pytest.fixture(name="ContainerTestAttribute") -def fixtureContainerTestAttribute(): - class ContainerTestAttribute( - ContainerPerunAttribute[List[str]], - perun_id=0000, - perun_friendly_name="myContainerTestAttr", - perun_type="test", - perun_namespace="test", - ): - def __init__(self, **kwargs: str) -> None: - super().__init__(**kwargs) - - return ContainerTestAttribute - - -@pytest.fixture(name="ScalarTestAttribute") -def fixtureScalarTestAttribute(): - class ScalarTestAttribute( - ScalarPerunAttribute[Optional[str]], - perun_id=0000, - perun_friendly_name="myScalarTestAttr", - perun_type="test", - perun_namespace="test", - ): - def __init__(self, **kwargs: str) -> None: - super().__init__(**kwargs) - - return ScalarTestAttribute - - -def test_scalar_attribute_fixed_type(ScalarTestAttribute): - my_attr = ScalarTestAttribute(value="test_str", displayName="test") - assert not my_attr.has_changed - my_attr.value = "1234" - assert ( - my_attr.value == "1234" - ), "Changing value without changing type is wrongly forbidden" - assert my_attr.has_changed - with pytest.raises(TypeError): - my_attr.value = 1234 - - -def test_container_attribute_read_only(ContainerTestAttribute): - """Only the contents of container attributes are allowed to change""" - my_attr = ContainerTestAttribute(value=["lala", "1234"], displayName="test") - assert not my_attr.has_changed - my_attr.value.append("test2") - with pytest.raises(AttributeError): - my_attr.value = 1234 - assert my_attr.has_changed - - -def test_container_attribute_not_none_value(ContainerTestAttribute): - """In case of value=None a container attribute must initialize `value` with an - empty container (List/Set/Tuple) since only its contents can be changed - """ - with pytest.raises(AttributeError): - ContainerTestAttribute(value=None, displayName="test") - - -def test_container_none_value_false(): - for attr_class in PerunAttribute.registered_attributes.values(): - try: - attr = attr_class(value=None) - except Exception: - # in case of attributes which do not allow empty values - continue - print("Testing falseness of empty", attr_class) - assert not bool( - attr - ), f"No provided value must evaluate to False, error for attribute {attr_class}" - - -def test_changed_indicator(): - my_attr = DenbiCreditTimestamps(value=None, displayName="test") - assert not my_attr.has_changed - my_attr.value.update({"test": "test"}) - assert my_attr.has_changed - my_attr.has_changed = False - assert not my_attr.has_changed - with pytest.raises(ValueError): - my_attr.has_changed = True - - -def test_credits_granted_read_only(): - credits_granted = DenbiCreditsGranted(value="100") - with pytest.raises(AttributeError): - credits_granted.value = 200 diff --git a/tests/test_calculations.py b/tests/test_calculations.py deleted file mode 100644 index f065846..0000000 --- a/tests/test_calculations.py +++ /dev/null @@ -1,94 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime -from datetime import timedelta -from decimal import Decimal -from typing import Type - -import pytest - -from os_credits.credits.base_models import Metric -from os_credits.credits.base_models import TotalUsageMetric -from os_credits.credits.base_models import UsageMeasurement -from os_credits.credits.billing import calculate_credits -from os_credits.credits.models import measurement_by_name -from os_credits.exceptions import CalculationResultError -from os_credits.exceptions import MeasurementError - -with pytest.raises(TypeError): - # Due to CREDITS_PER_VIRTUAL_HOUR being None - - class _TestMetric1(TotalUsageMetric, name="test_fail1", friendly_name="test_fail1"): - CREDITS_PER_VIRTUAL_HOUR = None # type: ignore - - -class _TestMetric2(TotalUsageMetric, name="test2", friendly_name="test2"): - CREDITS_PER_VIRTUAL_HOUR = Decimal("1") - - -class _TestMetric3(TotalUsageMetric, name="test3", friendly_name="test3"): - CREDITS_PER_VIRTUAL_HOUR = Decimal("2") - - @classmethod - def calculate_credits(cls, *, current_measurement, older_measurement): - return -10 - - -@dataclass(frozen=True) -class _TestMeasurement2(UsageMeasurement): - metric: Type[Metric] = _TestMetric2 - - -@dataclass(frozen=True) -class _TestMeasurement3(UsageMeasurement): - metric: Type[Metric] = _TestMetric3 - - -now = datetime.now() - -m21 = _TestMeasurement2( - measurement="test2", value=100.0, timestamp=now, project_name="", location_id=0 -) -m22 = _TestMeasurement2( - measurement="test2", - value=110.0, - timestamp=now + timedelta(hours=1), - project_name="", - location_id=0, -) - -m31 = _TestMeasurement3( - measurement="test3", value=100.0, timestamp=now, project_name="", location_id=0 -) -m32 = _TestMeasurement3( - measurement="test3", - value=110.0, - timestamp=now + timedelta(hours=1), - project_name="", - location_id=0, -) - - -def test_supported_measurements_error(): - with pytest.raises(ValueError): - measurement_by_name("definitelyNotSupported") - - -def test_internal_calculate_credits(): - with pytest.raises(TypeError): - # Measurements must be of same type - m21.metric.calculate_credits(current_measurement=m31, older_measurement=m21) - with pytest.raises(MeasurementError): - # Fails since m2 is NEWER instead of older - m21.metric.calculate_credits(current_measurement=m21, older_measurement=m22) - - -def test_public_calculate_credits(): - # test actual credits calculation - assert ( - 10 == calculate_credits(m21, m22) == calculate_credits(m22, m21) - ), "Actual credits calculation, automatically determining older measurement" - - with pytest.raises(CalculationResultError): - # this fails due to the custom `calculate_credits` function returning a - # negative amount of credits to bill - calculate_credits(m31, m32) diff --git a/tests/test_credits_history.py b/tests/test_credits_history.py deleted file mode 100644 index e1408bf..0000000 --- a/tests/test_credits_history.py +++ /dev/null @@ -1,84 +0,0 @@ -from datetime import datetime -from datetime import timedelta -from decimal import Decimal -from http import HTTPStatus - -from os_credits.credits.base_models import Credits -from os_credits.credits.models import BillingHistory -from os_credits.influx.client import InfluxDBClient -from os_credits.main import create_app - -datetime_format = "%Y-%m-%d %H:%M:%S" - - -async def test_api_endpoint(influx_client: InfluxDBClient, aiohttp_client): - now = datetime.now() - yesterday = now - timedelta(days=1) - tomorrow = now + timedelta(days=1) - - credits_left = Credits(Decimal(300)) - metric_name = metric_friendly_name = "test_history_metric" - project_name = "test_history_measurement" - point = BillingHistory( - measurement=project_name, - timestamp=now, - credits_left=credits_left, - metric_name=metric_name, - metric_friendly_name=metric_friendly_name, - ) - await influx_client.write_billing_history(point) - app = await create_app(_existing_influxdb_client=influx_client) - http_client = await aiohttp_client(app) - resp1 = await http_client.get( - app.router["api_credits_history"].url_for(project_name=project_name) - ) - resp2 = await http_client.get( - app.router["api_credits_history"] - .url_for(project_name=project_name) - .with_query( - { - "start_date": yesterday.strftime(datetime_format), - "end_date": tomorrow.strftime(datetime_format), - } - ) - ) - expected_resp = dict( - credits=["credits", point.credits_left], - metrics=["metrics", point.metric_friendly_name], - timestamps=["timestamps", point.timestamp.strftime(datetime_format)], - ) - assert resp1.status == resp2.status == HTTPStatus.OK - assert await resp1.json() == await resp2.json() == expected_resp - resp1 = await http_client.get( - app.router["api_credits_history"] - .url_for(project_name=project_name) - .with_query({"start_date": tomorrow.strftime(datetime_format)}) - ) - resp2 = await http_client.get( - app.router["api_credits_history"] - .url_for(project_name=project_name) - .with_query({"end_date": yesterday.strftime(datetime_format)}) - ) - assert resp1.status == resp2.status == HTTPStatus.NO_CONTENT - - -async def test_invalid_params(aiohttp_client, influx_client): - app = await create_app(_existing_influxdb_client=influx_client) - http_client = await aiohttp_client(app) - - resp = await http_client.get( - # a totally empty project_name would result in a 404 - app.router["api_credits_history"].url_for(project_name=" ") - ) - - assert resp.status == HTTPStatus.BAD_REQUEST, "Empty project_name accepted" - resp = await http_client.get( - app.router["api_credits_history"] - .url_for(project_name="unknown") - .with_query( - {"start_date": "2019-01-01 00:00:01", "end_date": "2019-01-01 00:00:00"} - ) - ) - assert ( - resp.status == HTTPStatus.BAD_REQUEST - ), "Accepted invalid combination of date params" diff --git a/tests/test_group.py b/tests/test_group.py deleted file mode 100644 index 34fc09b..0000000 --- a/tests/test_group.py +++ /dev/null @@ -1,10 +0,0 @@ -import pytest - -from os_credits.perun.attributes import DenbiCreditsUsed - - -def test_perun_attr_setattr(perun_test_group): - credits_used = DenbiCreditsUsed(value=200) - perun_test_group.credits_used = credits_used - with pytest.raises(AttributeError): - perun_test_group.credits_used = 5 diff --git a/tests/test_influx.py b/tests/test_influx.py deleted file mode 100644 index f7cef1e..0000000 --- a/tests/test_influx.py +++ /dev/null @@ -1,129 +0,0 @@ -from dataclasses import dataclass -from dataclasses import field -from datetime import datetime - -from aioinflux import iterpoints -from pytest import approx - -from os_credits.credits.models import BillingHistory -from os_credits.influx.client import InfluxDBClient -from os_credits.influx.model import InfluxDBPoint -from os_credits.settings import config - - -@dataclass(frozen=True) -class _TestPoint(InfluxDBPoint): - field1: str - tag1: str = field(metadata={"tag": True}) - - -async def test_missing_db_exception(influx_client): - influx_client.db = config["CREDITS_HISTORY_DB"] - await influx_client.drop_database() - assert ( - not await influx_client.ensure_history_db_exists() - ), "Did not detect missing database" - - -async def test_history_exists(influx_client): - - now = datetime.now() - - credits_left = 300 - metric_name = metric_friendly_name = "test_history_metric" - project_name = "test_history_measurement" - point = BillingHistory( - measurement=project_name, - timestamp=now, - credits_left=credits_left, - metric_name=metric_name, - metric_friendly_name=metric_friendly_name, - ) - await influx_client.write_billing_history(point) - assert await influx_client.project_has_history(project_name) - assert not await influx_client.project_has_history(f"not{project_name}") - - -def test_influx_line_conversion(): - - influx_line = b'measurement,tag1=test field1="test" 1553342599293000000' - - point1 = _TestPoint.from_lineprotocol(influx_line) - point2 = _TestPoint( - measurement="measurement", - field1="test", - tag1="test", - timestamp=datetime(2019, 3, 23, 13, 3, 19, 293000), - ) - assert point1 == point2, "Parsing from Line Protocol failed" - # time has to be compared separately - rest1, time1 = point1.to_lineprotocol().decode().rsplit(" ", 1) - rest2, time2 = influx_line.decode().rsplit(" ", 1) - # approx is necessary since we are losing some nanoseconds when converting - assert rest1 == rest2 and int(time1) == approx( - int(time2), 100 - ), "Construction of Line Protocol failed" - - -async def test_query_points(influx_client): - point = _TestPoint( - measurement="test_project_entries_query_measurement", - tag1="test_project_entries_query", - field1="test", - timestamp=datetime.now(), - ) - await influx_client.write(point) - previous_points = [ - point - async for point in influx_client.query_points( - point.measurement, type(point), db=influx_client.db - ) - ] - assert previous_points == [point] - - -async def test_influx_read_write(influx_client): - point2 = _TestPoint( - measurement="test_influx_read_write", - tag1="test_influx_read_write", - field1="test", - timestamp=datetime.now(), - ) - await influx_client.write(point2) - result = await influx_client.query("SELECT * FROM test_influx_read_write") - parsed_point = list(iterpoints(result, _TestPoint.from_iterpoint))[0] - assert parsed_point == point2 - - -async def test_bool_serializer(influx_client): - """Separate test since it is the only default serializer currently not in use - """ - - @dataclass(frozen=True) - class BoolTest(InfluxDBPoint): - t: bool - f: bool = field(metadata={"tag": True}) - - influx_line = b"bool_test,t=T f=FALSE 1553342599293000000" - - test1 = BoolTest.from_lineprotocol(influx_line) - test2 = BoolTest( - measurement="bool_test", - timestamp=datetime(2019, 3, 23, 13, 3, 19, 293000), - f=False, - t=True, - ) - assert test1 == test2 - await influx_client.write(test2) - result = await influx_client.query("SELECT * FROM bool_test") - parsed_point = list(iterpoints(result, BoolTest.from_iterpoint))[0] - assert parsed_point == test2 - - -def test_sanitize_parameter(): - bad_param = "'\"\\;" - sanitized_param = "\\'\\\"\\\\\\;" - - assert ( - InfluxDBClient.sanitize_parameter(bad_param) == sanitized_param - ), "Sanitization of parameters for InfluxDB query failed" diff --git a/tests/test_notifications.py b/tests/test_notifications.py deleted file mode 100644 index 3314000..0000000 --- a/tests/test_notifications.py +++ /dev/null @@ -1,187 +0,0 @@ -from asyncio import wait -from importlib import reload - -import pytest - - -@pytest.fixture -def notification_group(): - from os_credits.perun.attributes import ( - DenbiCreditsGranted, - DenbiCreditsUsed, - ToEmail, - ) - from os_credits.perun.group import Group - from .conftest import TEST_INITIAL_CREDITS_GRANTED - - test_group = Group("TestGroup") - test_group.email = ToEmail(value=["admin@project"]) - test_group.credits_used = DenbiCreditsUsed(value=str(TEST_INITIAL_CREDITS_GRANTED)) - test_group.credits_granted = DenbiCreditsGranted( - value=str(TEST_INITIAL_CREDITS_GRANTED) - ) - return test_group - - -@pytest.fixture -def NotificationClass(): - from os_credits.notifications import EmailNotificationBase, EmailRecipient - - class NotificationTest1(EmailNotificationBase): - body_template = "$credits_granted - $project" - subject_template = "Test: $credits_used" - to = {"test3@local", EmailRecipient.PROJECT_MAINTAINERS} - cc = {EmailRecipient.CLOUD_GOVERNANCE} - bcc = {"test2@local"} - - def __init__(self, group): - super().__init__(group, "") - - return NotificationTest1 - - -def test_detect_invalid_notifications(notification_group): - from os_credits.notifications import EmailNotificationBase - from os_credits.exceptions import ( - BrokenTemplateError, - MissingTemplateError, - MissingToError, - ) - - with pytest.raises(MissingTemplateError): - - class EmptySubjectTemplate(EmailNotificationBase): - body_template = "Hallo welt" - subject_template = "" - to = {"test@local"} - - with pytest.raises(MissingTemplateError): - - class MissingSubjectTemplate(EmailNotificationBase): - body_template = "Hallo welt" - to = {"test@local"} - - with pytest.raises(MissingToError): - - class MissingTo(EmailNotificationBase): - body_template = "Hallo $welt" - subject_template = "Broken $template" - - with pytest.raises(MissingToError): - - class InvalidTo(EmailNotificationBase): - body_template = "Hallo $welt" - subject_template = "Broken $template" - to = set() - - with pytest.raises(BrokenTemplateError): - - class BrokenTemplate(EmailNotificationBase): - body_template = "Hallo $100 welt" - subject_template = "Broken $ template" - to = {"test@local"} - - def __init__(self, group): - super().__init__(group, "") - - BrokenTemplate(notification_group).construct_message() - - -def test_notification_to_overwrite(NotificationClass, notification_group): - from os_credits.settings import config - - overwrite_mail = "overwrite@mail" - config["NOTIFICATION_TO_OVERWRITE"] = overwrite_mail - from os_credits import notifications - - # necessary since the module imports config and does not see the changes - reload(notifications) - from os_credits.notifications import EmailNotificationBase, EmailRecipient - - class NotificationClass(EmailNotificationBase): - body_template = "$credits_granted - $project" - subject_template = "Test: $credits_used" - to = {"test3@local", EmailRecipient.PROJECT_MAINTAINERS} - cc = {EmailRecipient.CLOUD_GOVERNANCE} - bcc = {"test2@local"} - - def __init__(self, group): - super().__init__(group, "") - - notification = NotificationClass(notification_group) - msg = notification.construct_message() - assert ( - msg["Cc"] == msg["Bcc"] == None - ), "Cc and Bcc not empty despite NOTIFICATION_TO_OVERWRITE" - assert ( - msg["To"] == overwrite_mail - ), "NOTIFICATION_TO_OVERWRITE not applied correctly" - del config["NOTIFICATION_TO_OVERWRITE"] - - -def test_message_construction(NotificationClass, notification_group): - """Require smtpserver fixture to ensure that the value of - config['CLOUD_GOVERNANCE_MAIL'] is set""" - - from os_credits.settings import config - - notification = NotificationClass(notification_group) - msg = notification.construct_message() - # using sets to test independent of order - assert set(msg["To"].split(",")) == set( - ("test3@local", "admin@project") - ), "Wrong To in header of constructed message" - assert ( - msg["Cc"] == config["CLOUD_GOVERNANCE_MAIL"] - ), "Wrong Cc in header of constructed message" - assert ( - msg["Bcc"] == "test2@local" - ), "Wrong Bcc in header of construct_message message" - assert ( - msg["Subject"] == f"Test: {notification_group.credits_used.value}" - ), "Bad substitution of Subject" - assert ( - msg._payload == f"{notification_group.credits_granted.value} - TestGroup" - ), "Bad substitution of Body" - - -async def test_sending(smtpserver, loop, NotificationClass, notification_group): - from os_credits.notifications import EmailNotificationBase, send_notification - - notification = NotificationClass(notification_group) - - with pytest.raises(EmailNotificationBase): - raise notification - assert len(smtpserver.outbox) == 0 - - await wait({loop.create_task(send_notification(notification))}) - assert len(smtpserver.outbox) == 1 - - -def test_construction_with_missing_placeholder(NotificationClass, notification_group): - """Test whether a notification with an unresolvable placeholder in a template - constructs a message with the placeholder unresolved. Requires the smtpserver - fixture to ensure that the value of config['CLOUD_GOVERNANCE_MAIL'] is set""" - - class NotificationWithUnresolvableTemplate(NotificationClass): - subject_template = "$unresolvable" - - def __init__(self, group): - super().__init__(group) - - notification = NotificationWithUnresolvableTemplate(notification_group) - msg = notification.construct_message() - assert msg["Subject"] == NotificationWithUnresolvableTemplate.subject_template - - -def test_placeholder_overwrite_default(NotificationClass, notification_group): - class NotificationWithCustomPlaceholder(NotificationClass): - subject_template = "$credits_used" - custom_placeholders = {"credits_used": "MyPlaceholder"} - - def __init__(self, group): - super().__init__(group) - - notification = NotificationWithCustomPlaceholder(notification_group) - msg = notification.construct_message() - assert msg["Subject"] == "MyPlaceholder" diff --git a/tests/test_perun.py b/tests/test_perun.py deleted file mode 100644 index 6f212e1..0000000 --- a/tests/test_perun.py +++ /dev/null @@ -1,113 +0,0 @@ -from importlib import reload -from os import getenv -from random import randint - -import pytest - -from os_credits.perun.attributes import DenbiCreditsUsed -from os_credits.perun.attributes import DenbiCreditTimestamps -from os_credits.perun.attributes import ToEmail -from os_credits.perun.attributesManager import get_attributes -from os_credits.perun.attributesManager import get_resource_bound_attributes -from os_credits.perun.attributesManager import set_attributes -from os_credits.perun.attributesManager import set_resource_bound_attributes -from os_credits.perun.exceptions import BadCredentialsException -from os_credits.perun.exceptions import DenbiCreditsGrantedMissing -from os_credits.perun.exceptions import GroupNotExistsError -from os_credits.perun.exceptions import GroupResourceNotAssociatedError -from os_credits.perun.group import Group -from os_credits.perun.groupsManager import get_group_by_name - -pytestmark = pytest.mark.skipif( - not getenv("TEST_ONLINE", False), - reason="Skip all tests against Perun since $TEST_ONLINE is not set.", -) - - -async def test_bad_credentials(perun_test_group: Group, monkeypatch): - from os_credits import settings - - monkeypatch.setenv("OS_CREDITS_PERUN_LOGIN", "bogus") - monkeypatch.setenv("OS_CREDITS_PERUN_PASSWORD", "bogus") - reload(settings) - - with pytest.raises(BadCredentialsException): - await get_group_by_name(perun_test_group.name) - - -async def test_group_not_exists(): - - with pytest.raises(GroupNotExistsError): - await get_group_by_name("This Group Does Not Exist") - - -async def test_get_group_by_name(perun_test_group: Group): - resp = await get_group_by_name(perun_test_group.name) - assert resp["id"] == perun_test_group.id - - -async def test_get_attributes(perun_test_group: Group): - # assert value which must not be changed inside Perun - resp = await get_attributes( - perun_test_group.id, attribute_full_names=[ToEmail.get_full_name()] - ) - assert resp[0]["value"] == ["DO NOT CHANGE THIS VALUES"], "Unexpected response" - - -async def test_set_attributes(perun_test_group: Group, loop): - random_value = randint(0, 200) - - credits_used = DenbiCreditsUsed(value=random_value) - await set_attributes(perun_test_group.id, [credits_used]) - resp = await get_attributes( - perun_test_group.id, attribute_full_names=[DenbiCreditsUsed.get_full_name()] - ) - # value is stored as str inside Perun - assert resp[0]["value"] == str(random_value) - - -async def test_credits_granted_missing(perun_test_group: Group): - # name of Perun group without any value - perun_test_group.name = f"{perun_test_group.name}_credits_granted" - # attr should not be set - with pytest.raises(DenbiCreditsGrantedMissing): - await perun_test_group.connect() - - -async def test_group_resource_not_associated(perun_test_group: Group): - # name of Perun group without association - perun_test_group.name = f"{perun_test_group.name}_no_resource" - # attr should not be set - with pytest.raises(GroupResourceNotAssociatedError): - await perun_test_group.connect() - - -async def test_not_associated_error(perun_test_group: Group): - assert await perun_test_group.is_assigned_resource() - # OPENSTACK_RESOURCE - not_associated_but_existing_resource_id = 8675 - perun_test_group.resource_id = not_associated_but_existing_resource_id - assert not await perun_test_group.is_assigned_resource() - - -async def test_set_resource_bound_attributes(perun_test_group: Group): - random_value = {"test": "2019-01-01 00:00:00.000000"} - - timestamps = DenbiCreditTimestamps(value=random_value) - await set_resource_bound_attributes( - perun_test_group.id, perun_test_group.resource_id, [timestamps] - ) - resp = await get_resource_bound_attributes( - perun_test_group.id, - perun_test_group.resource_id, - attribute_full_names=[DenbiCreditTimestamps.get_full_name()], - ) - # value is stored as str inside Perun - assert resp[0]["value"] == random_value - - -async def test_group_repr(perun_test_group): - await perun_test_group.connect() - # make sure that __repr__ never fails - repr(perun_test_group) - assert True diff --git a/tests/test_settings.py b/tests/test_settings.py deleted file mode 100644 index 528f6b6..0000000 --- a/tests/test_settings.py +++ /dev/null @@ -1,51 +0,0 @@ -from decimal import Decimal -from importlib import reload - - -def test_parsing_special_values(monkeypatch): - from os_credits import settings - - "Test whether values from environment variables are parsed and stored correctly" - integer_conf_value = 98 - list_conf_value = {"ProjectA", "ProjectB"} - monkeypatch.setenv( - "OS_CREDITS_PROJECT_WHITELIST", ";".join(sorted(list_conf_value)) - ) - monkeypatch.setenv("INFLUXDB_PORT", str(integer_conf_value)) - monkeypatch.setenv("OS_CREDITS_PRECISION", str(3)) - # necessary to pickup changed environment variables - reload(settings) - from os_credits.settings import config - - assert ( - config["OS_CREDITS_PROJECT_WHITELIST"] == list_conf_value - ), "Comma-separated list was not parsed correctly from environment" - assert ( - config["INFLUXDB_PORT"] == integer_conf_value - ), "Integer value was not parsed/converted correctly from environment" - assert config["OS_CREDITS_PRECISION"] == Decimal(10) ** -3 - - -def test_bad_int_values(monkeypatch): - from os_credits import settings - - bad_test_port = -324 - monkeypatch.setenv("INFLUXDB_PORT", str(bad_test_port)) - reload(settings) - from os_credits.settings import config - - assert ( - config["INFLUXDB_PORT"] != bad_test_port - ), "Negative int value from environment was not ignored" - assert config["INFLUXDB_PORT"] != str( - bad_test_port - ), "Bad integer value was not removed from environment and is still accessible due to the Chainmap" - - invalid_int_value = "lala" - monkeypatch.setenv("INFLUXDB_PORT", invalid_int_value) - reload(settings) - from os_credits.settings import config - - assert ( - config["INFLUXDB_PORT"] != invalid_int_value - ), "Negative int value from environment was not ignored" From a65ddf8ca97c7d27afa61ca4c1ae853bb830ceda Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Wed, 30 Mar 2022 18:32:41 +0200 Subject: [PATCH 20/36] update to python 3.10.4 --- poetry.lock | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 84a9881..b10b0f5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1021,8 +1021,8 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" -python-versions = "3.10.1" -content-hash = "5ab83e1bbce7fc6d52dec47126839de8c4b20b198b131a13917b47cf815b7ed6" +python-versions = "3.10.4" +content-hash = "8ef37468cb53b1100f56de1d079605f9697c17f7d112768eb3d0a9c4f5005305" [metadata.files] aiohttp = [ diff --git a/pyproject.toml b/pyproject.toml index 5a2d4bf..ad10a14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "" authors = ["gilbus", "ekatchko"] [tool.poetry.dependencies] -python = "3.10.1" +python = "3.10.4" aiohttp = "3.8.1" aiohttp-swagger = "1.0.16" aiohttp_jinja2 = "1.5" From 89b2d782ddb651b5e45994a49c451b8f0bfd0061 Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Wed, 30 Mar 2022 21:40:28 +0200 Subject: [PATCH 21/36] remove src. from imports --- src/os_credits/db_client/client.py | 6 +++--- src/os_credits/db_client/tasks.py | 6 +++--- src/os_credits/main.py | 12 ++++++------ src/os_credits/views.py | 6 +++--- src/os_credits/worker_helper.py | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/os_credits/db_client/client.py b/src/os_credits/db_client/client.py index ea2b64f..4512b14 100644 --- a/src/os_credits/db_client/client.py +++ b/src/os_credits/db_client/client.py @@ -7,11 +7,11 @@ from sqlalchemy.exc import OperationalError from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker, Session -from src.os_credits.settings import config -from src.os_credits.db_client.model import Base, MetricCredits, Project, Credits, PromCatalogReflected, PromMetricReflected, \ +from os_credits.settings import config +from os_credits.db_client.model import Base, MetricCredits, Project, Credits, PromCatalogReflected, PromMetricReflected, \ make_measurement_class, Metric, Label, \ BaseMeasurement -from src.os_credits.log import timescaledb_logger +from os_credits.log import timescaledb_logger class TimescaleDBManager: diff --git a/src/os_credits/db_client/tasks.py b/src/os_credits/db_client/tasks.py index 4c518ff..7e24612 100644 --- a/src/os_credits/db_client/tasks.py +++ b/src/os_credits/db_client/tasks.py @@ -5,9 +5,9 @@ from aiohttp.web import Application from sqlalchemy.ext.asyncio import AsyncSession -from src.os_credits.db_client.client import TimescaleDBManager -from src.os_credits.log import producer_logger, task_logger, TASK_ID -from src.os_credits.settings import config +from os_credits.db_client.client import TimescaleDBManager +from os_credits.log import producer_logger, task_logger, TASK_ID +from os_credits.settings import config def unique_identifier(project_name: str) -> str: diff --git a/src/os_credits/main.py b/src/os_credits/main.py index 16d41d4..861e3dd 100644 --- a/src/os_credits/main.py +++ b/src/os_credits/main.py @@ -9,10 +9,10 @@ from aiohttp_jinja2 import setup from aiohttp_swagger import setup_swagger from jinja2 import FileSystemLoader -from src.os_credits.worker_helper import create_consumer_worker, stop_consumer_worker, create_producer -from src.os_credits.db_client.client import TimescaleDBManager -from src.os_credits.log import internal_logger -from src.os_credits.views import ping, credits_history_api, costs_per_hour, get_metrics, get_current_credits +from os_credits.worker_helper import create_consumer_worker, stop_consumer_worker, create_producer +from os_credits.db_client.client import TimescaleDBManager +from os_credits.log import internal_logger +from os_credits.views import ping, credits_history_api, costs_per_hour, get_metrics, get_current_credits APP_ROOT = Path(__file__).parent @@ -49,8 +49,8 @@ async def create_app() -> web.Application: """ # imported inside the function to allow pytest to set environment variables and have # them applied - from src.os_credits.settings import config - from src.os_credits.log import DEFAULT_LOGGING_CONFIG + from os_credits.settings import config + from os_credits.log import DEFAULT_LOGGING_CONFIG dictConfig(DEFAULT_LOGGING_CONFIG) internal_logger.info("Applied default logging config") diff --git a/src/os_credits/views.py b/src/os_credits/views.py index 604d6de..44881e2 100644 --- a/src/os_credits/views.py +++ b/src/os_credits/views.py @@ -10,9 +10,9 @@ from aiohttp import web -from src.os_credits.db_client.client import TimescaleDBManager -from src.os_credits.log import views_logger -from src.os_credits.settings import config +from os_credits.db_client.client import TimescaleDBManager +from os_credits.log import views_logger +from os_credits.settings import config _DEFINITELY_PAST = datetime.min _DEFINITELY_END = datetime.max diff --git a/src/os_credits/worker_helper.py b/src/os_credits/worker_helper.py index 9b5a44e..f261cf9 100644 --- a/src/os_credits/worker_helper.py +++ b/src/os_credits/worker_helper.py @@ -1,6 +1,6 @@ import asyncio -from src.os_credits.db_client.tasks import consumer_worker, put_projects_into_queue +from os_credits.db_client.tasks import consumer_worker, put_projects_into_queue from aiohttp import web from asyncio import TimeoutError from asyncio import create_task From f9fb55112acc96c0820fdc56ec1fa5ccd80d96b0 Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Wed, 30 Mar 2022 21:52:54 +0200 Subject: [PATCH 22/36] remove src from logging file --- src/os_credits/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/os_credits/log.py b/src/os_credits/log.py index 39262b1..7a13209 100644 --- a/src/os_credits/log.py +++ b/src/os_credits/log.py @@ -74,7 +74,7 @@ def filter(self, record: LogRecord) -> bool: "format": "[%(levelname)s] %(asctime)s %(name)s %(funcName)s:%(lineno)d: %(message)s" }, }, - "filters": {"task_id_filter": {"()": "src.os_credits.log.TaskIdFilter"}}, + "filters": {"task_id_filter": {"()": "os_credits.log.TaskIdFilter"}}, "handlers": { "with_task_id": { "class": "logging.StreamHandler", From be867b2aa05629cbfc5c6b193e461699307b77c6 Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Thu, 31 Mar 2022 15:31:39 +0200 Subject: [PATCH 23/36] fix getting used credits --- src/os_credits/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/os_credits/views.py b/src/os_credits/views.py index 44881e2..15105da 100644 --- a/src/os_credits/views.py +++ b/src/os_credits/views.py @@ -392,5 +392,5 @@ async def get_current_credits(request: web.Request) -> web.Response: if not project: raise web.HTTPNotFound(reason=f"No credits found for {project_name}.") return web.json_response( - {"current_credits": float(project.used_credits)} + {"current_credits": float(project[0].used_credits)} ) From 0311f26dea10268d4c1b92ea12c06f4080848769 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 3 Apr 2022 04:33:33 +0000 Subject: [PATCH 24/36] feat(Dependencies): Update dependency pre-commit to v2.18.1 | datasource | package | from | to | | ---------- | ---------- | ------ | ------ | | pypi | pre-commit | 2.17.0 | 2.18.1 | --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index cb25b02..3dae6ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -659,11 +659,11 @@ dev = ["pre-commit", "tox"] [[package]] name = "pre-commit" -version = "2.17.0" +version = "2.18.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" [package.dependencies] cfgv = ">=2.0.0" @@ -1261,7 +1261,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "04f5a4369cc065734d5e27c456f8329d8024b7f6291111038b25167985b5b88b" +content-hash = "02f8c14e2e67f5439e7aff39855a74329bfa6f36049f62262b2d70df4316f210" [metadata.files] aiohttp = [ @@ -1932,8 +1932,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ - {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, - {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, + {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, + {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, ] prometheus-async = [ {file = "prometheus-async-22.1.0.tar.gz", hash = "sha256:2abdf0ffd419aa9889b375b0dd5af4cb5708457a0b19ae21d3968fea68c3faf3"}, diff --git a/pyproject.toml b/pyproject.toml index 77c6b43..3b6106c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ mypy_extensions = "0.4.3" aiohttp-devtools = "1.0.post0" pytest = "6.2.5" pytest-aiohttp = "1.0.4" -pre-commit = "2.17.0" +pre-commit = "2.18.1" black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.17.0" mypy = "0.942" From df60fecb6e4837f35bcfe66efc1ab0e8d3e38da4 Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Thu, 7 Apr 2022 18:49:07 +0200 Subject: [PATCH 25/36] feat(workflows): add mermaid workflows --- workflows/systems_setup.md | 96 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 workflows/systems_setup.md diff --git a/workflows/systems_setup.md b/workflows/systems_setup.md new file mode 100644 index 0000000..5ba12b7 --- /dev/null +++ b/workflows/systems_setup.md @@ -0,0 +1,96 @@ +#Legend +```mermaid +%%{init: {'theme': 'dark', 'flowchart': {'curve':'linear'}}}%% +flowchart TB + direction TB + process[Process] + if_clause{Decision} + start([Start/Stop]) + entity(Entity/Instance) + database[(Database)] + simplified((Simplified/Reduced Graph)) +``` +#Current setup +```mermaid +%%{init: {'theme': 'dark', 'flowchart': {'curve':'linear'}}}%% +flowchart TB + subgraph current [Current] + direction TB + subgraph site [Compute Center] + direction TB + openstack(Openstack) + exporter(Site exporter) + site_prometheus(Prometheus) + exporter-->|Get instances and project data|openstack + site_prometheus-->|Scrape computed values|exporter + end + + subgraph credits_system [Credits system] + direction TB + credits(OS Credits) + timescaledb[(TimescaleDB)] + portal_prometheus(Prometheus) + promscale(Promscale) + portal_grafana(Grafana) + portal_prometheus--->|Scrape values|site_prometheus + portal_prometheus-->|Write scraped and filtered usage values|promscale + promscale-->|Write processed usage values|timescaledb + credits<-->|Compute credits from usage values|timescaledb + portal_grafana-->|Read data to display charts|promscale + end + + subgraph portal [Portal] + direction TB + project_api(Cloud-API) + project_api--->|Get used credits, history, credits cost|credits + credits--->|Get granted credits and post data|project_api + end + end + +``` +#Longterm de.NBI setup +```mermaid +%%{init: {'theme': 'dark', 'flowchart': {'curve':'linear'}}}%% +flowchart TB + subgraph a [de.NBI] + direction TB + cc((Compute Center)) + credits((Credits system)) + credits-->|Get data|cc + + subgraph portal [Portal] + direction TB + project_api(Project management API) + simplevm_api(SimpleVM API) + end + + project_api--->|Get used credits, get history, get credits cost|credits + credits-->|Get granted credits and post data|project_api + simplevm_api-->|Get used/granted credits, get history, get credits cost|credits + end + +``` +#Shortterm Techfak setup +```mermaid +%%{init: {'theme': 'dark', 'flowchart': {'curve':'linear'}}}%% +flowchart TB + subgraph a [Techfak] + direction TB + cc((Compute Center)) + credits((Credits system)) + credits--->|Get data|cc + user((VO-Admin)) + + subgraph portal [Portal] + direction TB + simplevm_webapp(SimpleVM Webapp) + simplevm_api(SimpleVM API) + simplevm_api<--->|Get/Post|simplevm_webapp + end + + simplevm_api-->|Get used/granted credits, get history, get credits cost|credits + user-->|Set granted credits if project exists else cache granted credits|credits + user----->|Calculate granted credits for project with lifetime|simplevm_webapp + end + +``` From 393b8282dc67a85270a1327f2b516c55195cdeeb Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 12 Apr 2022 11:19:44 +0000 Subject: [PATCH 26/36] feat(Dependencies): Update dependency sphinx-autodoc-typehints to v1.17.1 | datasource | package | from | to | | ---------- | ------------------------ | ------ | ------ | | pypi | sphinx-autodoc-typehints | 1.17.0 | 1.17.1 | --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3dae6ae..ef19f40 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1026,7 +1026,7 @@ test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" -version = "1.17.0" +version = "1.17.1" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" category = "dev" optional = false @@ -1036,7 +1036,7 @@ python-versions = ">=3.7" Sphinx = ">=4" [package.extras] -testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "nptyping (>=1)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] +testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "nptyping (>=1,<2)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] type_comments = ["typed-ast (>=1.4.0)"] [[package]] @@ -1261,7 +1261,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "02f8c14e2e67f5439e7aff39855a74329bfa6f36049f62262b2d70df4316f210" +content-hash = "ebdbf7b48a16e9e6df59c1c27c04b6fbaed9197db4662d48d29d061109c5d01f" [metadata.files] aiohttp = [ @@ -2099,8 +2099,8 @@ sphinx = [ {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"}, ] sphinx-autodoc-typehints = [ - {file = "sphinx_autodoc_typehints-1.17.0-py3-none-any.whl", hash = "sha256:081daf53077b4ae1c28347d6d858e13e63aefe3b4aacef79fd717dd60687b470"}, - {file = "sphinx_autodoc_typehints-1.17.0.tar.gz", hash = "sha256:51c7b3f5cb9ccd15d0b52088c62df3094f1abd9612930340365c26def8629a14"}, + {file = "sphinx_autodoc_typehints-1.17.1-py3-none-any.whl", hash = "sha256:f16491cad05a13f4825ecdf9ee4ff02925d9a3b1cf103d4d02f2f81802cce653"}, + {file = "sphinx_autodoc_typehints-1.17.1.tar.gz", hash = "sha256:844d7237d3f6280b0416f5375d9556cfd84df1945356fcc34b82e8aaacab40f3"}, ] sphinxcontrib-applehelp = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, diff --git a/pyproject.toml b/pyproject.toml index 3b6106c..3c03214 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ pytest = "6.2.5" pytest-aiohttp = "1.0.4" pre-commit = "2.18.1" black = {version = "22.1.0",allow-prereleases = true} -sphinx-autodoc-typehints = "1.17.0" +sphinx-autodoc-typehints = "1.17.1" mypy = "0.942" sphinx = "4.4.0" pytest-localserver = "0.5.1.post0" From bd32a0182663180742ef1970872e22f993a668c1 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 14 Apr 2022 19:24:42 +0000 Subject: [PATCH 27/36] feat(Dependencies): Update actions/checkout action to v3.0.1 | datasource | package | from | to | | ----------- | ---------------- | ------ | ------ | | github-tags | actions/checkout | v3.0.0 | v3.0.1 | --- .github/workflows/build-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index b1384ec..5d5ee84 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -9,7 +9,7 @@ jobs: uses: rokroskar/workflow-run-cleanup-action@v0.3.3 env: GITHUB_TOKEN: "${{ secrets.GITHUBSECRET2 }}" - - uses: actions/checkout@v3.0.0 + - uses: actions/checkout@v3.0.1 - name: Build with retry uses: Wandalen/wretry.action@v1.0.11 with: From 8bd713d8b6c1060e01cb6312bc7bba8c9ac02e5f Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 21 Apr 2022 17:31:27 +0000 Subject: [PATCH 28/36] feat(Dependencies): Update actions/checkout action to v3.0.2 | datasource | package | from | to | | ----------- | ---------------- | ------ | ------ | | github-tags | actions/checkout | v3.0.1 | v3.0.2 | --- .github/workflows/build-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 5d5ee84..3424cff 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -9,7 +9,7 @@ jobs: uses: rokroskar/workflow-run-cleanup-action@v0.3.3 env: GITHUB_TOKEN: "${{ secrets.GITHUBSECRET2 }}" - - uses: actions/checkout@v3.0.1 + - uses: actions/checkout@v3.0.2 - name: Build with retry uses: Wandalen/wretry.action@v1.0.11 with: From a690c94a70046e75a3444dc62de18eed5a82369d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 25 Apr 2022 15:41:35 +0000 Subject: [PATCH 29/36] feat(Dependencies): Update github/codeql-action action to v2 | datasource | package | from | to | | ----------- | -------------------- | ---- | -- | | github-tags | github/codeql-action | v1 | v2 | | github-tags | github/codeql-action | v1 | v2 | | github-tags | github/codeql-action | v1 | v2 | --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3df5453..931c291 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} queries: +security-extended, security-and-quality @@ -51,7 +51,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -65,4 +65,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From 9a15a8643621043cfc6c6809482bf91a9cfad4f2 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 5 May 2022 15:04:53 +0000 Subject: [PATCH 30/36] feat(Dependencies): Update dependency pre-commit to v2.19.0 | datasource | package | from | to | | ---------- | ---------- | ------ | ------ | | pypi | pre-commit | 2.18.1 | 2.19.0 | --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index ef19f40..522d96c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -659,7 +659,7 @@ dev = ["pre-commit", "tox"] [[package]] name = "pre-commit" -version = "2.18.1" +version = "2.19.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -1261,7 +1261,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "ebdbf7b48a16e9e6df59c1c27c04b6fbaed9197db4662d48d29d061109c5d01f" +content-hash = "6bca832e53ab7a26f3c332e7a51863924750fa3cfe1d5d2d283e29d53e336352" [metadata.files] aiohttp = [ @@ -1932,8 +1932,8 @@ pluggy = [ {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] pre-commit = [ - {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, - {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, + {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, + {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, ] prometheus-async = [ {file = "prometheus-async-22.1.0.tar.gz", hash = "sha256:2abdf0ffd419aa9889b375b0dd5af4cb5708457a0b19ae21d3968fea68c3faf3"}, diff --git a/pyproject.toml b/pyproject.toml index 3c03214..bffde35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ mypy_extensions = "0.4.3" aiohttp-devtools = "1.0.post0" pytest = "6.2.5" pytest-aiohttp = "1.0.4" -pre-commit = "2.18.1" +pre-commit = "2.19.0" black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.17.1" mypy = "0.942" From b46ac4ed9194b1ae078f4f76703eab87c81c1f28 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 9 May 2022 06:36:42 +0000 Subject: [PATCH 31/36] feat(Dependencies): Update dependency pytest-localserver to v0.6.0 | datasource | package | from | to | | ---------- | ------------------ | ----------- | ----- | | pypi | pytest-localserver | 0.5.1.post0 | 0.6.0 | --- poetry.lock | 39 ++++++++++++++++++++++++++++++++++----- pyproject.toml | 2 +- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 522d96c..7c7c23d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -90,6 +90,18 @@ python-versions = ">=3.6" [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "aiosmtpd" +version = "1.4.2" +description = "aiosmtpd - asyncio based SMTP server" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +atpublic = "*" +attrs = "*" + [[package]] name = "aiosmtplib" version = "1.1.6" @@ -134,6 +146,14 @@ category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "atpublic" +version = "3.0.1" +description = "Keep all y'all's __all__'s in sync" +category = "dev" +optional = false +python-versions = ">=3.7" + [[package]] name = "attrs" version = "20.3.0" @@ -897,13 +917,14 @@ tests = ["mock"] [[package]] name = "pytest-localserver" -version = "0.5.1.post0" +version = "0.6.0" description = "py.test plugin to test server connections locally." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.5" [package.dependencies] +aiosmtpd = "*" werkzeug = ">=0.10" [[package]] @@ -1261,7 +1282,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "6bca832e53ab7a26f3c332e7a51863924750fa3cfe1d5d2d283e29d53e336352" +content-hash = "20cfb4557e80bfeffd193f4a065baeddbd8ffe76fd181518de4a6fbaced29924" [metadata.files] aiohttp = [ @@ -1358,6 +1379,10 @@ aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, ] +aiosmtpd = [ + {file = "aiosmtpd-1.4.2-py3-none-any.whl", hash = "sha256:314f70b74cb8474882cef396b186fbfad8660c7b52be5c1937f3c31df14232a4"}, + {file = "aiosmtpd-1.4.2.tar.gz", hash = "sha256:aa891d010d2097274189078c6ce2a59a167f3fb2e974e028b572a61e92e1549c"}, +] aiosmtplib = [ {file = "aiosmtplib-1.1.6-py3-none-any.whl", hash = "sha256:84174765778b2c5e0e207fbce0a769202fcf0c3de81faa87cc03551a6333bfa9"}, {file = "aiosmtplib-1.1.6.tar.gz", hash = "sha256:d138fe6ffecbc9e6320269690b9ac0b75e540ef96e8f5c77d4a306760014dce2"}, @@ -1378,6 +1403,10 @@ atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] +atpublic = [ + {file = "atpublic-3.0.1-py3-none-any.whl", hash = "sha256:3deaa4aed146bf4adf5a8e0f9e4f0096774169a7157621753aa408d9a180b8b5"}, + {file = "atpublic-3.0.1.tar.gz", hash = "sha256:bb072b50e6484490404e5cb4034e782aaa339fdd6ac36434e53c10791aef18bf"}, +] attrs = [ {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, @@ -2022,8 +2051,8 @@ pytest-isort = [ {file = "pytest_isort-2.0.0-py3-none-any.whl", hash = "sha256:ab949c593213dad38ba75db32a0ce361fcddd11d4152be4a2c93b85104cc4376"}, ] pytest-localserver = [ - {file = "pytest-localserver-0.5.1.post0.tar.gz", hash = "sha256:5ec7f8e6534cf03887af2cb59e577f169ac0e8b2fd2c3e3409280035f386d407"}, - {file = "pytest_localserver-0.5.1.post0-py3-none-any.whl", hash = "sha256:b3ff1b8bade571d54701bad3efd68ca1bb463ad88daa75da15cc8842809659ea"}, + {file = "pytest-localserver-0.6.0.tar.gz", hash = "sha256:ddc479aba96a7da0e7a1cc7d3a303e50582d6d505803e8f67b827218f9fe0f65"}, + {file = "pytest_localserver-0.6.0-py3-none-any.whl", hash = "sha256:47b1718643ca376a42f8c6079b03519afe4e754c6b198bf06109bed735983919"}, ] pytest-mypy = [ {file = "pytest-mypy-0.9.1.tar.gz", hash = "sha256:9ffa3bf405c12c5c6be9e92e22bebb6ab2c91b9c32f45b0f0c93af473269ab5c"}, diff --git a/pyproject.toml b/pyproject.toml index bffde35..137a778 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.17.1" mypy = "0.942" sphinx = "4.4.0" -pytest-localserver = "0.5.1.post0" +pytest-localserver = "0.6.0" pytest-cov = "3.0.0" sphinxcontrib-trio = "1.1.2" lxml = "4.7.1" From fc873f9c82cd12c8e0585656978b644da566fe5a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 25 May 2022 15:41:21 +0000 Subject: [PATCH 32/36] feat(Dependencies): Update dependency mypy to v0.960 | datasource | package | from | to | | ---------- | ------- | ----- | ----- | | pypi | mypy | 0.942 | 0.960 | --- poetry.lock | 138 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7c7c23d..50e9ca7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -292,14 +292,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.3.2" +version = "6.4" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_version < \"3.11\" and extra == \"toml\""} [package.extras] toml = ["tomli"] @@ -584,7 +584,7 @@ python-versions = ">=3.6" [[package]] name = "mypy" -version = "0.942" +version = "0.960" description = "Optional static typing for Python" category = "dev" optional = false @@ -592,7 +592,7 @@ python-versions = ">=3.6" [package.dependencies] mypy-extensions = ">=0.4.3" -tomli = ">=1.1.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" [package.extras] @@ -1282,7 +1282,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "20cfb4557e80bfeffd193f4a065baeddbd8ffe76fd181518de4a6fbaced29924" +content-hash = "62f7309602fff08eae8aa41b341bf39eb5de487ec86865fef0a5c34c2a7da846" [metadata.files] aiohttp = [ @@ -1531,47 +1531,47 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, - {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, - {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, - {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, - {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, - {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, - {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, - {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, - {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, - {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, - {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, - {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, - {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, - {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, - {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, - {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, - {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, - {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, - {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, - {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, - {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, - {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, - {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, - {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, - {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, - {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, - {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, - {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, - {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, - {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, - {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, - {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, - {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, - {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, - {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, - {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, - {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, - {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, - {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, - {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, - {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, + {file = "coverage-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50ed480b798febce113709846b11f5d5ed1e529c88d8ae92f707806c50297abf"}, + {file = "coverage-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26f8f92699756cb7af2b30720de0c5bb8d028e923a95b6d0c891088025a1ac8f"}, + {file = "coverage-6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c2147921da7f4d2d04f570e1838db32b95c5509d248f3fe6417e91437eaf41"}, + {file = "coverage-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750e13834b597eeb8ae6e72aa58d1d831b96beec5ad1d04479ae3772373a8088"}, + {file = "coverage-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5b9ee0fc146e907aa0f5fb858c3b3da9199d78b7bb2c9973d95550bd40f701"}, + {file = "coverage-6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a022394996419142b33a0cf7274cb444c01d2bb123727c4bb0b9acabcb515dea"}, + {file = "coverage-6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5a78cf2c43b13aa6b56003707c5203f28585944c277c1f3f109c7b041b16bd39"}, + {file = "coverage-6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9229d074e097f21dfe0643d9d0140ee7433814b3f0fc3706b4abffd1e3038632"}, + {file = "coverage-6.4-cp310-cp310-win32.whl", hash = "sha256:fb45fe08e1abc64eb836d187b20a59172053999823f7f6ef4f18a819c44ba16f"}, + {file = "coverage-6.4-cp310-cp310-win_amd64.whl", hash = "sha256:3cfd07c5889ddb96a401449109a8b97a165be9d67077df6802f59708bfb07720"}, + {file = "coverage-6.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03014a74023abaf5a591eeeaf1ac66a73d54eba178ff4cb1fa0c0a44aae70383"}, + {file = "coverage-6.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82f2cd69c71698152e943f4a5a6b83a3ab1db73b88f6e769fabc86074c3b08"}, + {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b546cf2b1974ddc2cb222a109b37c6ed1778b9be7e6b0c0bc0cf0438d9e45a6"}, + {file = "coverage-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc173f1ce9ffb16b299f51c9ce53f66a62f4d975abe5640e976904066f3c835d"}, + {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c53ad261dfc8695062fc8811ac7c162bd6096a05a19f26097f411bdf5747aee7"}, + {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:eef5292b60b6de753d6e7f2d128d5841c7915fb1e3321c3a1fe6acfe76c38052"}, + {file = "coverage-6.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:543e172ce4c0de533fa892034cce260467b213c0ea8e39da2f65f9a477425211"}, + {file = "coverage-6.4-cp37-cp37m-win32.whl", hash = "sha256:00c8544510f3c98476bbd58201ac2b150ffbcce46a8c3e4fb89ebf01998f806a"}, + {file = "coverage-6.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b84ab65444dcc68d761e95d4d70f3cfd347ceca5a029f2ffec37d4f124f61311"}, + {file = "coverage-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d548edacbf16a8276af13063a2b0669d58bbcfca7c55a255f84aac2870786a61"}, + {file = "coverage-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:033ebec282793bd9eb988d0271c211e58442c31077976c19c442e24d827d356f"}, + {file = "coverage-6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742fb8b43835078dd7496c3c25a1ec8d15351df49fb0037bffb4754291ef30ce"}, + {file = "coverage-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55fae115ef9f67934e9f1103c9ba826b4c690e4c5bcf94482b8b2398311bf9c"}, + {file = "coverage-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd698341626f3c77784858427bad0cdd54a713115b423d22ac83a28303d1d95"}, + {file = "coverage-6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d382f7d77eeeaff14b30516b17bcbe80f645f5cf02bb755baac376591c653c"}, + {file = "coverage-6.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:016d7f5cf1c8c84f533a3c1f8f36126fbe00b2ec0ccca47cc5731c3723d327c6"}, + {file = "coverage-6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:69432946f154c6add0e9ede03cc43b96e2ef2733110a77444823c053b1ff5166"}, + {file = "coverage-6.4-cp38-cp38-win32.whl", hash = "sha256:83bd142cdec5e4a5c4ca1d4ff6fa807d28460f9db919f9f6a31babaaa8b88426"}, + {file = "coverage-6.4-cp38-cp38-win_amd64.whl", hash = "sha256:4002f9e8c1f286e986fe96ec58742b93484195defc01d5cc7809b8f7acb5ece3"}, + {file = "coverage-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4f52c272fdc82e7c65ff3f17a7179bc5f710ebc8ce8a5cadac81215e8326740"}, + {file = "coverage-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5578efe4038be02d76c344007b13119b2b20acd009a88dde8adec2de4f630b5"}, + {file = "coverage-6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8099ea680201c2221f8468c372198ceba9338a5fec0e940111962b03b3f716a"}, + {file = "coverage-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a00441f5ea4504f5abbc047589d09e0dc33eb447dc45a1a527c8b74bfdd32c65"}, + {file = "coverage-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e76bd16f0e31bc2b07e0fb1379551fcd40daf8cdf7e24f31a29e442878a827c"}, + {file = "coverage-6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d2e80dd3438e93b19e1223a9850fa65425e77f2607a364b6fd134fcd52dc9df"}, + {file = "coverage-6.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:341e9c2008c481c5c72d0e0dbf64980a4b2238631a7f9780b0fe2e95755fb018"}, + {file = "coverage-6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21e6686a95025927775ac501e74f5940cdf6fe052292f3a3f7349b0abae6d00f"}, + {file = "coverage-6.4-cp39-cp39-win32.whl", hash = "sha256:968ed5407f9460bd5a591cefd1388cc00a8f5099de9e76234655ae48cfdbe2c3"}, + {file = "coverage-6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e35217031e4b534b09f9b9a5841b9344a30a6357627761d4218818b865d45055"}, + {file = "coverage-6.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:e637ae0b7b481905358624ef2e81d7fb0b1af55f5ff99f9ba05442a444b11e45"}, + {file = "coverage-6.4.tar.gz", hash = "sha256:727dafd7f67a6e1cad808dc884bd9c5a2f6ef1f8f6d2f22b37b96cb0080d4f49"}, ] cryptography = [ {file = "cryptography-3.4.6-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799"}, @@ -1908,29 +1908,29 @@ multidict = [ {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, ] mypy = [ - {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, - {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, - {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, - {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, - {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, - {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, - {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, - {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, - {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, - {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, - {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, - {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, - {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, - {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, - {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, - {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, - {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, - {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, - {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, + {file = "mypy-0.960-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3a3e525cd76c2c4f90f1449fd034ba21fcca68050ff7c8397bb7dd25dd8b8248"}, + {file = "mypy-0.960-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a76dc4f91e92db119b1be293892df8379b08fd31795bb44e0ff84256d34c251"}, + {file = "mypy-0.960-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffdad80a92c100d1b0fe3d3cf1a4724136029a29afe8566404c0146747114382"}, + {file = "mypy-0.960-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7d390248ec07fa344b9f365e6ed9d205bd0205e485c555bed37c4235c868e9d5"}, + {file = "mypy-0.960-cp310-cp310-win_amd64.whl", hash = "sha256:925aa84369a07846b7f3b8556ccade1f371aa554f2bd4fb31cb97a24b73b036e"}, + {file = "mypy-0.960-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:239d6b2242d6c7f5822163ee082ef7a28ee02e7ac86c35593ef923796826a385"}, + {file = "mypy-0.960-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f1ba54d440d4feee49d8768ea952137316d454b15301c44403db3f2cb51af024"}, + {file = "mypy-0.960-cp36-cp36m-win_amd64.whl", hash = "sha256:cb7752b24528c118a7403ee955b6a578bfcf5879d5ee91790667c8ea511d2085"}, + {file = "mypy-0.960-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:826a2917c275e2ee05b7c7b736c1e6549a35b7ea5a198ca457f8c2ebea2cbecf"}, + {file = "mypy-0.960-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3eabcbd2525f295da322dff8175258f3fc4c3eb53f6d1929644ef4d99b92e72d"}, + {file = "mypy-0.960-cp37-cp37m-win_amd64.whl", hash = "sha256:f47322796c412271f5aea48381a528a613f33e0a115452d03ae35d673e6064f8"}, + {file = "mypy-0.960-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2c7f8bb9619290836a4e167e2ef1f2cf14d70e0bc36c04441e41487456561409"}, + {file = "mypy-0.960-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbfb873cf2b8d8c3c513367febde932e061a5f73f762896826ba06391d932b2a"}, + {file = "mypy-0.960-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc537885891382e08129d9862553b3d00d4be3eb15b8cae9e2466452f52b0117"}, + {file = "mypy-0.960-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:481f98c6b24383188c928f33dd2f0776690807e12e9989dd0419edd5c74aa53b"}, + {file = "mypy-0.960-cp38-cp38-win_amd64.whl", hash = "sha256:29dc94d9215c3eb80ac3c2ad29d0c22628accfb060348fd23d73abe3ace6c10d"}, + {file = "mypy-0.960-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:33d53a232bb79057f33332dbbb6393e68acbcb776d2f571ba4b1d50a2c8ba873"}, + {file = "mypy-0.960-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d645e9e7f7a5da3ec3bbcc314ebb9bb22c7ce39e70367830eb3c08d0140b9ce"}, + {file = "mypy-0.960-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85cf2b14d32b61db24ade8ac9ae7691bdfc572a403e3cb8537da936e74713275"}, + {file = "mypy-0.960-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a85a20b43fa69efc0b955eba1db435e2ffecb1ca695fe359768e0503b91ea89f"}, + {file = "mypy-0.960-cp39-cp39-win_amd64.whl", hash = "sha256:0ebfb3f414204b98c06791af37a3a96772203da60636e2897408517fcfeee7a8"}, + {file = "mypy-0.960-py3-none-any.whl", hash = "sha256:bfd4f6536bd384c27c392a8b8f790fd0ed5c0cf2f63fc2fed7bce56751d53026"}, + {file = "mypy-0.960.tar.gz", hash = "sha256:d4fccf04c1acf750babd74252e0f2db6bd2ac3aa8fe960797d9f3ef41cf2bfd4"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, diff --git a/pyproject.toml b/pyproject.toml index 137a778..59f69a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ pytest-aiohttp = "1.0.4" pre-commit = "2.19.0" black = {version = "22.1.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.17.1" -mypy = "0.942" +mypy = "0.960" sphinx = "4.4.0" pytest-localserver = "0.6.0" pytest-cov = "3.0.0" From 2245b3895fd998381a75f8e8f2353cf9b80f21e1 Mon Sep 17 00:00:00 2001 From: Jeenyus Date: Thu, 2 Jun 2022 22:10:06 +0200 Subject: [PATCH 33/36] specify schema for metrics and labels --- src/os_credits/db_client/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/os_credits/db_client/model.py b/src/os_credits/db_client/model.py index a5fdc3e..367a0cd 100644 --- a/src/os_credits/db_client/model.py +++ b/src/os_credits/db_client/model.py @@ -85,6 +85,7 @@ class Metric(PromCatalogReflected, Base): Used to get prom_metric. views. """ __tablename__ = "metric" + __table_args__ = ({"schema": "_prom_catalog"}) metric_name = Column(String) def __repr__(self): @@ -97,6 +98,7 @@ class Label(PromCatalogReflected, Base): Once reflected this class can be used in SQLAlchemy queries, e.g. select(Label).where(Label.key == 'project_name'). """ __tablename__ = "label" + __table_args__ = ({"schema": "_prom_catalog"}) id = Column(BigInteger, primary_key=True) key = Column(String) value = Column(String) From 1be7a13827b34261c2a0832ba917d5a9e54915b9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 7 Jun 2022 13:30:52 +0000 Subject: [PATCH 34/36] feat(Dependencies): Update dependency timescale/timescaledb to v2.5.2 | datasource | package | from | to | | ---------- | --------------------- | ----- | ----- | | docker | timescale/timescaledb | 2.5.1 | 2.5.2 | --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9546ef2..d3dc713 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: - "./src:/code/src:ro" timescaledb: - image: "timescale/timescaledb:2.5.1-pg14" + image: "timescale/timescaledb:2.5.2-pg14" container_name: dev_timescaledb command: postgres -c shared_preload_libraries=timescaledb networks: From 2fac7baa410c27c27f5747623e30d1820dbd3ea4 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 7 Jun 2022 17:47:23 +0000 Subject: [PATCH 35/36] feat(Dependencies): Update dependency sphinx to v5 | datasource | package | from | to | | ---------- | ------- | ----- | ----- | | pypi | sphinx | 4.5.0 | 5.0.1 | --- poetry.lock | 14 +++++++------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index e19cace..a8e0418 100644 --- a/poetry.lock +++ b/poetry.lock @@ -760,7 +760,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "4.5.0" +version = "5.0.1" description = "Python documentation generator" category = "dev" optional = false @@ -770,7 +770,7 @@ python-versions = ">=3.6" alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.18" +docutils = ">=0.14,<0.19" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -786,8 +786,8 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] -test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.950)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest (>=4.6)", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autodoc-typehints" @@ -1021,7 +1021,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "3.10.4" -content-hash = "2ba36dfcc14e10ba554e87b4bbf44f35f74f1824dec150f34baa923eccc7c919" +content-hash = "b7ee9693fe16b3982e5a2d4a00a091ce14cbc880f32da6a99dd95d04825238e3" [metadata.files] aiohttp = [ @@ -1812,8 +1812,8 @@ snowballstemmer = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] sphinx = [ - {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, - {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, + {file = "Sphinx-5.0.1-py3-none-any.whl", hash = "sha256:36aa2a3c2f6d5230be94585bc5d74badd5f9ed8f3388b8eedc1726fe45b1ad30"}, + {file = "Sphinx-5.0.1.tar.gz", hash = "sha256:f4da1187785a5bc7312cc271b0e867a93946c319d106363e102936a3d9857306"}, ] sphinx-autodoc-typehints = [ {file = "sphinx_autodoc_typehints-1.17.0-py3-none-any.whl", hash = "sha256:081daf53077b4ae1c28347d6d858e13e63aefe3b4aacef79fd717dd60687b470"}, diff --git a/pyproject.toml b/pyproject.toml index 6bc898a..bf68bf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ aiohttp-devtools = "1.0.post0" pre-commit = "2.18.1" black = {version = "22.3.0",allow-prereleases = true} sphinx-autodoc-typehints = "1.17.0" -sphinx = "4.5.0" +sphinx = "5.0.1" sphinxcontrib-trio = "1.1.2" lxml = "4.8.0" sphinxcontrib-programoutput = "0.17" From 61538f5227b1b1a584236dcf56e13e2ddf7dd118 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 8 Jun 2022 12:46:56 +0000 Subject: [PATCH 36/36] feat(Dependencies): Update dependency alembic to v1.8.0 | datasource | package | from | to | | ---------- | ------- | ----- | ----- | | pypi | alembic | 1.7.7 | 1.8.0 | --- poetry.lock | 10 +++++----- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index a8e0418..875c38c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,11 +94,11 @@ python-versions = "*" [[package]] name = "alembic" -version = "1.7.7" +version = "1.8.0" description = "A database migration tool for SQLAlchemy." category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] Mako = "*" @@ -1021,7 +1021,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "3.10.4" -content-hash = "b7ee9693fe16b3982e5a2d4a00a091ce14cbc880f32da6a99dd95d04825238e3" +content-hash = "c372b6eaf5146204f5e9de78898858f1b0d644435778d3c7474120ad35a9ff5a" [metadata.files] aiohttp = [ @@ -1123,8 +1123,8 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] alembic = [ - {file = "alembic-1.7.7-py3-none-any.whl", hash = "sha256:29be0856ec7591c39f4e1cb10f198045d890e6e2274cf8da80cb5e721a09642b"}, - {file = "alembic-1.7.7.tar.gz", hash = "sha256:4961248173ead7ce8a21efb3de378f13b8398e6630fab0eb258dc74a8af24c58"}, + {file = "alembic-1.8.0-py3-none-any.whl", hash = "sha256:b5ae4bbfc7d1302ed413989d39474d102e7cfa158f6d5969d2497955ffe85a30"}, + {file = "alembic-1.8.0.tar.gz", hash = "sha256:a2d4d90da70b30e70352cd9455e35873a255a31402a438fe24815758d7a0e5e1"}, ] anyio = [ {file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"}, diff --git a/pyproject.toml b/pyproject.toml index bf68bf0..f085f25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ aiohttp_jinja2 = "1.5" aiosmtplib = "1.1.6" asyncpg = "0.25.0" SQLAlchemy = "1.4.31" -alembic = "1.7.7" +alembic = "1.8.0" psycopg2 = "2.9.3" [tool.poetry.dev-dependencies]