From 7aaea80ae6bc4e9114108aff22089e77b8a4a69f Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 5 Aug 2022 13:25:54 +0000 Subject: [PATCH 01/59] chore(ci): update changelog with latest changes --- CHANGELOG.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f0b6819d8..f53a0d81e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,15 @@ # Unreleased + + +## [v1.27.0] - 2022-08-05 ## Bug Fixes * **ci:** changelog workflow must receive git tags too * **ci:** add additional input to accurately describe intent on skip * **ci:** job permissions +* **event_sources:** add test for Function URL AuthZ ([#1421](https://github.com/awslabs/aws-lambda-powertools-python/issues/1421)) ## Documentation @@ -24,6 +28,8 @@ ## Maintenance +* **ci:** sync area labels to prevent dedup +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** add manual trigger for docs * **ci:** update changelog with latest changes @@ -35,13 +41,13 @@ * **ci:** readd changelog step on release * **ci:** move changelog generation to rebuild_latest_doc workflow * **ci:** drop 3.6 from workflows -* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/awslabs/aws-lambda-powertools-python/issues/1400)) -* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/awslabs/aws-lambda-powertools-python/issues/1407)) +* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/awslabs/aws-lambda-powertools-python/issues/1399)) * **deps:** bump constructs from 10.1.1 to 10.1.66 ([#1414](https://github.com/awslabs/aws-lambda-powertools-python/issues/1414)) +* **deps:** bump jsii from 1.57.0 to 1.63.2 ([#1400](https://github.com/awslabs/aws-lambda-powertools-python/issues/1400)) * **deps:** bump constructs from 10.1.1 to 10.1.64 ([#1405](https://github.com/awslabs/aws-lambda-powertools-python/issues/1405)) * **deps:** bump attrs from 21.4.0 to 22.1.0 ([#1397](https://github.com/awslabs/aws-lambda-powertools-python/issues/1397)) * **deps:** bump constructs from 10.1.1 to 10.1.63 ([#1402](https://github.com/awslabs/aws-lambda-powertools-python/issues/1402)) -* **deps:** bump constructs from 10.1.1 to 10.1.60 ([#1399](https://github.com/awslabs/aws-lambda-powertools-python/issues/1399)) +* **deps:** bump constructs from 10.1.1 to 10.1.65 ([#1407](https://github.com/awslabs/aws-lambda-powertools-python/issues/1407)) * **deps-dev:** bump types-requests from 2.28.5 to 2.28.6 ([#1401](https://github.com/awslabs/aws-lambda-powertools-python/issues/1401)) * **deps-dev:** bump types-requests from 2.28.6 to 2.28.7 ([#1406](https://github.com/awslabs/aws-lambda-powertools-python/issues/1406)) * **docs:** remove pause sentence from roadmap ([#1409](https://github.com/awslabs/aws-lambda-powertools-python/issues/1409)) @@ -2193,7 +2199,8 @@ * Merge pull request [#5](https://github.com/awslabs/aws-lambda-powertools-python/issues/5) from jfuss/feat/python38 -[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.7...HEAD +[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.27.0...HEAD +[v1.27.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.7...v1.27.0 [v1.26.7]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.6...v1.26.7 [v1.26.6]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.5...v1.26.6 [v1.26.5]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.26.4...v1.26.5 From b1b954990da2d83046fcba193c466a409a1b24ec Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 15:36:11 +0200 Subject: [PATCH 02/59] fix(ci): add explicit origin to fix release detached head --- .github/workflows/reusable_publish_changelog.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml index 16fd2c06d18..9f045643aa1 100644 --- a/.github/workflows/reusable_publish_changelog.yml +++ b/.github/workflows/reusable_publish_changelog.yml @@ -6,6 +6,9 @@ on: permissions: contents: write +env: + GIT_ORIGIN: https://github.com/awslabs/aws-lambda-powertools-python + jobs: publish_changelog: # Force Github action to run only a single job at a time (based on the group name) @@ -18,12 +21,17 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Git client setup and refresh tip + - name: Git client setup run: | git config user.name "Release bot" git config user.email "aws-devax-open-source@amazon.com" git config pull.rebase true - git pull --rebase + - name: Git references setup and refresh + # `release` event trigger uses DETACHED Head instead of trunk like all other events + # so we need to explicitly configure it + run: | + git remote add origin ${GIT_ORIGIN} + git pull origin develop --rebase - name: "Generate latest changelog" run: make changelog - name: Update Changelog in trunk From 7b29013ea753f9fd7155ac37362bf089354fef34 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 15:38:25 +0200 Subject: [PATCH 03/59] Revert "fix(ci): add explicit origin to fix release detached head" This reverts commit b1b954990da2d83046fcba193c466a409a1b24ec. --- .github/workflows/reusable_publish_changelog.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml index 9f045643aa1..16fd2c06d18 100644 --- a/.github/workflows/reusable_publish_changelog.yml +++ b/.github/workflows/reusable_publish_changelog.yml @@ -6,9 +6,6 @@ on: permissions: contents: write -env: - GIT_ORIGIN: https://github.com/awslabs/aws-lambda-powertools-python - jobs: publish_changelog: # Force Github action to run only a single job at a time (based on the group name) @@ -21,17 +18,12 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Git client setup + - name: Git client setup and refresh tip run: | git config user.name "Release bot" git config user.email "aws-devax-open-source@amazon.com" git config pull.rebase true - - name: Git references setup and refresh - # `release` event trigger uses DETACHED Head instead of trunk like all other events - # so we need to explicitly configure it - run: | - git remote add origin ${GIT_ORIGIN} - git pull origin develop --rebase + git pull --rebase - name: "Generate latest changelog" run: make changelog - name: Update Changelog in trunk From 615448ef13e85c18ef6289868595ba3e7f80b08c Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 15:41:30 +0200 Subject: [PATCH 04/59] fix(ci): temporarily disable changelog upon release --- .github/workflows/publish.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3eaabc7fab6..16e51a5f542 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -101,14 +101,17 @@ jobs: aws ssm put-parameter --name "powertools-python-release-version" --value $RELEASE_VERSION --overwrite aws codepipeline start-pipeline-execution --name ${{ secrets.AWS_SAR_PIPELINE_NAME }} - changelog: - needs: release - permissions: - contents: write - uses: ./.github/workflows/reusable_publish_changelog.yml + # NOTE: `event` type brings a detached head failing git setup + # and reusable workflows only work as a standalone job + # meaning we need to research for a solution that works for non-detached and detached mode + # changelog: + # needs: release + # permissions: + # contents: write + # uses: ./.github/workflows/reusable_publish_changelog.yml docs: - needs: [release, changelog] + needs: release permissions: contents: write pages: write From 5e4856eec43613c87357cc7f47727e821dc92cdf Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 15:53:21 +0200 Subject: [PATCH 05/59] fix(ci): move from pip-tools to poetry on layers --- .github/workflows/publish_layer.yml | 7 +- layer/poetry.lock | 409 ++++++++++++++++++++++++++++ layer/pyproject.toml | 18 ++ layer/requirements-dev.txt | 2 - layer/requirements.txt | 84 ------ 5 files changed, 431 insertions(+), 89 deletions(-) create mode 100644 layer/poetry.lock create mode 100644 layer/pyproject.toml delete mode 100644 layer/requirements-dev.txt delete mode 100644 layer/requirements.txt diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index 2ad83624111..36c6b97b40f 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -28,6 +28,8 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Install poetry + run: pipx install poetry - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -36,7 +38,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.9" - cache: "pip" + cache: "poetry" - name: Set release notes tag run: | RELEASE_INPUT=${{ inputs.latest_published_version }} @@ -48,8 +50,7 @@ jobs: npm install -g aws-cdk@2.29.0 cdk --version - name: install deps - run: | - pip install -r requirements.txt + run: poetry install - name: CDK build run: cdk synth --context version=$RELEASE_TAG_VERSION -o cdk.out - name: zip output diff --git a/layer/poetry.lock b/layer/poetry.lock new file mode 100644 index 00000000000..15254157146 --- /dev/null +++ b/layer/poetry.lock @@ -0,0 +1,409 @@ +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +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", "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 = "aws-cdk-lib" +version = "2.35.0" +description = "Version 2 of the AWS Cloud Development Kit library" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.63.2,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "boto3" +version = "1.24.46" +description = "The AWS SDK for Python" +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +botocore = ">=1.27.46,<1.28.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.27.46" +description = "Low-level, data-driven core of boto 3." +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.13.8)"] + +[[package]] +name = "cattrs" +version = "22.1.0" +description = "Composable complex class support for attrs and dataclasses." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +attrs = ">=20" +exceptiongroup = {version = "*", markers = "python_version <= \"3.10\""} + +[[package]] +name = "cdk-lambda-powertools-python-layer" +version = "2.0.49" +description = "A lambda layer for AWS Powertools for python" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +aws-cdk-lib = ">=2.2.0,<3.0.0" +constructs = ">=10.0.5,<11.0.0" +jsii = ">=1.61.0,<2.0.0" +publication = ">=0.0.3" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "constructs" +version = "10.1.67" +description = "A programming model for software-defined state" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +jsii = ">=1.63.2,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "exceptiongroup" +version = "1.0.0rc8" +description = "Backport of PEP 654 (exception groups)" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jsii" +version = "1.63.2" +description = "Python client for jsii runtime" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +attrs = ">=21.2,<22.0" +cattrs = ">=1.8,<22.2" +publication = ">=0.0.3" +python-dateutil = "*" +typeguard = ">=2.13.3,<2.14.0" +typing-extensions = ">=3.7,<5.0" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "publication" +version = "0.0.3" +description = "Publication helps you maintain public-api-friendly modules by preventing unintentional access to private implementation details via introspection." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.11.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.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[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" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "s3transfer" +version = "0.6.0" +description = "An Amazon S3 Transfer Manager" +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typeguard" +version = "2.13.3" +description = "Run-time type checker for Python" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[package.extras] +doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["pytest", "typing-extensions", "mypy"] + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.11" +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.*, !=3.5.*, <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)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "4e22a360373fca274a19fd3b7c9e4e53ffb8ac96c827edfaa5a4feb126912133" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +aws-cdk-lib = [ + {file = "aws-cdk-lib-2.35.0.tar.gz", hash = "sha256:fc9cba4df0b60a9ab7f17ceb3b1c447d27e96cec9eb9e8c5b7ecfd1275878930"}, + {file = "aws_cdk_lib-2.35.0-py3-none-any.whl", hash = "sha256:ee481dca9335c32b5871e58ba697e27e2f1e92d9b81cf9341cfc6cc36127a2b0"}, +] +boto3 = [ + {file = "boto3-1.24.46-py3-none-any.whl", hash = "sha256:44026e44549148dbc5b261ead5f6b339e785680c350ef621bf85f7e2fca05b49"}, + {file = "boto3-1.24.46.tar.gz", hash = "sha256:b2d9d55f123a9a91eea2fd8e379d90abf37634420fbb45c22d67e10b324ec71b"}, +] +botocore = [ + {file = "botocore-1.27.46-py3-none-any.whl", hash = "sha256:747b7e94aef41498f063fc0be79c5af102d940beea713965179e1ead89c7e9ec"}, + {file = "botocore-1.27.46.tar.gz", hash = "sha256:f66d8305d1f59d83334df9b11b6512bb1e14698ec4d5d6d42f833f39f3304ca7"}, +] +cattrs = [ + {file = "cattrs-22.1.0-py3-none-any.whl", hash = "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364"}, + {file = "cattrs-22.1.0.tar.gz", hash = "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6"}, +] +cdk-lambda-powertools-python-layer = [ + {file = "cdk-lambda-powertools-python-layer-2.0.49.tar.gz", hash = "sha256:8055fc691539f16e22a40e3d3df9c3f59fb28012437b08c47c639aefb001f1b2"}, + {file = "cdk_lambda_powertools_python_layer-2.0.49-py3-none-any.whl", hash = "sha256:9b0a7b7344f9ccb486564af728cefeac743687bfb131631e6d9171a55800dbac"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +constructs = [ + {file = "constructs-10.1.67-py3-none-any.whl", hash = "sha256:d597d8d5387328c1e95fa674d5d64969b1c1a479e63544e53a067a5d95b5c46b"}, + {file = "constructs-10.1.67.tar.gz", hash = "sha256:8b9fdf5040dde63545c08b8cc86fcd019512e0d16ee599c82b1201a5806f0066"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"}, + {file = "exceptiongroup-1.0.0rc8.tar.gz", hash = "sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +jmespath = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] +jsii = [ + {file = "jsii-1.63.2-py3-none-any.whl", hash = "sha256:ae8cbc84c633382c317dc367e1441bb2afd8b74ed82b3557b8df15e05316b14d"}, + {file = "jsii-1.63.2.tar.gz", hash = "sha256:6f68dcd82395ccd12606b31383f611adfefd246082750350891a2a277562f34b"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +publication = [ + {file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"}, + {file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +s3transfer = [ + {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"}, + {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typeguard = [ + {file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"}, + {file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +urllib3 = [] diff --git a/layer/pyproject.toml b/layer/pyproject.toml new file mode 100644 index 00000000000..d5dcef9c810 --- /dev/null +++ b/layer/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "aws-lambda-powertools-python-layer" +version = "0.1.0" +description = "AWS Lambda Powertools for Python Lambda Layers" +authors = ["DevAx "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.9" +cdk-lambda-powertools-python-layer = "^2.0.49" + +[tool.poetry.dev-dependencies] +pytest = "^7.1.2" +boto3 = "^1.24.46" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/layer/requirements-dev.txt b/layer/requirements-dev.txt deleted file mode 100644 index f3ec7d732b5..00000000000 --- a/layer/requirements-dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest==6.2.5 -boto3==1.24.22 diff --git a/layer/requirements.txt b/layer/requirements.txt deleted file mode 100644 index c165a46a846..00000000000 --- a/layer/requirements.txt +++ /dev/null @@ -1,84 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: -# -# pip-compile --generate-hashes requirements.txt -# -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c - # via - # -r requirements.txt - # cattrs - # jsii -aws-cdk-lib==2.31.1 \ - --hash=sha256:a07f6a247be110e874af374fa683d6c7eba86dfc9781cb555428b534c75bd4c0 \ - --hash=sha256:a3868c367cab3cf09e6bb68405e31f4342fc4a4905ccc3e3fdde133d520206c0 - # via - # -r requirements.txt - # cdk-lambda-powertools-python-layer -cattrs==22.1.0 \ - --hash=sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6 \ - --hash=sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364 - # via - # -r requirements.txt - # jsii -cdk-lambda-powertools-python-layer==2.0.49 \ - --hash=sha256:8055fc691539f16e22a40e3d3df9c3f59fb28012437b08c47c639aefb001f1b2 \ - --hash=sha256:9b0a7b7344f9ccb486564af728cefeac743687bfb131631e6d9171a55800dbac - # via -r requirements.txt -constructs==10.1.66 \ - --hash=sha256:0f9a7a34e4e07c11a792214481b41559e0ef17b3f3e6de6e018829c39882064e \ - --hash=sha256:79ecb6a23edafc9939a026d82f63fff6f3a0fe8520d3bc1d47f7731aa229eea4 - # via - # -r requirements.txt - # aws-cdk-lib - # cdk-lambda-powertools-python-layer -exceptiongroup==1.0.0rc8 \ - --hash=sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a \ - --hash=sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035 - # via - # -r requirements.txt - # cattrs -jsii==1.63.2 \ - --hash=sha256:6f68dcd82395ccd12606b31383f611adfefd246082750350891a2a277562f34b \ - --hash=sha256:ae8cbc84c633382c317dc367e1441bb2afd8b74ed82b3557b8df15e05316b14d - # via - # -r requirements.txt - # aws-cdk-lib - # cdk-lambda-powertools-python-layer - # constructs -publication==0.0.3 \ - --hash=sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6 \ - --hash=sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4 - # via - # -r requirements.txt - # aws-cdk-lib - # cdk-lambda-powertools-python-layer - # constructs - # jsii -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via - # -r requirements.txt - # jsii -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # -r requirements.txt - # python-dateutil -typeguard==2.13.3 \ - --hash=sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4 \ - --hash=sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1 - # via - # -r requirements.txt - # constructs - # jsii -typing-extensions==4.3.0 \ - --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ - --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 - # via - # -r requirements.txt - # jsii From 476b99f2cda283ad96b2bae1af2d3b70890e4c7a Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 16:08:58 +0200 Subject: [PATCH 06/59] fix(ci): move from pip-tools to poetry on layers reusable workflow --- .github/workflows/reusable_deploy_layer_stack.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index 705ef530853..ed18c984e26 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -55,6 +55,8 @@ jobs: steps: - name: checkout uses: actions/checkout@v3 + - name: Install poetry + run: pipx install poetry - name: aws credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -74,8 +76,7 @@ jobs: npm install -g aws-cdk@2.29.0 cdk --version - name: install deps - run: | - pip install -r requirements.txt + run: poetry install - name: Download artifact uses: actions/download-artifact@v3 with: From b0eae58f4d8dcc736d7157f11b0282eb604e8d8c Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 16:19:00 +0200 Subject: [PATCH 07/59] fix(ci): add cdk v2 dep for layers workflow --- layer/poetry.lock | 2 +- layer/pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/layer/poetry.lock b/layer/poetry.lock index 15254157146..182094a8b9d 100644 --- a/layer/poetry.lock +++ b/layer/poetry.lock @@ -304,7 +304,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "4e22a360373fca274a19fd3b7c9e4e53ffb8ac96c827edfaa5a4feb126912133" +content-hash = "a68a9649808efb49529ace7d990559e6569be096bf2d86234f3bd056bae0fdc3" [metadata.files] atomicwrites = [ diff --git a/layer/pyproject.toml b/layer/pyproject.toml index d5dcef9c810..7f219453a72 100644 --- a/layer/pyproject.toml +++ b/layer/pyproject.toml @@ -8,6 +8,7 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.9" cdk-lambda-powertools-python-layer = "^2.0.49" +aws-cdk-lib = "^2.35.0" [tool.poetry.dev-dependencies] pytest = "^7.1.2" From 742a3a9a43cee0d061b5b4fef21c0a2b3eb7159b Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 16:29:34 +0200 Subject: [PATCH 08/59] fix(ci): disable poetry venv for layer workflow as cdk ignores venv --- .github/workflows/publish_layer.yml | 6 +++++- .github/workflows/reusable_deploy_layer_stack.yml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index 36c6b97b40f..10373f1c547 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -29,7 +29,11 @@ jobs: with: fetch-depth: 0 - name: Install poetry - run: pipx install poetry + # CDK spawns system python when compiling stack + # therefore it ignores activated virtual env, thus missing deps + run: | + pipx install poetry + poetry config virtualenvs.create false - name: Setup Node.js uses: actions/setup-node@v3 with: diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index ed18c984e26..ceee7bade71 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -56,7 +56,11 @@ jobs: - name: checkout uses: actions/checkout@v3 - name: Install poetry - run: pipx install poetry + # CDK spawns system python when compiling stack + # therefore it ignores activated virtual env, thus missing deps + run: | + pipx install poetry + poetry config virtualenvs.create false - name: aws credentials uses: aws-actions/configure-aws-credentials@v1 with: From 31f2bdbdeb5da5472f143bdc84e66c2c65215661 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 16:42:24 +0200 Subject: [PATCH 09/59] fix(ci): use poetry to resolve layer deps; pip for CDK --- .github/workflows/publish_layer.yml | 16 ++++++++-------- .../workflows/reusable_deploy_layer_stack.yml | 12 +++++++----- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index 10373f1c547..c6921798738 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -29,11 +29,7 @@ jobs: with: fetch-depth: 0 - name: Install poetry - # CDK spawns system python when compiling stack - # therefore it ignores activated virtual env, thus missing deps - run: | - pipx install poetry - poetry config virtualenvs.create false + run: pipx install poetry - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -42,7 +38,13 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.9" - cache: "poetry" + cache: "pip" + - name: Install project dependencies + # CDK spawns system python when compiling stack + # therefore it ignores both activated virtual env and cached interpreter by GH + run: | + poetry export --format requirements.txt --output requirements.txt + pip install requirements.txt - name: Set release notes tag run: | RELEASE_INPUT=${{ inputs.latest_published_version }} @@ -53,8 +55,6 @@ jobs: run: | npm install -g aws-cdk@2.29.0 cdk --version - - name: install deps - run: poetry install - name: CDK build run: cdk synth --context version=$RELEASE_TAG_VERSION -o cdk.out - name: zip output diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index ceee7bade71..18d0c6c0401 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -56,11 +56,7 @@ jobs: - name: checkout uses: actions/checkout@v3 - name: Install poetry - # CDK spawns system python when compiling stack - # therefore it ignores activated virtual env, thus missing deps - run: | - pipx install poetry - poetry config virtualenvs.create false + run: pipx install poetry - name: aws credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -75,6 +71,12 @@ jobs: with: python-version: "3.9" cache: "pip" + - name: Install project dependencies + # CDK spawns system python when compiling stack + # therefore it ignores both activated virtual env and cached interpreter by GH + run: | + poetry export --format requirements.txt --output requirements.txt + pip install requirements.txt - name: install cdk and deps run: | npm install -g aws-cdk@2.29.0 From 0949de1e50f0db094027004aea9ddb9b9a456479 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 16:45:43 +0200 Subject: [PATCH 10/59] fix(ci): typo and bust gh actions cache --- .github/workflows/publish_layer.yml | 4 ++-- .github/workflows/reusable_deploy_layer_stack.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index c6921798738..564cbfad9de 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -39,12 +39,12 @@ jobs: with: python-version: "3.9" cache: "pip" - - name: Install project dependencies + - name: Resolve and install project dependencies # CDK spawns system python when compiling stack # therefore it ignores both activated virtual env and cached interpreter by GH run: | poetry export --format requirements.txt --output requirements.txt - pip install requirements.txt + pip install -r requirements.txt - name: Set release notes tag run: | RELEASE_INPUT=${{ inputs.latest_published_version }} diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index 18d0c6c0401..e1190e19873 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -71,12 +71,12 @@ jobs: with: python-version: "3.9" cache: "pip" - - name: Install project dependencies + - name: Resolve and install project dependencies # CDK spawns system python when compiling stack # therefore it ignores both activated virtual env and cached interpreter by GH run: | poetry export --format requirements.txt --output requirements.txt - pip install requirements.txt + pip install -r requirements.txt - name: install cdk and deps run: | npm install -g aws-cdk@2.29.0 From 0ae515fab1d841ef0e457244f6ab353146ce51df Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 17:04:49 +0200 Subject: [PATCH 11/59] docs(layer): upgrade to 1.27.0 --- docs/index.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/index.md b/docs/index.md index 17808c89917..95ce2c2a707 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,7 +14,7 @@ A suite of utilities for AWS Lambda functions to ease adopting best practices su Powertools is available in the following formats: -* **Lambda Layer**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:28**](#){: .copyMe}:clipboard: +* **Lambda Layer**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:29**](#){: .copyMe}:clipboard: * **PyPi**: **`pip install aws-lambda-powertools`** ???+ hint "Support this project by using Lambda Layers :heart:" @@ -32,23 +32,23 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: | Region | Layer ARN | | ---------------- | -------------------------------------------------------------------------------------------------------- | - | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | + | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | ??? question "Can't find our Lambda Layer for your preferred AWS region?" You can use [Serverless Application Repository (SAR)](#sar) method, our [CDK Layer Construct](https://github.com/aws-samples/cdk-lambda-powertools-python-layer){target="_blank"}, or PyPi like you normally would for any other library. @@ -62,7 +62,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: Type: AWS::Serverless::Function Properties: Layers: - - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:28 + - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:29 ``` === "Serverless framework" @@ -72,7 +72,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: hello: handler: lambda_function.lambda_handler layers: - - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPython:28 + - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPython:29 ``` === "CDK" @@ -88,7 +88,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn( self, id="lambda-powertools", - layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPython:28" + layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPython:29" ) aws_lambda.Function(self, 'sample-app-lambda', @@ -137,7 +137,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: role = aws_iam_role.iam_for_lambda.arn handler = "index.test" runtime = "python3.9" - layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:28"] + layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:29"] source_code_hash = filebase64sha256("lambda_function_payload.zip") } @@ -156,7 +156,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: ? Do you want to configure advanced settings? Yes ... ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29 ❯ amplify push -y @@ -167,7 +167,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: - Name: ? Which setting do you want to update? Lambda layers configuration ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29 ? Do you want to edit the local lambda function now? No ``` @@ -175,7 +175,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: Change {region} to your AWS region, e.g. `eu-west-1` ```bash title="AWS CLI" - aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:28 --region {region} + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:29 --region {region} ``` The pre-signed URL to download this Lambda Layer will be within `Location` key. From 52772c28e13bd18931e4f1d0cf920790e4175eba Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 5 Aug 2022 15:05:19 +0000 Subject: [PATCH 12/59] chore(ci): update changelog with latest changes --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53a0d81e57..9ac0b480634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ # Unreleased +## Bug Fixes + +* **ci:** typo and bust gh actions cache +* **ci:** use poetry to resolve layer deps; pip for CDK +* **ci:** disable poetry venv for layer workflow as cdk ignores venv +* **ci:** add cdk v2 dep for layers workflow +* **ci:** move from pip-tools to poetry on layers reusable workflow +* **ci:** move from pip-tools to poetry on layers +* **ci:** temporarily disable changelog upon release +* **ci:** add explicit origin to fix release detached head + +## Documentation + +* **layer:** upgrade to 1.27.0 + +## Maintenance + +* **ci:** update changelog with latest changes + ## [v1.27.0] - 2022-08-05 From f9779a80012ab75e6e4d2df788a9ce3e277a8a68 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 5 Aug 2022 15:08:52 +0000 Subject: [PATCH 13/59] chore(ci): update changelog with latest changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac0b480634..6b85db5304d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ ## Maintenance * **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes From 355f8cda34756d52fcd548f70ef3160f00c76585 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 15:53:21 +0200 Subject: [PATCH 14/59] fix(ci): move from pip-tools to poetry on layers to fix conflicts --- .github/workflows/publish_layer.yml | 11 +- .../workflows/reusable_deploy_layer_stack.yml | 11 +- layer/poetry.lock | 409 ++++++++++++++++++ layer/pyproject.toml | 19 + layer/requirements-dev.txt | 2 - layer/requirements.txt | 84 ---- 6 files changed, 445 insertions(+), 91 deletions(-) create mode 100644 layer/poetry.lock create mode 100644 layer/pyproject.toml delete mode 100644 layer/requirements-dev.txt delete mode 100644 layer/requirements.txt diff --git a/.github/workflows/publish_layer.yml b/.github/workflows/publish_layer.yml index 2ad83624111..564cbfad9de 100644 --- a/.github/workflows/publish_layer.yml +++ b/.github/workflows/publish_layer.yml @@ -28,6 +28,8 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Install poetry + run: pipx install poetry - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -37,6 +39,12 @@ jobs: with: python-version: "3.9" cache: "pip" + - name: Resolve and install project dependencies + # CDK spawns system python when compiling stack + # therefore it ignores both activated virtual env and cached interpreter by GH + run: | + poetry export --format requirements.txt --output requirements.txt + pip install -r requirements.txt - name: Set release notes tag run: | RELEASE_INPUT=${{ inputs.latest_published_version }} @@ -47,9 +55,6 @@ jobs: run: | npm install -g aws-cdk@2.29.0 cdk --version - - name: install deps - run: | - pip install -r requirements.txt - name: CDK build run: cdk synth --context version=$RELEASE_TAG_VERSION -o cdk.out - name: zip output diff --git a/.github/workflows/reusable_deploy_layer_stack.yml b/.github/workflows/reusable_deploy_layer_stack.yml index 705ef530853..e1190e19873 100644 --- a/.github/workflows/reusable_deploy_layer_stack.yml +++ b/.github/workflows/reusable_deploy_layer_stack.yml @@ -55,6 +55,8 @@ jobs: steps: - name: checkout uses: actions/checkout@v3 + - name: Install poetry + run: pipx install poetry - name: aws credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -69,13 +71,18 @@ jobs: with: python-version: "3.9" cache: "pip" + - name: Resolve and install project dependencies + # CDK spawns system python when compiling stack + # therefore it ignores both activated virtual env and cached interpreter by GH + run: | + poetry export --format requirements.txt --output requirements.txt + pip install -r requirements.txt - name: install cdk and deps run: | npm install -g aws-cdk@2.29.0 cdk --version - name: install deps - run: | - pip install -r requirements.txt + run: poetry install - name: Download artifact uses: actions/download-artifact@v3 with: diff --git a/layer/poetry.lock b/layer/poetry.lock new file mode 100644 index 00000000000..182094a8b9d --- /dev/null +++ b/layer/poetry.lock @@ -0,0 +1,409 @@ +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +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", "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 = "aws-cdk-lib" +version = "2.35.0" +description = "Version 2 of the AWS Cloud Development Kit library" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +constructs = ">=10.0.0,<11.0.0" +jsii = ">=1.63.2,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "boto3" +version = "1.24.46" +description = "The AWS SDK for Python" +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +botocore = ">=1.27.46,<1.28.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.6.0,<0.7.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "botocore" +version = "1.27.46" +description = "Low-level, data-driven core of boto 3." +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = ">=1.25.4,<1.27" + +[package.extras] +crt = ["awscrt (==0.13.8)"] + +[[package]] +name = "cattrs" +version = "22.1.0" +description = "Composable complex class support for attrs and dataclasses." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +attrs = ">=20" +exceptiongroup = {version = "*", markers = "python_version <= \"3.10\""} + +[[package]] +name = "cdk-lambda-powertools-python-layer" +version = "2.0.49" +description = "A lambda layer for AWS Powertools for python" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +aws-cdk-lib = ">=2.2.0,<3.0.0" +constructs = ">=10.0.5,<11.0.0" +jsii = ">=1.61.0,<2.0.0" +publication = ">=0.0.3" + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "constructs" +version = "10.1.67" +description = "A programming model for software-defined state" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +jsii = ">=1.63.2,<2.0.0" +publication = ">=0.0.3" +typeguard = ">=2.13.3,<2.14.0" + +[[package]] +name = "exceptiongroup" +version = "1.0.0rc8" +description = "Backport of PEP 654 (exception groups)" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jsii" +version = "1.63.2" +description = "Python client for jsii runtime" +category = "main" +optional = false +python-versions = "~=3.7" + +[package.dependencies] +attrs = ">=21.2,<22.0" +cattrs = ">=1.8,<22.2" +publication = ">=0.0.3" +python-dateutil = "*" +typeguard = ">=2.13.3,<2.14.0" +typing-extensions = ">=3.7,<5.0" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "publication" +version = "0.0.3" +description = "Publication helps you maintain public-api-friendly modules by preventing unintentional access to private implementation details via introspection." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.11.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.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "pytest" +version = "7.1.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[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" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "s3transfer" +version = "0.6.0" +description = "An Amazon S3 Transfer Manager" +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +botocore = ">=1.12.36,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.20.29,<2.0a.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typeguard" +version = "2.13.3" +description = "Run-time type checker for Python" +category = "main" +optional = false +python-versions = ">=3.5.3" + +[package.extras] +doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["pytest", "typing-extensions", "mypy"] + +[[package]] +name = "typing-extensions" +version = "4.3.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.11" +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.*, !=3.5.*, <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)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "a68a9649808efb49529ace7d990559e6569be096bf2d86234f3bd056bae0fdc3" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +aws-cdk-lib = [ + {file = "aws-cdk-lib-2.35.0.tar.gz", hash = "sha256:fc9cba4df0b60a9ab7f17ceb3b1c447d27e96cec9eb9e8c5b7ecfd1275878930"}, + {file = "aws_cdk_lib-2.35.0-py3-none-any.whl", hash = "sha256:ee481dca9335c32b5871e58ba697e27e2f1e92d9b81cf9341cfc6cc36127a2b0"}, +] +boto3 = [ + {file = "boto3-1.24.46-py3-none-any.whl", hash = "sha256:44026e44549148dbc5b261ead5f6b339e785680c350ef621bf85f7e2fca05b49"}, + {file = "boto3-1.24.46.tar.gz", hash = "sha256:b2d9d55f123a9a91eea2fd8e379d90abf37634420fbb45c22d67e10b324ec71b"}, +] +botocore = [ + {file = "botocore-1.27.46-py3-none-any.whl", hash = "sha256:747b7e94aef41498f063fc0be79c5af102d940beea713965179e1ead89c7e9ec"}, + {file = "botocore-1.27.46.tar.gz", hash = "sha256:f66d8305d1f59d83334df9b11b6512bb1e14698ec4d5d6d42f833f39f3304ca7"}, +] +cattrs = [ + {file = "cattrs-22.1.0-py3-none-any.whl", hash = "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364"}, + {file = "cattrs-22.1.0.tar.gz", hash = "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6"}, +] +cdk-lambda-powertools-python-layer = [ + {file = "cdk-lambda-powertools-python-layer-2.0.49.tar.gz", hash = "sha256:8055fc691539f16e22a40e3d3df9c3f59fb28012437b08c47c639aefb001f1b2"}, + {file = "cdk_lambda_powertools_python_layer-2.0.49-py3-none-any.whl", hash = "sha256:9b0a7b7344f9ccb486564af728cefeac743687bfb131631e6d9171a55800dbac"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +constructs = [ + {file = "constructs-10.1.67-py3-none-any.whl", hash = "sha256:d597d8d5387328c1e95fa674d5d64969b1c1a479e63544e53a067a5d95b5c46b"}, + {file = "constructs-10.1.67.tar.gz", hash = "sha256:8b9fdf5040dde63545c08b8cc86fcd019512e0d16ee599c82b1201a5806f0066"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"}, + {file = "exceptiongroup-1.0.0rc8.tar.gz", hash = "sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +jmespath = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] +jsii = [ + {file = "jsii-1.63.2-py3-none-any.whl", hash = "sha256:ae8cbc84c633382c317dc367e1441bb2afd8b74ed82b3557b8df15e05316b14d"}, + {file = "jsii-1.63.2.tar.gz", hash = "sha256:6f68dcd82395ccd12606b31383f611adfefd246082750350891a2a277562f34b"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +publication = [ + {file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"}, + {file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +s3transfer = [ + {file = "s3transfer-0.6.0-py3-none-any.whl", hash = "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd"}, + {file = "s3transfer-0.6.0.tar.gz", hash = "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typeguard = [ + {file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"}, + {file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +urllib3 = [] diff --git a/layer/pyproject.toml b/layer/pyproject.toml new file mode 100644 index 00000000000..7f219453a72 --- /dev/null +++ b/layer/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "aws-lambda-powertools-python-layer" +version = "0.1.0" +description = "AWS Lambda Powertools for Python Lambda Layers" +authors = ["DevAx "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.9" +cdk-lambda-powertools-python-layer = "^2.0.49" +aws-cdk-lib = "^2.35.0" + +[tool.poetry.dev-dependencies] +pytest = "^7.1.2" +boto3 = "^1.24.46" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/layer/requirements-dev.txt b/layer/requirements-dev.txt deleted file mode 100644 index f3ec7d732b5..00000000000 --- a/layer/requirements-dev.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest==6.2.5 -boto3==1.24.22 diff --git a/layer/requirements.txt b/layer/requirements.txt deleted file mode 100644 index c165a46a846..00000000000 --- a/layer/requirements.txt +++ /dev/null @@ -1,84 +0,0 @@ -# -# This file is autogenerated by pip-compile with python 3.9 -# To update, run: -# -# pip-compile --generate-hashes requirements.txt -# -attrs==22.1.0 \ - --hash=sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6 \ - --hash=sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c - # via - # -r requirements.txt - # cattrs - # jsii -aws-cdk-lib==2.31.1 \ - --hash=sha256:a07f6a247be110e874af374fa683d6c7eba86dfc9781cb555428b534c75bd4c0 \ - --hash=sha256:a3868c367cab3cf09e6bb68405e31f4342fc4a4905ccc3e3fdde133d520206c0 - # via - # -r requirements.txt - # cdk-lambda-powertools-python-layer -cattrs==22.1.0 \ - --hash=sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6 \ - --hash=sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364 - # via - # -r requirements.txt - # jsii -cdk-lambda-powertools-python-layer==2.0.49 \ - --hash=sha256:8055fc691539f16e22a40e3d3df9c3f59fb28012437b08c47c639aefb001f1b2 \ - --hash=sha256:9b0a7b7344f9ccb486564af728cefeac743687bfb131631e6d9171a55800dbac - # via -r requirements.txt -constructs==10.1.66 \ - --hash=sha256:0f9a7a34e4e07c11a792214481b41559e0ef17b3f3e6de6e018829c39882064e \ - --hash=sha256:79ecb6a23edafc9939a026d82f63fff6f3a0fe8520d3bc1d47f7731aa229eea4 - # via - # -r requirements.txt - # aws-cdk-lib - # cdk-lambda-powertools-python-layer -exceptiongroup==1.0.0rc8 \ - --hash=sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a \ - --hash=sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035 - # via - # -r requirements.txt - # cattrs -jsii==1.63.2 \ - --hash=sha256:6f68dcd82395ccd12606b31383f611adfefd246082750350891a2a277562f34b \ - --hash=sha256:ae8cbc84c633382c317dc367e1441bb2afd8b74ed82b3557b8df15e05316b14d - # via - # -r requirements.txt - # aws-cdk-lib - # cdk-lambda-powertools-python-layer - # constructs -publication==0.0.3 \ - --hash=sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6 \ - --hash=sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4 - # via - # -r requirements.txt - # aws-cdk-lib - # cdk-lambda-powertools-python-layer - # constructs - # jsii -python-dateutil==2.8.2 \ - --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ - --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 - # via - # -r requirements.txt - # jsii -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # -r requirements.txt - # python-dateutil -typeguard==2.13.3 \ - --hash=sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4 \ - --hash=sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1 - # via - # -r requirements.txt - # constructs - # jsii -typing-extensions==4.3.0 \ - --hash=sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02 \ - --hash=sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6 - # via - # -r requirements.txt - # jsii From 66e367416f88ea37080bf7b68df9672d839cea31 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 5 Aug 2022 17:04:49 +0200 Subject: [PATCH 15/59] docs(layer): upgrade to 1.27.0 --- docs/index.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/index.md b/docs/index.md index 17808c89917..95ce2c2a707 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,7 +14,7 @@ A suite of utilities for AWS Lambda functions to ease adopting best practices su Powertools is available in the following formats: -* **Lambda Layer**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:28**](#){: .copyMe}:clipboard: +* **Lambda Layer**: [**arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:29**](#){: .copyMe}:clipboard: * **PyPi**: **`pip install aws-lambda-powertools`** ???+ hint "Support this project by using Lambda Layers :heart:" @@ -32,23 +32,23 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: | Region | Layer ARN | | ---------------- | -------------------------------------------------------------------------------------------------------- | - | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | - | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPython:28](#){: .copyMe}:clipboard: | + | `us-east-1` | [arn:aws:lambda:us-east-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `us-east-2` | [arn:aws:lambda:us-east-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `us-west-1` | [arn:aws:lambda:us-west-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `us-west-2` | [arn:aws:lambda:us-west-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-south-1` | [arn:aws:lambda:ap-south-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-northeast-1` | [arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-northeast-2` | [arn:aws:lambda:ap-northeast-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-northeast-3` | [arn:aws:lambda:ap-northeast-3:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-southeast-1` | [arn:aws:lambda:ap-southeast-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ap-southeast-2` | [arn:aws:lambda:ap-southeast-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-central-1` | [arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-west-1` | [arn:aws:lambda:eu-west-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-west-2` | [arn:aws:lambda:eu-west-2:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-west-3` | [arn:aws:lambda:eu-west-3:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `eu-north-1` | [arn:aws:lambda:eu-north-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `ca-central-1` | [arn:aws:lambda:ca-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | + | `sa-east-1` | [arn:aws:lambda:sa-east-1:017000801446:layer:AWSLambdaPowertoolsPython:29](#){: .copyMe}:clipboard: | ??? question "Can't find our Lambda Layer for your preferred AWS region?" You can use [Serverless Application Repository (SAR)](#sar) method, our [CDK Layer Construct](https://github.com/aws-samples/cdk-lambda-powertools-python-layer){target="_blank"}, or PyPi like you normally would for any other library. @@ -62,7 +62,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: Type: AWS::Serverless::Function Properties: Layers: - - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:28 + - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:29 ``` === "Serverless framework" @@ -72,7 +72,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: hello: handler: lambda_function.lambda_handler layers: - - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPython:28 + - arn:aws:lambda:${aws:region}:017000801446:layer:AWSLambdaPowertoolsPython:29 ``` === "CDK" @@ -88,7 +88,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: powertools_layer = aws_lambda.LayerVersion.from_layer_version_arn( self, id="lambda-powertools", - layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPython:28" + layer_version_arn=f"arn:aws:lambda:{env.region}:017000801446:layer:AWSLambdaPowertoolsPython:29" ) aws_lambda.Function(self, 'sample-app-lambda', @@ -137,7 +137,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: role = aws_iam_role.iam_for_lambda.arn handler = "index.test" runtime = "python3.9" - layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:28"] + layers = ["arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:29"] source_code_hash = filebase64sha256("lambda_function_payload.zip") } @@ -156,7 +156,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: ? Do you want to configure advanced settings? Yes ... ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29 ❯ amplify push -y @@ -167,7 +167,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: - Name: ? Which setting do you want to update? Lambda layers configuration ? Do you want to enable Lambda layers for this function? Yes - ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:28 + ? Enter up to 5 existing Lambda layer ARNs (comma-separated): arn:aws:lambda:eu-central-1:017000801446:layer:AWSLambdaPowertoolsPython:29 ? Do you want to edit the local lambda function now? No ``` @@ -175,7 +175,7 @@ You can include Lambda Powertools Lambda Layer using [AWS Lambda Console](https: Change {region} to your AWS region, e.g. `eu-west-1` ```bash title="AWS CLI" - aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:28 --region {region} + aws lambda get-layer-version-by-arn --arn arn:aws:lambda:{region}:017000801446:layer:AWSLambdaPowertoolsPython:29 --region {region} ``` The pre-signed URL to download this Lambda Layer will be within `Location` key. From a71a0e5b29aae8a46ef1f43a82f74e146669404c Mon Sep 17 00:00:00 2001 From: Alexander Melnyk Date: Tue, 9 Aug 2022 14:17:49 +0200 Subject: [PATCH 16/59] chore(ci): reduce payload and only send prod notification --- layer/app.py | 18 ++++++++++++++---- layer/layer/canary/app.py | 13 +++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/layer/app.py b/layer/app.py index 78e99b17654..50f8090482e 100644 --- a/layer/app.py +++ b/layer/app.py @@ -12,12 +12,22 @@ if not POWERTOOLS_VERSION: raise ValueError( - "Please set the version for Powertools by passing the '--context=version:' parameter to the CDK " + "Please set the version for Powertools by passing the '--context version=' parameter to the CDK " "synth step." ) -LayerStack(app, "LayerStack", powertools_version=POWERTOOLS_VERSION, ssm_paramter_layer_arn=SSM_PARAM_LAYER_ARN) - -CanaryStack(app, "CanaryStack", powertools_version=POWERTOOLS_VERSION, ssm_paramter_layer_arn=SSM_PARAM_LAYER_ARN) +LayerStack( + app, + "LayerStack", + powertools_version=POWERTOOLS_VERSION, + ssm_paramter_layer_arn=SSM_PARAM_LAYER_ARN, +) + +CanaryStack( + app, + "CanaryStack", + powertools_version=POWERTOOLS_VERSION, + ssm_paramter_layer_arn=SSM_PARAM_LAYER_ARN, +) app.synth() diff --git a/layer/layer/canary/app.py b/layer/layer/canary/app.py index 31db94dd92b..1011fc654c2 100644 --- a/layer/layer/canary/app.py +++ b/layer/layer/canary/app.py @@ -42,7 +42,9 @@ def on_create(event): def check_envs(): - logger.info('Checking required envs ["POWERTOOLS_LAYER_ARN", "AWS_REGION", "STAGE"]') + logger.info( + 'Checking required envs ["POWERTOOLS_LAYER_ARN", "AWS_REGION", "STAGE"]' + ) if not layer_arn: raise ValueError("POWERTOOLS_LAYER_ARN is not set. Aborting...") if not powertools_version: @@ -73,6 +75,11 @@ def send_notification(): """ sends an event to version tracking event bridge """ + if stage != "PROD": + logger.info( + "Not sending notification to event bus, because this is not the PROD stage" + ) + return event = { "Time": datetime.datetime.now(), "Source": "powertools.layer.canary", @@ -80,10 +87,8 @@ def send_notification(): "DetailType": "deployment", "Detail": json.dumps( { - "id": "powertools-python", - "stage": stage, - "region": os.environ["AWS_REGION"], "version": powertools_version, + "region": os.environ["AWS_REGION"], "layerArn": layer_arn, } ), From 7be425e05f2c193a20c1558de867dc39b81cabb3 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 9 Aug 2022 15:07:30 +0100 Subject: [PATCH 17/59] docs(jmespath_util): snippets split, improved, and lint (#1419) Co-authored-by: heitorlessa --- docs/utilities/jmespath_functions.md | 254 +++++++----------- .../extract_data_from_builtin_envelope.json | 20 ++ .../src/extract_data_from_builtin_envelope.py | 9 + .../src/extract_data_from_envelope.json | 12 + .../src/extract_data_from_envelope.py | 12 + ...owertools_base64_gzip_jmespath_function.py | 41 +++ ...wertools_base64_gzip_jmespath_payload.json | 3 + .../powertools_base64_gzip_jmespath_schema.py | 47 ++++ .../powertools_base64_jmespath_function.py | 63 +++++ .../powertools_base64_jmespath_payload.json | 3 + .../src/powertools_base64_jmespath_schema.py | 46 ++++ .../powertools_custom_jmespath_function.json | 14 + .../powertools_custom_jmespath_function.py | 45 ++++ .../powertools_json_idempotency_jmespath.json | 30 +++ .../powertools_json_idempotency_jmespath.py | 34 +++ .../src/powertools_json_jmespath_function.py | 56 ++++ .../src/powertools_json_jmespath_payload.json | 3 + .../src/powertools_json_jmespath_schema.py | 46 ++++ 18 files changed, 575 insertions(+), 163 deletions(-) create mode 100644 examples/jmespath_functions/src/extract_data_from_builtin_envelope.json create mode 100644 examples/jmespath_functions/src/extract_data_from_builtin_envelope.py create mode 100644 examples/jmespath_functions/src/extract_data_from_envelope.json create mode 100644 examples/jmespath_functions/src/extract_data_from_envelope.py create mode 100644 examples/jmespath_functions/src/powertools_base64_gzip_jmespath_function.py create mode 100644 examples/jmespath_functions/src/powertools_base64_gzip_jmespath_payload.json create mode 100644 examples/jmespath_functions/src/powertools_base64_gzip_jmespath_schema.py create mode 100644 examples/jmespath_functions/src/powertools_base64_jmespath_function.py create mode 100644 examples/jmespath_functions/src/powertools_base64_jmespath_payload.json create mode 100644 examples/jmespath_functions/src/powertools_base64_jmespath_schema.py create mode 100644 examples/jmespath_functions/src/powertools_custom_jmespath_function.json create mode 100644 examples/jmespath_functions/src/powertools_custom_jmespath_function.py create mode 100644 examples/jmespath_functions/src/powertools_json_idempotency_jmespath.json create mode 100644 examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py create mode 100644 examples/jmespath_functions/src/powertools_json_jmespath_function.py create mode 100644 examples/jmespath_functions/src/powertools_json_jmespath_payload.json create mode 100644 examples/jmespath_functions/src/powertools_json_jmespath_schema.py diff --git a/docs/utilities/jmespath_functions.md b/docs/utilities/jmespath_functions.md index eee88c13cfb..45250ea0fcd 100644 --- a/docs/utilities/jmespath_functions.md +++ b/docs/utilities/jmespath_functions.md @@ -3,6 +3,8 @@ title: JMESPath Functions description: Utility --- + + ???+ tip JMESPath is a query language for JSON used by AWS CLI, AWS Python SDK, and AWS Lambda Powertools for Python. @@ -12,221 +14,164 @@ Built-in [JMESPath](https://jmespath.org/){target="_blank"} Functions to easily * Deserialize JSON from JSON strings, base64, and compressed data * Use JMESPath to extract and combine data recursively +* Provides commonly used JMESPath expression with popular event sources ## Getting started +???+ tip + All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}. + You might have events that contains encoded JSON payloads as string, base64, or even in compressed format. It is a common use case to decode and extract them partially or fully as part of your Lambda function invocation. -Lambda Powertools also have utilities like [validation](validation.md), [idempotency](idempotency.md), or [feature flags](feature_flags.md) where you might need to extract a portion of your data before using them. +Powertools also have utilities like [validation](validation.md), [idempotency](idempotency.md), or [feature flags](feature_flags.md) where you might need to extract a portion of your data before using them. -???+ info - **Envelope** is the terminology we use for the JMESPath expression to extract your JSON object from your data input. +???+ info "Terminology" + **Envelope** is the terminology we use for the **JMESPath expression** to extract your JSON object from your data input. We might use those two terms interchangeably. ### Extracting data -You can use the `extract_data_from_envelope` function along with any [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank"}. - -=== "app.py" - - ```python hl_lines="1 7" - from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope - - from aws_lambda_powertools.utilities.typing import LambdaContext +You can use the `extract_data_from_envelope` function with any [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank"}. +???+ tip + Another common use case is to fetch deeply nested data, filter, flatten, and more. - def handler(event: dict, context: LambdaContext): - payload = extract_data_from_envelope(data=event, envelope="powertools_json(body)") - customer = payload.get("customerId") # now deserialized - ... - ``` +=== "extract_data_from_envelope.py" + ```python hl_lines="1 6 10" + --8<-- "examples/jmespath_functions/src/extract_data_from_envelope.py" + ``` -=== "event.json" +=== "extract_data_from_envelope.json" ```json - { - "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}" - } + --8<-- "examples/jmespath_functions/src/extract_data_from_envelope.json" ``` ### Built-in envelopes -We provide built-in envelopes for popular JMESPath expressions used when looking to decode/deserialize JSON objects within AWS Lambda Event Sources. +We provide built-in envelopes for popular AWS Lambda event sources to easily decode and/or deserialize JSON objects. -=== "app.py" +=== "extract_data_from_builtin_envelope.py" - ```python hl_lines="1 7" - from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope, envelopes - - from aws_lambda_powertools.utilities.typing import LambdaContext - - - def handler(event: dict, context: LambdaContext): - payload = extract_data_from_envelope(data=event, envelope=envelopes.SNS) - customer = payload.get("customerId") # now deserialized - ... + ```python hl_lines="1 6" + --8<-- "examples/jmespath_functions/src/extract_data_from_builtin_envelope.py" ``` -=== "event.json" - - ```json hl_lines="6" - { - "Records": [ - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\",\"booking\":{\"id\":\"5b2c4803-330b-42b7-811a-c68689425de1\",\"reference\":\"ySz7oA\",\"outboundFlightId\":\"20c0d2f2-56a3-4068-bf20-ff7703db552d\"},\"payment\":{\"receipt\":\"https:\/\/pay.stripe.com\/receipts\/acct_1Dvn7pF4aIiftV70\/ch_3JTC14F4aIiftV700iFq2CHB\/rcpt_K7QsrFln9FgFnzUuBIiNdkkRYGxUL0X\",\"amount\":100}}", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "7b270e59b47ff90a553787216d55d91d", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" - } - ] - } +=== "extract_data_from_builtin_envelope.json" + + ```json hl_lines="6 15" + --8<-- "examples/jmespath_functions/src/extract_data_from_builtin_envelope.json" ``` These are all built-in envelopes you can use along with their expression as a reference: -Envelope | JMESPath expression -------------------------------------------------- | --------------------------------------------------------------------------------- -**`API_GATEWAY_REST`** | `powertools_json(body)` -**`API_GATEWAY_HTTP`** | `API_GATEWAY_REST` -**`SQS`** | `Records[*].powertools_json(body)` -**`SNS`** | `Records[0].Sns.Message | powertools_json(@)` -**`EVENTBRIDGE`** | `detail` -**`CLOUDWATCH_EVENTS_SCHEDULED`** | `EVENTBRIDGE` -**`KINESIS_DATA_STREAM`** | `Records[*].kinesis.powertools_json(powertools_base64(data))` -**`CLOUDWATCH_LOGS`** | `awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]` +| Envelope | JMESPath expression | +| --------------------------------- | ------------------------------------------------------------- | +| **`API_GATEWAY_REST`** | `powertools_json(body)` | +| **`API_GATEWAY_HTTP`** | `API_GATEWAY_REST` | +| **`SQS`** | `Records[*].powertools_json(body)` | +| **`SNS`** | `Records[0].Sns.Message | powertools_json(@)` | +| **`EVENTBRIDGE`** | `detail` | +| **`CLOUDWATCH_EVENTS_SCHEDULED`** | `EVENTBRIDGE` | +| **`KINESIS_DATA_STREAM`** | `Records[*].kinesis.powertools_json(powertools_base64(data))` | +| **`CLOUDWATCH_LOGS`** | `awslogs.powertools_base64_gzip(data) | powertools_json(@).logEvents[*]` | ## Advanced ### Built-in JMESPath functions -You can use our built-in JMESPath functions within your expressions to do exactly that to decode JSON Strings, base64, and uncompress gzip data. +You can use our built-in JMESPath functions within your envelope expression. They handle deserialization for common data formats found in AWS Lambda event sources such as JSON strings, base64, and uncompress gzip data. ???+ info - We use these for built-in envelopes to easily decode and unwrap events from sources like API Gateway, Kinesis, CloudWatch Logs, etc. + We use these everywhere in Powertools to easily decode and unwrap events from Amazon API Gateway, Amazon Kinesis, AWS CloudWatch Logs, etc. #### powertools_json function -Use `powertools_json` function to decode any JSON String anywhere a JMESPath expression is allowed. +Use `powertools_json` function to decode any JSON string anywhere a JMESPath expression is allowed. > **Validation scenario** -This sample will decode the value within the `data` key into a valid JSON before we can validate it. +This sample will deserialize the JSON string within the `data` key before validation. === "powertools_json_jmespath_function.py" - ```python hl_lines="9" - from aws_lambda_powertools.utilities.validation import validate - - import schemas + ```python hl_lines="5 8 34 45 48 51" + --8<-- "examples/jmespath_functions/src/powertools_json_jmespath_function.py" + ``` - sample_event = { - 'data': '{"payload": {"message": "hello hello", "username": "blah blah"}}' - } +=== "powertools_json_jmespath_schema.py" - validate(event=sample_event, schema=schemas.INPUT, envelope="powertools_json(data)") + ```python hl_lines="7 8 10 12 17 19 24 26 31 33 38 40" + --8<-- "examples/jmespath_functions/src/powertools_json_jmespath_schema.py" ``` -=== "schemas.py" +=== "powertools_json_jmespath_payload.json" - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json + --8<-- "examples/jmespath_functions/src/powertools_json_jmespath_payload.json" ``` > **Idempotency scenario** -This sample will decode the value within the `body` key of an API Gateway event into a valid JSON object to ensure the Idempotency utility processes a JSON object instead of a string. - -```python hl_lines="7" title="Deserializing JSON before using as idempotency key" -import json -from aws_lambda_powertools.utilities.idempotency import ( - IdempotencyConfig, DynamoDBPersistenceLayer, idempotent -) - -persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable") -config = IdempotencyConfig(event_key_jmespath="powertools_json(body)") - -@idempotent(config=config, persistence_store=persistence_layer) -def handler(event:APIGatewayProxyEvent, context): - body = json.loads(event['body']) - payment = create_subscription_payment( - user=body['user'], - product=body['product_id'] - ) - ... - return { - "payment_id": payment.id, - "message": "success", - "statusCode": 200 - } -``` +This sample will deserialize the JSON string within the `body` key before [Idempotency](./idempotency.md){target="_blank"} processes it. + +=== "powertools_json_idempotency_jmespath.py" + + ```python hl_lines="12" + --8<-- "examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py" + ``` + +=== "powertools_json_idempotency_jmespath.json" + + ```json hl_lines="28" + --8<-- "examples/jmespath_functions/src/powertools_json_idempotency_jmespath.json" + ``` #### powertools_base64 function Use `powertools_base64` function to decode any base64 data. -This sample will decode the base64 value within the `data` key, and decode the JSON string into a valid JSON before we can validate it. +This sample will decode the base64 value within the `data` key, and deserialize the JSON string before validation. -=== "powertools_json_jmespath_function.py" - - ```python hl_lines="12" - from aws_lambda_powertools.utilities.validation import validate +=== "powertools_base64_jmespath_function.py" - import schemas + ```python hl_lines="7 10 37 48 52 54 56" + --8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_function.py" + ``` - sample_event = { - "data": "eyJtZXNzYWdlIjogImhlbGxvIGhlbGxvIiwgInVzZXJuYW1lIjogImJsYWggYmxhaCJ9=" - } +=== "powertools_base64_jmespath_schema.py" - validate( - event=sample_event, - schema=schemas.INPUT, - envelope="powertools_json(powertools_base64(data))" - ) + ```python hl_lines="7 8 10 12 17 19 24 26 31 33 38 40" + --8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_schema.py" ``` -=== "schemas.py" +=== "powertools_base64_jmespath_payload.json" - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json + --8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_payload.json" ``` #### powertools_base64_gzip function Use `powertools_base64_gzip` function to decompress and decode base64 data. -This sample will decompress and decode base64 data, then use JMESPath pipeline expression to pass the result for decoding its JSON string. +This sample will decompress and decode base64 data from Cloudwatch Logs, then use JMESPath pipeline expression to pass the result for decoding its JSON string. -=== "powertools_json_jmespath_function.py" - - ```python hl_lines="12" - from aws_lambda_powertools.utilities.validation import validate +=== "powertools_base64_gzip_jmespath_function.py" - import schemas + ```python hl_lines="6 10 15 29 31 33 35" + --8<-- "examples/jmespath_functions/src/powertools_base64_gzip_jmespath_function.py" + ``` - sample_event = { - "data": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA==" - } +=== "powertools_base64_gzip_jmespath_schema.py" - validate( - event=sample_event, - schema=schemas.INPUT, - envelope="powertools_base64_gzip(data) | powertools_json(@)" - ) + ```python hl_lines="7-15 17 19 24 26 31 33 38 40" + --8<-- "examples/jmespath_functions/src/powertools_base64_gzip_jmespath_schema.py" ``` -=== "schemas.py" +=== "powertools_base64_gzip_jmespath_payload.json" - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json + --8<-- "examples/jmespath_functions/src/powertools_base64_gzip_jmespath_payload.json" ``` ### Bring your own JMESPath function @@ -234,35 +179,18 @@ This sample will decompress and decode base64 data, then use JMESPath pipeline e ???+ warning This should only be used for advanced use cases where you have special formats not covered by the built-in functions. -For special binary formats that you want to decode before applying JSON Schema validation, you can bring your own [JMESPath function](https://github.com/jmespath/jmespath.py#custom-functions){target="_blank"} and any additional option via `jmespath_options` param. - -In order to keep the built-in functions from Powertools, you can subclass from `PowertoolsFunctions`: - -=== "custom_jmespath_function.py" - - ```python hl_lines="2-3 6-9 11 17" - from aws_lambda_powertools.utilities.jmespath_utils import ( - PowertoolsFunctions, extract_data_from_envelope) - from jmespath.functions import signature - +For special binary formats that you want to decode before applying JSON Schema validation, you can bring your own [JMESPath function](https://github.com/jmespath/jmespath.py#custom-functions){target="_blank"} and any additional option via `jmespath_options` param. To keep Powertools built-in functions, you can subclass from `PowertoolsFunctions`. - class CustomFunctions(PowertoolsFunctions): - @signature({'types': ['string']}) # Only decode if value is a string - def _func_special_decoder(self, s): - return my_custom_decoder_logic(s) +Here is an example of how to decompress messages using [snappy](https://github.com/andrix/python-snappy){target="_blank"}: - custom_jmespath_options = {"custom_functions": CustomFunctions()} +=== "powertools_custom_jmespath_function.py" - def handler(event, context): - # use the custom name after `_func_` - extract_data_from_envelope(data=event, - envelope="special_decoder(body)", - jmespath_options=**custom_jmespath_options) - ... + ```python hl_lines="8 11 14-15 20 31 36 38 40" + --8<-- "examples/jmespath_functions/src/powertools_custom_jmespath_function.py" ``` -=== "event.json" +=== "powertools_custom_jmespath_function.json" ```json - {"body": "custom_encoded_data"} + --8<-- "examples/jmespath_functions/src/powertools_custom_jmespath_function.json" ``` diff --git a/examples/jmespath_functions/src/extract_data_from_builtin_envelope.json b/examples/jmespath_functions/src/extract_data_from_builtin_envelope.json new file mode 100644 index 00000000000..6fe1d1655ab --- /dev/null +++ b/examples/jmespath_functions/src/extract_data_from_builtin_envelope.json @@ -0,0 +1,20 @@ +{ + "Records": [ + { + "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", + "receiptHandle": "MessageReceiptHandle", + "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\",\"booking\":{\"id\":\"5b2c4803-330b-42b7-811a-c68689425de1\",\"reference\":\"ySz7oA\",\"outboundFlightId\":\"20c0d2f2-56a3-4068-bf20-ff7703db552d\"},\"payment\":{\"receipt\":\"https:\/\/pay.stripe.com\/receipts\/acct_1Dvn7pF4aIiftV70\/ch_3JTC14F4aIiftV700iFq2CHB\/rcpt_K7QsrFln9FgFnzUuBIiNdkkRYGxUL0X\",\"amount\":100}}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1523232000000", + "SenderId": "123456789012", + "ApproximateFirstReceiveTimestamp": "1523232000001" + }, + "messageAttributes": {}, + "md5OfBody": "7b270e59b47ff90a553787216d55d91d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", + "awsRegion": "us-east-1" + } + ] +} diff --git a/examples/jmespath_functions/src/extract_data_from_builtin_envelope.py b/examples/jmespath_functions/src/extract_data_from_builtin_envelope.py new file mode 100644 index 00000000000..53c230e1b9b --- /dev/null +++ b/examples/jmespath_functions/src/extract_data_from_builtin_envelope.py @@ -0,0 +1,9 @@ +from aws_lambda_powertools.utilities.jmespath_utils import envelopes, extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext + + +def handler(event: dict, context: LambdaContext) -> dict: + payload = extract_data_from_envelope(data=event, envelope=envelopes.SQS) + customer_id = payload.get("customerId") # now deserialized + + return {"customer_id": customer_id, "message": "success", "statusCode": 200} diff --git a/examples/jmespath_functions/src/extract_data_from_envelope.json b/examples/jmespath_functions/src/extract_data_from_envelope.json new file mode 100644 index 00000000000..0a0f0763279 --- /dev/null +++ b/examples/jmespath_functions/src/extract_data_from_envelope.json @@ -0,0 +1,12 @@ +{ + "body": "{\"customerId\":\"dd4649e6-2484-4993-acb8-0f9123103394\"}", + "deeply_nested": [ + { + "some_data": [ + 1, + 2, + 3 + ] + } + ] +} \ No newline at end of file diff --git a/examples/jmespath_functions/src/extract_data_from_envelope.py b/examples/jmespath_functions/src/extract_data_from_envelope.py new file mode 100644 index 00000000000..5c35bc4348b --- /dev/null +++ b/examples/jmespath_functions/src/extract_data_from_envelope.py @@ -0,0 +1,12 @@ +from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext + + +def handler(event: dict, context: LambdaContext) -> dict: + payload = extract_data_from_envelope(data=event, envelope="powertools_json(body)") + customer_id = payload.get("customerId") # now deserialized + + # also works for fetching and flattening deeply nested data + some_data = extract_data_from_envelope(data=event, envelope="deeply_nested[*].some_data[]") + + return {"customer_id": customer_id, "message": "success", "context": some_data, "statusCode": 200} diff --git a/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_function.py b/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_function.py new file mode 100644 index 00000000000..cff3424b487 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_function.py @@ -0,0 +1,41 @@ +import base64 +import binascii +import gzip +import json + +import powertools_base64_gzip_jmespath_schema as schemas +from jmespath.exceptions import JMESPathTypeError + +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + + +def lambda_handler(event, context: LambdaContext) -> dict: + try: + validate(event=event, schema=schemas.INPUT, envelope="powertools_base64_gzip(payload) | powertools_json(@)") + + # Alternatively, extract_data_from_envelope works here too + encoded_payload = base64.b64decode(event["payload"]) + uncompressed_payload = gzip.decompress(encoded_payload).decode() + log: dict = json.loads(uncompressed_payload) + + return { + "message": "Logs processed", + "log_group": log.get("logGroup"), + "owner": log.get("owner"), + "success": True, + } + + except JMESPathTypeError: + return return_error_message("The powertools_base64_gzip() envelope function must match a valid path.") + except binascii.Error: + return return_error_message("Payload must be a valid base64 encoded string") + except json.JSONDecodeError: + return return_error_message("Payload must be valid JSON (base64 encoded).") + except SchemaValidationError as exception: + # SchemaValidationError indicates where a data mismatch is + return return_error_message(str(exception)) + + +def return_error_message(message: str) -> dict: + return {"message": message, "success": False} diff --git a/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_payload.json b/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_payload.json new file mode 100644 index 00000000000..13995523099 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_payload.json @@ -0,0 +1,3 @@ +{ + "payload": "H4sIACZAXl8C/52PzUrEMBhFX2UILpX8tPbHXWHqIOiq3Q1F0ubrWEiakqTWofTdTYYB0YWL2d5zvnuTFellBIOedoiyKH5M0iwnlKH7HZL6dDB6ngLDfLFYctUKjie9gHFaS/sAX1xNEq525QxwFXRGGMEkx4Th491rUZdV3YiIZ6Ljfd+lfSyAtZloacQgAkqSJCGhxM6t7cwwuUGPz4N0YKyvO6I9WDeMPMSo8Z4Ca/kJ6vMEYW5f1MX7W1lVxaG8vqX8hNFdjlc0iCBBSF4ERT/3Pl7RbMGMXF2KZMh/C+gDpNS7RRsp0OaRGzx0/t8e0jgmcczyLCWEePhni/23JWalzjdu0a3ZvgEaNLXeugEAAA==" +} diff --git a/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_schema.py b/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_schema.py new file mode 100644 index 00000000000..0ba02934928 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_base64_gzip_jmespath_schema.py @@ -0,0 +1,47 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [ + { + "owner": "123456789012", + "logGroup": "/aws/lambda/powertools-example", + "logStream": "2022/08/07/[$LATEST]d3a8dcaffc7f4de2b8db132e3e106660", + "logEvents": {}, + } + ], + "required": ["owner", "logGroup", "logStream", "logEvents"], + "properties": { + "owner": { + "$id": "#/properties/owner", + "type": "string", + "title": "The owner", + "examples": ["123456789012"], + "maxLength": 12, + }, + "logGroup": { + "$id": "#/properties/logGroup", + "type": "string", + "title": "The logGroup", + "examples": ["/aws/lambda/powertools-example"], + "maxLength": 100, + }, + "logStream": { + "$id": "#/properties/logStream", + "type": "string", + "title": "The logGroup", + "examples": ["2022/08/07/[$LATEST]d3a8dcaffc7f4de2b8db132e3e106660"], + "maxLength": 100, + }, + "logEvents": { + "$id": "#/properties/logEvents", + "type": "array", + "title": "The logEvents", + "examples": [ + "{'id': 'eventId1', 'message': {'username': 'lessa', 'message': 'hello world'}, 'timestamp': 1440442987000}" # noqa E501 + ], + }, + }, +} diff --git a/examples/jmespath_functions/src/powertools_base64_jmespath_function.py b/examples/jmespath_functions/src/powertools_base64_jmespath_function.py new file mode 100644 index 00000000000..ba7208298a0 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_base64_jmespath_function.py @@ -0,0 +1,63 @@ +import base64 +import binascii +import json +from dataclasses import asdict, dataclass, field, is_dataclass +from uuid import uuid4 + +import powertools_base64_jmespath_schema as schemas +from jmespath.exceptions import JMESPathTypeError + +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + + +@dataclass +class Order: + user_id: int + product_id: int + quantity: int + price: float + currency: str + order_id: str = field(default_factory=lambda: f"{uuid4()}") + + +class DataclassCustomEncoder(json.JSONEncoder): + """A custom JSON encoder to serialize dataclass obj""" + + def default(self, obj): + # Only called for values that aren't JSON serializable + # where `obj` will be an instance of Todo in this example + return asdict(obj) if is_dataclass(obj) else super().default(obj) + + +def lambda_handler(event, context: LambdaContext) -> dict: + + # Try to validate the schema + try: + validate(event=event, schema=schemas.INPUT, envelope="powertools_json(powertools_base64(payload))") + + # alternatively, extract_data_from_envelope works here too + payload_decoded = base64.b64decode(event["payload"]).decode() + + order_payload: dict = json.loads(payload_decoded) + + return { + "order": json.dumps(Order(**order_payload), cls=DataclassCustomEncoder), + "message": "order created", + "success": True, + } + except JMESPathTypeError: + return return_error_message( + "The powertools_json(powertools_base64()) envelope function must match a valid path." + ) + except binascii.Error: + return return_error_message("Payload must be a valid base64 encoded string") + except json.JSONDecodeError: + return return_error_message("Payload must be valid JSON (base64 encoded).") + except SchemaValidationError as exception: + # SchemaValidationError indicates where a data mismatch is + return return_error_message(str(exception)) + + +def return_error_message(message: str) -> dict: + return {"order": None, "message": message, "success": False} diff --git a/examples/jmespath_functions/src/powertools_base64_jmespath_payload.json b/examples/jmespath_functions/src/powertools_base64_jmespath_payload.json new file mode 100644 index 00000000000..b4ea41d1d09 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_base64_jmespath_payload.json @@ -0,0 +1,3 @@ +{ + "payload":"eyJ1c2VyX2lkIjogMTIzLCAicHJvZHVjdF9pZCI6IDEsICJxdWFudGl0eSI6IDIsICJwcmljZSI6IDEwLjQwLCAiY3VycmVuY3kiOiAiVVNEIn0=" +} diff --git a/examples/jmespath_functions/src/powertools_base64_jmespath_schema.py b/examples/jmespath_functions/src/powertools_base64_jmespath_schema.py new file mode 100644 index 00000000000..bd643a11c13 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_base64_jmespath_schema.py @@ -0,0 +1,46 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample order schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"user_id": 123, "product_id": 1, "quantity": 2, "price": 10.40, "currency": "USD"}], + "required": ["user_id", "product_id", "quantity", "price", "currency"], + "properties": { + "user_id": { + "$id": "#/properties/user_id", + "type": "integer", + "title": "The unique identifier of the user", + "examples": [123], + "maxLength": 10, + }, + "product_id": { + "$id": "#/properties/product_id", + "type": "integer", + "title": "The unique identifier of the product", + "examples": [1], + "maxLength": 10, + }, + "quantity": { + "$id": "#/properties/quantity", + "type": "integer", + "title": "The quantity of the product", + "examples": [2], + "maxLength": 10, + }, + "price": { + "$id": "#/properties/price", + "type": "number", + "title": "The individual price of the product", + "examples": [10.40], + "maxLength": 10, + }, + "currency": { + "$id": "#/properties/currency", + "type": "string", + "title": "The currency", + "examples": ["The currency of the order"], + "maxLength": 100, + }, + }, +} diff --git a/examples/jmespath_functions/src/powertools_custom_jmespath_function.json b/examples/jmespath_functions/src/powertools_custom_jmespath_function.json new file mode 100644 index 00000000000..fa0c44a3060 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_custom_jmespath_function.json @@ -0,0 +1,14 @@ +{ + "Records": [ + { + "user": "integration-kafka", + "datetime": "2022-01-01T00:00:00.000Z", + "log": "/QGIMjAyMi8wNi8xNiAxNjoyNTowMCBbY3JpdF0gMzA1MTg5MCMNCPBEOiAqMSBjb25uZWN0KCkg\ndG8gMTI3LjAuMC4xOjUwMDAgZmFpbGVkICgxMzogUGVybWlzc2lvbiBkZW5pZWQpIHdoaWxlEUEI\naW5nAUJAdXBzdHJlYW0sIGNsaWVudDoZVKgsIHNlcnZlcjogXywgcmVxdWVzdDogIk9QVElPTlMg\nLyBIVFRQLzEuMSIsFUckOiAiaHR0cDovLzabABQvIiwgaG8FQDAxMjcuMC4wLjE6ODEi\n" + }, + { + "user": "integration-kafka", + "datetime": "2022-01-01T00:00:01.000Z", + "log": "tQHwnDEyNy4wLjAuMSAtIC0gWzE2L0p1bi8yMDIyOjE2OjMwOjE5ICswMTAwXSAiT1BUSU9OUyAv\nIEhUVFAvMS4xIiAyMDQgMCAiLSIgIk1vemlsbGEvNS4wIChYMTE7IExpbnV4IHg4Nl82NCkgQXBw\nbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEwMi4BmUwwIFNhZmFy\naS81MzcuMzYiICItIg==\n" + } + ] +} diff --git a/examples/jmespath_functions/src/powertools_custom_jmespath_function.py b/examples/jmespath_functions/src/powertools_custom_jmespath_function.py new file mode 100644 index 00000000000..71fdecd0db2 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_custom_jmespath_function.py @@ -0,0 +1,45 @@ +import base64 +import binascii + +import snappy +from jmespath.exceptions import JMESPathTypeError +from jmespath.functions import signature + +from aws_lambda_powertools.utilities.jmespath_utils import PowertoolsFunctions, extract_data_from_envelope + + +class CustomFunctions(PowertoolsFunctions): + # only decode if value is a string + # see supported data types: https://jmespath.org/specification.html#built-in-functions + @signature({"types": ["string"]}) + def _func_decode_snappy_compression(self, payload: str): + decoded: bytes = base64.b64decode(payload) + return snappy.uncompress(decoded) + + +custom_jmespath_options = {"custom_functions": CustomFunctions()} + + +def lambda_handler(event, context) -> dict: + + try: + logs = [] + logs.append( + extract_data_from_envelope( + data=event, + # NOTE: Use the prefix `_func_` before the name of the function + envelope="Records[*].decode_snappy_compression(log)", + jmespath_options=custom_jmespath_options, + ) + ) + return {"logs": logs, "message": "Extracted messages", "success": True} + except JMESPathTypeError: + return return_error_message("The envelope function must match a valid path.") + except snappy.UncompressError: + return return_error_message("Log must be a valid snappy compressed binary") + except binascii.Error: + return return_error_message("Log must be a valid base64 encoded string") + + +def return_error_message(message: str) -> dict: + return {"logs": None, "message": message, "success": False} diff --git a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.json b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.json new file mode 100644 index 00000000000..31d61c31839 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.json @@ -0,0 +1,30 @@ +{ + "version":"2.0", + "routeKey":"ANY /createpayment", + "rawPath":"/createpayment", + "rawQueryString":"", + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "requestContext":{ + "accountId":"123456789012", + "apiId":"api-id", + "domainName":"id.execute-api.us-east-1.amazonaws.com", + "domainPrefix":"id", + "http":{ + "method":"POST", + "path":"/createpayment", + "protocol":"HTTP/1.1", + "sourceIp":"ip", + "userAgent":"agent" + }, + "requestId":"id", + "routeKey":"ANY /createpayment", + "stage":"$default", + "time":"10/Feb/2021:13:40:43 +0000", + "timeEpoch":1612964443723 + }, + "body":"{\"user\":\"xyz\",\"product_id\":\"123456789\"}", + "isBase64Encoded":false + } diff --git a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py new file mode 100644 index 00000000000..15880dedff3 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py @@ -0,0 +1,34 @@ +import json +from uuid import uuid4 + +import requests + +from aws_lambda_powertools.utilities.idempotency import DynamoDBPersistenceLayer, IdempotencyConfig, idempotent + +persistence_layer = DynamoDBPersistenceLayer(table_name="IdempotencyTable") + +# Treat everything under the "body" key +# in the event json object as our payload +config = IdempotencyConfig(event_key_jmespath="powertools_json(body)") + + +class PaymentError(Exception): + ... + + +@idempotent(config=config, persistence_store=persistence_layer) +def handler(event, context) -> dict: + body = json.loads(event["body"]) + try: + payment = create_subscription_payment(user=body["user"], product_id=body["product_id"]) + return {"payment_id": payment.id, "message": "success", "statusCode": 200} + except requests.HTTPError as e: + raise PaymentError("Unable to create payment subscription") from e + + +def create_subscription_payment(user: str, product_id: str) -> dict: + payload = {"user": user, "product_id": product_id} + ret: requests.Response = requests.post(url="https://httpbin.org/anything", data=payload) + ret.raise_for_status() + + return {"id": f"{uuid4()}", "message": "paid"} diff --git a/examples/jmespath_functions/src/powertools_json_jmespath_function.py b/examples/jmespath_functions/src/powertools_json_jmespath_function.py new file mode 100644 index 00000000000..5eae585c0c1 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_json_jmespath_function.py @@ -0,0 +1,56 @@ +import json +from dataclasses import asdict, dataclass, field, is_dataclass +from uuid import uuid4 + +import powertools_json_jmespath_schema as schemas +from jmespath.exceptions import JMESPathTypeError + +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + + +@dataclass +class Order: + user_id: int + product_id: int + quantity: int + price: float + currency: str + order_id: str = field(default_factory=lambda: f"{uuid4()}") + + +class DataclassCustomEncoder(json.JSONEncoder): + """A custom JSON encoder to serialize dataclass obj""" + + def default(self, obj): + # Only called for values that aren't JSON serializable + # where `obj` will be an instance of Order in this example + return asdict(obj) if is_dataclass(obj) else super().default(obj) + + +def lambda_handler(event, context: LambdaContext) -> dict: + try: + # Validate order against our schema + validate(event=event, schema=schemas.INPUT, envelope="powertools_json(payload)") + + # Deserialize JSON string order as dict + # alternatively, extract_data_from_envelope works here too + order_payload: dict = json.loads(event.get("payload")) + + return { + "order": json.dumps(Order(**order_payload), cls=DataclassCustomEncoder), + "message": "order created", + "success": True, + } + except JMESPathTypeError: + # The powertools_json() envelope function must match a valid path + return return_error_message("Invalid request.") + except SchemaValidationError as exception: + # SchemaValidationError indicates where a data mismatch is + return return_error_message(str(exception)) + except json.JSONDecodeError: + return return_error_message("Payload must be valid JSON (base64 encoded).") + + +def return_error_message(message: str) -> dict: + return {"order": None, "message": message, "success": False} diff --git a/examples/jmespath_functions/src/powertools_json_jmespath_payload.json b/examples/jmespath_functions/src/powertools_json_jmespath_payload.json new file mode 100644 index 00000000000..647583bba82 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_json_jmespath_payload.json @@ -0,0 +1,3 @@ +{ + "payload":"{\"user_id\": 123, \"product_id\": 1, \"quantity\": 2, \"price\": 10.40, \"currency\": \"USD\"}" +} diff --git a/examples/jmespath_functions/src/powertools_json_jmespath_schema.py b/examples/jmespath_functions/src/powertools_json_jmespath_schema.py new file mode 100644 index 00000000000..bd643a11c13 --- /dev/null +++ b/examples/jmespath_functions/src/powertools_json_jmespath_schema.py @@ -0,0 +1,46 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample order schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"user_id": 123, "product_id": 1, "quantity": 2, "price": 10.40, "currency": "USD"}], + "required": ["user_id", "product_id", "quantity", "price", "currency"], + "properties": { + "user_id": { + "$id": "#/properties/user_id", + "type": "integer", + "title": "The unique identifier of the user", + "examples": [123], + "maxLength": 10, + }, + "product_id": { + "$id": "#/properties/product_id", + "type": "integer", + "title": "The unique identifier of the product", + "examples": [1], + "maxLength": 10, + }, + "quantity": { + "$id": "#/properties/quantity", + "type": "integer", + "title": "The quantity of the product", + "examples": [2], + "maxLength": 10, + }, + "price": { + "$id": "#/properties/price", + "type": "number", + "title": "The individual price of the product", + "examples": [10.40], + "maxLength": 10, + }, + "currency": { + "$id": "#/properties/currency", + "type": "string", + "title": "The currency", + "examples": ["The currency of the order"], + "maxLength": 100, + }, + }, +} From ddc8668a775611ed048ab7e510bff0a421050da0 Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:08:00 +0000 Subject: [PATCH 18/59] update changelog with latest changes --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b85db5304d..c6f5fe65e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Bug Fixes +* **ci:** move from pip-tools to poetry on layers to fix conflicts * **ci:** typo and bust gh actions cache * **ci:** use poetry to resolve layer deps; pip for CDK * **ci:** disable poetry venv for layer workflow as cdk ignores venv @@ -17,10 +18,14 @@ ## Documentation +* **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) +* **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 ## Maintenance +* **ci:** reduce payload and only send prod notification +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes From a7abd87c2c06e621939d96f1deec92e0d906e593 Mon Sep 17 00:00:00 2001 From: Peter Schutt Date: Wed, 10 Aug 2022 00:09:15 +1000 Subject: [PATCH 19/59] docs(apigateway): removes duplicate admonition (#1426) --- docs/core/event_handler/api_gateway.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 1358f545eb8..f4f45a051f8 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -135,9 +135,6 @@ Each dynamic route you set must be part of your function signature. This allows ???+ tip You can also nest dynamic paths, for example `/todos//`. -???+ tip - You can also nest dynamic paths, for example `/todos//`. - #### Catch-all routes ???+ note From 60bc8f91da5a334f906b4aa4fc0c5da510d00757 Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:09:54 +0000 Subject: [PATCH 20/59] update changelog with latest changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f5fe65e71..9fc78d3dae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,12 +18,14 @@ ## Documentation +* **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) * **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) * **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 ## Maintenance +* **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes From 4a5ec4a8e1ad1c9a989fcce9e3f57bf5054cfa47 Mon Sep 17 00:00:00 2001 From: Peter Schutt Date: Wed, 10 Aug 2022 00:11:08 +1000 Subject: [PATCH 21/59] docs(parser): minor grammar fix (#1427) --- docs/utilities/parser.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index 8756725d1e0..cb69cf9699b 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -524,7 +524,7 @@ Parser is best suited for those looking for a trade-off between defining their m We export most common classes, exceptions, and utilities from Pydantic as part of parser e.g. `from aws_lambda_powertools.utilities.parser import BaseModel`. -If what's your trying to use isn't available as part of the high level import system, use the following escape hatch mechanism: +If what you're trying to use isn't available as part of the high level import system, use the following escape hatch mechanism: ```python title="Pydantic import escape hatch" from aws_lambda_powertools.utilities.parser.pydantic import From 19f5ae5c9eda9569eb4a1ac9fe3a3fce9d7f2127 Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:11:42 +0000 Subject: [PATCH 22/59] update changelog with latest changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fc78d3dae1..9a738104460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,9 +22,11 @@ * **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) * **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 +* **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) ## Maintenance +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes From 3602287ace29208a81add1f4b8f5cc580b6b7eaa Mon Sep 17 00:00:00 2001 From: Release bot Date: Tue, 9 Aug 2022 14:59:06 +0000 Subject: [PATCH 23/59] update changelog with latest changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a738104460..27d160ec75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ## Maintenance +* **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification From b82a9212cb1acc8d1b7e2208a9e723f52312da79 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 11 Aug 2022 19:29:23 +0100 Subject: [PATCH 24/59] fix(jmespath_util): snappy as dev dep and typing example (#1446) --- .../data_classes/lambda_function_url_event.py | 2 +- .../powertools_json_idempotency_jmespath.py | 4 +- mypy.ini | 5 +- poetry.lock | 64 ++++++++++++++++++- pyproject.toml | 1 + 5 files changed, 69 insertions(+), 7 deletions(-) diff --git a/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py b/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py index 2b88918f17b..01c1a83f5db 100644 --- a/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py +++ b/aws_lambda_powertools/utilities/data_classes/lambda_function_url_event.py @@ -7,7 +7,7 @@ class LambdaFunctionUrlEvent(APIGatewayProxyEventV2): Notes: ----- Lambda Function URL follows the API Gateway HTTP APIs Payload Format Version 2.0. - + Keys related to API Gateway features not available in Function URL use a sentinel value (e.g.`routeKey`, `stage`). Documentation: diff --git a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py index 15880dedff3..aaf5724b54b 100644 --- a/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py +++ b/examples/jmespath_functions/src/powertools_json_idempotency_jmespath.py @@ -20,8 +20,8 @@ class PaymentError(Exception): def handler(event, context) -> dict: body = json.loads(event["body"]) try: - payment = create_subscription_payment(user=body["user"], product_id=body["product_id"]) - return {"payment_id": payment.id, "message": "success", "statusCode": 200} + payment: dict = create_subscription_payment(user=body["user"], product_id=body["product_id"]) + return {"payment_id": payment.get("id"), "message": "success", "statusCode": 200} except requests.HTTPError as e: raise PaymentError("Unable to create payment subscription") from e diff --git a/mypy.ini b/mypy.ini index 8274442fe4b..4da15d3898a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -42,4 +42,7 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-aiohttp] -ignore_missing_imports = True \ No newline at end of file +ignore_missing_imports = True + +[mypy-snappy] +ignore_missing_imports = True diff --git a/poetry.lock b/poetry.lock index 96d9c4e560b..8719b1c9a07 100644 --- a/poetry.lock +++ b/poetry.lock @@ -645,8 +645,8 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -test = ["flake8 (>=3.0)", "coverage"] -dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] +dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] +test = ["coverage", "flake8 (>=3.0)"] [[package]] name = "mkdocs" @@ -1087,6 +1087,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "python-snappy" +version = "0.6.1" +description = "Python library for the snappy compression library from Google" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "pyyaml" version = "5.4.1" @@ -1328,7 +1336,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "575cd7f9ff3a989898ec6f9944aab56b4e08964a37173d49b34e1e1bbc6a3d39" +content-hash = "70c0562fda81dd5aa851acd9176d62387d696e100456403e2f165db542ca8833" [metadata.files] atomicwrites = [ @@ -1846,6 +1854,56 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +python-snappy = [ + {file = "python-snappy-0.6.1.tar.gz", hash = "sha256:b6a107ab06206acc5359d4c5632bd9b22d448702a79b3169b0c62e0fb808bb2a"}, + {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b7f920eaf46ebf41bd26f9df51c160d40f9e00b7b48471c3438cb8d027f7fb9b"}, + {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ec533a8c1f8df797bded662ec3e494d225b37855bb63eb0d75464a07947477c"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6f8bf4708a11b47517baf962f9a02196478bbb10fdb9582add4aa1459fa82380"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d0c019ee7dcf2c60e240877107cddbd95a5b1081787579bf179938392d66480"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb18d9cd7b3f35a2f5af47bb8ed6a5bdbf4f3ddee37f3daade4ab7864c292f5b"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b265cde49774752aec9ca7f5d272e3f98718164afc85521622a8a5394158a2b5"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d017775851a778ec9cc32651c4464079d06d927303c2dde9ae9830ccf6fe94e1"}, + {file = "python_snappy-0.6.1-cp310-cp310-win32.whl", hash = "sha256:8277d1f6282463c40761f802b742f833f9f2449fcdbb20a96579aa05c8feb614"}, + {file = "python_snappy-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2aaaf618c68d8c9daebc23a20436bd01b09ee70d7fbf7072b7f38b06d2fab539"}, + {file = "python_snappy-0.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:277757d5dad4e239dc1417438a0871b65b1b155beb108888e7438c27ffc6a8cc"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e066a0586833d610c4bbddba0be5ba0e3e4f8e0bc5bb6d82103d8f8fc47bb59a"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0d489b50f49433494160c45048fe806de6b3aeab0586e497ebd22a0bab56e427"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463fd340a499d47b26ca42d2f36a639188738f6e2098c6dbf80aef0e60f461e1"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9837ac1650cc68d22a3cf5f15fb62c6964747d16cecc8b22431f113d6e39555d"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e973e637112391f05581f427659c05b30b6843bc522a65be35ac7b18ce3dedd"}, + {file = "python_snappy-0.6.1-cp36-cp36m-win32.whl", hash = "sha256:c20498bd712b6e31a4402e1d027a1cd64f6a4a0066a3fe3c7344475886d07fdf"}, + {file = "python_snappy-0.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59e975be4206cc54d0a112ef72fa3970a57c2b1bcc2c97ed41d6df0ebe518228"}, + {file = "python_snappy-0.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2a7e528ab6e09c0d67dcb61a1730a292683e5ff9bb088950638d3170cf2a0a54"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39692bedbe0b717001a99915ac0eb2d9d0bad546440d392a2042b96d813eede1"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a7620404da966f637b9ce8d4d3d543d363223f7a12452a575189c5355fc2d25"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7778c224efc38a40d274da4eb82a04cac27aae20012372a7db3c4bbd8926c4d4"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d029f7051ec1bbeaa3e03030b6d8ed47ceb69cae9016f493c802a08af54e026"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ad38bc98d0b0497a0b0dbc29409bcabfcecff4511ed7063403c86de16927bc"}, + {file = "python_snappy-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:5a453c45178d7864c1bdd6bfe0ee3ed2883f63b9ba2c9bb967c6b586bf763f96"}, + {file = "python_snappy-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9f0c0d88b84259f93c3aa46398680646f2c23e43394779758d9f739c34e15295"}, + {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb05c28298803a74add08ba496879242ef159c75bc86a5406fac0ffc7dd021b"}, + {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9eac51307c6a1a38d5f86ebabc26a889fddf20cbba7a116ccb54ba1446601d5b"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:88b6ea78b83d2796f330b0af1b70cdd3965dbdab02d8ac293260ec2c8fe340ee"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c07220408d3268e8268c9351c5c08041bc6f8c6172e59d398b71020df108541"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4038019b1bcaadde726a57430718394076c5a21545ebc5badad2c045a09546cf"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc96668d9c7cc656609764275c5f8da58ef56d89bdd6810f6923d36497468ff7"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf5bb9254e1c38aacf253d510d3d9be631bba21f3d068b17672b38b5cbf2fff5"}, + {file = "python_snappy-0.6.1-cp38-cp38-win32.whl", hash = "sha256:eaf905a580f2747c4a474040a5063cd5e0cc3d1d2d6edb65f28196186493ad4a"}, + {file = "python_snappy-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:546c1a7470ecbf6239101e9aff0f709b68ca0f0268b34d9023019a55baa1f7c6"}, + {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3a013895c64352b49d0d8e107a84f99631b16dbab156ded33ebf0becf56c8b2"}, + {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fb9a88a4dd6336488f3de67ce75816d0d796dce53c2c6e4d70e0b565633c7fd"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:735cd4528c55dbe4516d6d2b403331a99fc304f8feded8ae887cf97b67d589bb"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:90b0186516b7a101c14764b0c25931b741fb0102f21253eff67847b4742dfc72"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a993dc8aadd901915a510fe6af5f20ae4256f527040066c22a154db8946751f"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:530bfb9efebcc1aab8bb4ebcbd92b54477eed11f6cf499355e882970a6d3aa7d"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5843feb914796b1f0405ccf31ea0fb51034ceb65a7588edfd5a8250cb369e3b2"}, + {file = "python_snappy-0.6.1-cp39-cp39-win32.whl", hash = "sha256:66c80e9b366012dbee262bb1869e4fc5ba8786cda85928481528bc4a72ec2ee8"}, + {file = "python_snappy-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d3cafdf454354a621c8ab7408e45aa4e9d5c0b943b61ff4815f71ca6bdf0130"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586724a0276d7a6083a17259d0b51622e492289a9998848a1b01b6441ca12b2f"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2be4f4550acd484912441f5f1209ba611ac399aac9355fee73611b9a0d4f949c"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdb6942180660bda7f7d01f4c0def3cfc72b1c6d99aad964801775a3e379aba"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:03bb511380fca2a13325b6f16fe8234c8e12da9660f0258cd45d9a02ffc916af"}, +] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, diff --git a/pyproject.toml b/pyproject.toml index 1b04b223f00..c85786c40db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,7 @@ mypy-boto3-lambda = "^1.24.0" mypy-boto3-xray = "^1.24.0" types-requests = "^2.28.7" typing-extensions = { version = "^4.3.0", python = ">=3.7" } +python-snappy = "^0.6.1" [tool.poetry.extras] pydantic = ["pydantic", "email-validator"] From 004954a26acf9ab3b8c40f48c2d5b0cbd0318f96 Mon Sep 17 00:00:00 2001 From: Release bot Date: Thu, 11 Aug 2022 18:35:56 +0000 Subject: [PATCH 25/59] update changelog with latest changes --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d160ec75f..ae964561674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,16 @@ ## Bug Fixes +* **ci:** move from pip-tools to poetry on layers reusable workflow * **ci:** move from pip-tools to poetry on layers to fix conflicts * **ci:** typo and bust gh actions cache * **ci:** use poetry to resolve layer deps; pip for CDK * **ci:** disable poetry venv for layer workflow as cdk ignores venv * **ci:** add cdk v2 dep for layers workflow -* **ci:** move from pip-tools to poetry on layers reusable workflow * **ci:** move from pip-tools to poetry on layers * **ci:** temporarily disable changelog upon release * **ci:** add explicit origin to fix release detached head +* **jmespath_util:** snappy as dev dep and typing example ([#1446](https://github.com/awslabs/aws-lambda-powertools-python/issues/1446)) ## Documentation @@ -29,6 +30,7 @@ * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes +* **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes From f69709094092f8511f0147b11f2b7466a56f0528 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Aug 2022 21:39:00 +0200 Subject: [PATCH 26/59] chore(deps-dev): bump types-requests from 2.28.7 to 2.28.8 (#1423) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 52 +++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8719b1c9a07..90450e5ef73 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,10 +15,10 @@ optional = false 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", "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"] +tests_no_zope = ["cloudpickle", "pytest-mypy-plugins", "mypy", "six", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +tests = ["cloudpickle", "zope.interface", "pytest-mypy-plugins", "mypy", "six", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +docs = ["sphinx-notfound-page", "zope.interface", "sphinx", "furo"] +dev = ["cloudpickle", "pre-commit", "sphinx-notfound-page", "sphinx", "furo", "zope.interface", "pytest-mypy-plugins", "mypy", "six", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] [[package]] name = "aws-cdk-lib" @@ -76,16 +76,16 @@ platformdirs = ">=2" tomli = ">=0.2.6,<2.0.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ - {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, ] [package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] +python2 = ["typed-ast (>=1.4.3)"] +jupyter = ["tokenize-rt (>=3.2.0)", "ipython (>=7.8.0)"] +d = ["aiohttp (>=3.7.4)"] +colorama = ["colorama (>=0.4.3)"] [[package]] name = "boto3" @@ -335,7 +335,7 @@ attrs = ">=19.2.0" flake8 = ">=3.0.0" [package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] +dev = ["pre-commit", "hypothesmith (>=0.2)", "hypothesis", "coverage"] [[package]] name = "flake8-builtins" @@ -486,8 +486,8 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["importlib-resources (>=1.3)", "pytest-mypy", "pytest-black (>=0.3.7)", "flufl.flake8", "pyfakefs", "pep517", "packaging", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=4.6)"] +docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] [[package]] name = "importlib-resources" @@ -645,8 +645,8 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] -test = ["coverage", "flake8 (>=3.0)"] +test = ["flake8 (>=3.0)", "coverage"] +dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] [[package]] name = "mkdocs" @@ -723,9 +723,9 @@ typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" [package.extras] -dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<2)"] reports = ["lxml"] +python2 = ["typed-ast (>=1.4.0,<2)"] +dmypy = ["psutil (>=4.0)"] [[package]] name = "mypy-boto3-appconfig" @@ -962,7 +962,7 @@ optional = false python-versions = ">=3.6" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" @@ -1056,7 +1056,7 @@ python-versions = ">=3.6" pytest = ">=5.0" [package.extras] -dev = ["pre-commit", "tox", "pytest-asyncio"] +dev = ["pytest-asyncio", "tox", "pre-commit"] [[package]] name = "pytest-xdist" @@ -1072,9 +1072,9 @@ pytest = ">=6.2.0" pytest-forked = "*" [package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] testing = ["filelock"] +setproctitle = ["setproctitle"] +psutil = ["psutil (>=3.0)"] [[package]] name = "python-dateutil" @@ -1248,7 +1248,7 @@ python-versions = "*" [[package]] name = "types-requests" -version = "2.28.7" +version = "2.28.8" description = "Typing stubs for requests" category = "dev" optional = false @@ -1327,8 +1327,8 @@ optional = false python-versions = ">=3.6" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.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"] +testing = ["pytest-mypy", "pytest-black (>=0.3.7)", "func-timeout", "jaraco.itertools", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=4.6)"] +docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] [extras] pydantic = ["pydantic", "email-validator"] @@ -1336,7 +1336,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "70c0562fda81dd5aa851acd9176d62387d696e100456403e2f165db542ca8833" +content-hash = "1ca9e5592ed8552f431e2984e71a2a3e7f52e4125efba36ea2d336bffcd3d437" [metadata.files] atomicwrites = [ @@ -2039,8 +2039,8 @@ typed-ast = [ {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] types-requests = [ - {file = "types-requests-2.28.7.tar.gz", hash = "sha256:36385618d4bd2ee3211d4d2e78b44f067ceb5984865c0f253f3c9ecb964526cf"}, - {file = "types_requests-2.28.7-py3-none-any.whl", hash = "sha256:38015d310d13cf7d4d712d2507178349e13fd5dab85259dab7d9a9884c2c9c2a"}, + {file = "types-requests-2.28.8.tar.gz", hash = "sha256:7a9f7b152d594a1c18dd4932cdd2596b8efbeedfd73caa4e4abb3755805b4685"}, + {file = "types_requests-2.28.8-py3-none-any.whl", hash = "sha256:b0421f9f2d0dd0f8df2c75f974686517ca67473f05b466232d4c6384d765ad7a"}, ] types-urllib3 = [ {file = "types-urllib3-1.26.17.tar.gz", hash = "sha256:73fd274524c3fc7cd8cd9ceb0cb67ed99b45f9cb2831013e46d50c1451044800"}, diff --git a/pyproject.toml b/pyproject.toml index c85786c40db..0a3a0babd51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,7 +66,7 @@ pytest-benchmark = "^3.4.1" mypy-boto3-cloudwatch = "^1.24.35" mypy-boto3-lambda = "^1.24.0" mypy-boto3-xray = "^1.24.0" -types-requests = "^2.28.7" +types-requests = "^2.28.8" typing-extensions = { version = "^4.3.0", python = ">=3.7" } python-snappy = "^0.6.1" From 3baaef939746849e0ed9088a8d2da2c06d4caa1c Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Fri, 12 Aug 2022 16:20:10 +0200 Subject: [PATCH 27/59] chore(tests): refactor E2E test mechanics to ease maintenance, writing tests and parallelization (#1444) --- poetry.lock | 556 ++++++++++---------- pyproject.toml | 3 +- tests/e2e/metrics/conftest.py | 21 + tests/e2e/metrics/handlers/basic_handler.py | 19 +- tests/e2e/metrics/handlers/cold_start.py | 12 + tests/e2e/metrics/infrastructure.py | 11 + tests/e2e/metrics/test_metrics.py | 83 ++- tests/e2e/utils/asset.py | 120 +++++ tests/e2e/utils/helpers.py | 191 ++++++- tests/e2e/utils/infrastructure.py | 243 ++++++++- tests/e2e/utils/models.py | 31 ++ 11 files changed, 933 insertions(+), 357 deletions(-) create mode 100644 tests/e2e/metrics/conftest.py create mode 100644 tests/e2e/metrics/handlers/cold_start.py create mode 100644 tests/e2e/metrics/infrastructure.py create mode 100644 tests/e2e/utils/asset.py create mode 100644 tests/e2e/utils/models.py diff --git a/poetry.lock b/poetry.lock index 90450e5ef73..16259c0b393 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "atomicwrites" -version = "1.4.0" +version = "1.4.1" description = "Atomic file writes." category = "dev" optional = false @@ -89,14 +89,14 @@ colorama = ["colorama (>=0.4.3)"] [[package]] name = "boto3" -version = "1.21.44" +version = "1.23.10" description = "The AWS SDK for Python" category = "main" optional = false python-versions = ">= 3.6" [package.dependencies] -botocore = ">=1.24.44,<1.25.0" +botocore = ">=1.26.10,<1.27.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.5.0,<0.6.0" @@ -105,7 +105,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.24.44" +version = "1.26.10" description = "Low-level, data-driven core of boto 3." category = "main" optional = false @@ -131,7 +131,7 @@ python-versions = "*" attrs = ">=17.3" [package.extras] -dev = ["pendulum", "hypothesis", "pytest", "sphinx", "coverage", "tox", "flake8", "watchdog", "wheel", "bumpversion"] +dev = ["bumpversion", "wheel", "watchdog", "flake8", "tox", "coverage", "sphinx", "pytest", "hypothesis", "pendulum"] [[package]] name = "cattrs" @@ -148,15 +148,15 @@ typing_extensions = {version = "*", markers = "python_version >= \"3.7\" and pyt [[package]] name = "certifi" -version = "2021.10.8" +version = "2022.6.15" description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "charset-normalizer" -version = "2.0.8" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false @@ -167,7 +167,7 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.3" +version = "8.0.4" description = "Composable command line interface toolkit" category = "dev" optional = false @@ -179,7 +179,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.5" description = "Cross-platform colored terminal text." category = "dev" optional = false @@ -229,18 +229,19 @@ python-versions = ">=3.5" [[package]] name = "dnspython" -version = "2.1.0" +version = "2.2.1" description = "DNS toolkit" category = "main" optional = true -python-versions = ">=3.6" +python-versions = ">=3.6,<4.0" [package.extras] -trio = ["sniffio (>=1.1)", "trio (>=0.14.0)"] -curio = ["sniffio (>=1.1)", "curio (>=1.2)"] -idna = ["idna (>=2.1)"] -doh = ["requests-toolbelt", "requests"] -dnssec = ["cryptography (>=2.6)"] +dnssec = ["cryptography (>=2.6,<37.0)"] +curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.20)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "email-validator" @@ -256,7 +257,7 @@ idna = ">=2.0.0" [[package]] name = "eradicate" -version = "2.0.0" +version = "2.1.0" description = "Removes commented-out code." category = "dev" optional = false @@ -309,19 +310,6 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" -[[package]] -name = "flake8-black" -version = "0.2.3" -description = "flake8 plugin to call black as a code style validator" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -black = "*" -flake8 = ">=3.0.0" -toml = "*" - [[package]] name = "flake8-bugbear" version = "22.7.1" @@ -399,14 +387,14 @@ python-versions = "*" [[package]] name = "flake8-isort" -version = "4.1.2.post0" +version = "4.2.0" description = "flake8 plugin that integrates isort ." category = "dev" optional = false python-versions = "*" [package.dependencies] -flake8 = ">=3.2.1,<5" +flake8 = ">=3.2.1,<6" isort = ">=4.3.5,<6" [package.extras] @@ -430,7 +418,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "ghp-import" -version = "2.0.2" +version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." category = "dev" optional = false @@ -440,7 +428,7 @@ python-versions = "*" python-dateutil = ">=2.8.1" [package.extras] -dev = ["wheel", "flake8", "markdown", "twine"] +dev = ["twine", "markdown", "flake8", "wheel"] [[package]] name = "gitdb" @@ -645,12 +633,12 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -test = ["flake8 (>=3.0)", "coverage"] -dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] +dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] +test = ["coverage", "flake8 (>=3.0)"] [[package]] name = "mkdocs" -version = "1.2.3" +version = "1.2.4" description = "Project documentation with Markdown." category = "dev" optional = false @@ -738,6 +726,17 @@ python-versions = ">=3.6" [package.dependencies] typing-extensions = ">=4.1.0" +[[package]] +name = "mypy-boto3-cloudformation" +version = "1.24.36.post1" +description = "Type annotations for boto3.CloudFormation 1.24.36 service generated with mypy-boto3-builder 7.10.0" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = ">=4.1.0" + [[package]] name = "mypy-boto3-cloudwatch" version = "1.24.35" @@ -771,6 +770,17 @@ python-versions = ">=3.6" [package.dependencies] typing-extensions = ">=4.1.0" +[[package]] +name = "mypy-boto3-s3" +version = "1.24.36.post1" +description = "Type annotations for boto3.S3 1.24.36 service generated with mypy-boto3-builder 7.10.0" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = ">=4.1.0" + [[package]] name = "mypy-boto3-secretsmanager" version = "1.24.11.post3" @@ -833,7 +843,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pbr" -version = "5.8.0" +version = "5.9.0" description = "Python Build Reasonableness" category = "dev" optional = false @@ -860,8 +870,8 @@ optional = false python-versions = ">=3.6" [package.extras] -test = ["pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "pytest (>=6)", "appdirs (==1.4.4)"] -docs = ["sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)", "Sphinx (>=4)"] +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" @@ -875,8 +885,8 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] -testing = ["pytest-benchmark", "pytest"] -dev = ["tox", "pre-commit"] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "publication" @@ -936,11 +946,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "pymdown-extensions" @@ -955,7 +965,7 @@ Markdown = ">=3.2" [[package]] name = "pyparsing" -version = "3.0.6" +version = "3.0.7" description = "Python parsing module" category = "dev" optional = false @@ -1030,7 +1040,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-forked" @@ -1097,11 +1107,11 @@ python-versions = "*" [[package]] name = "pyyaml" -version = "5.4.1" +version = "6.0" description = "YAML parser and emitter for Python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.6" [[package]] name = "pyyaml-env-tag" @@ -1129,7 +1139,7 @@ mando = ">=0.6,<0.7" [[package]] name = "requests" -version = "2.26.0" +version = "2.27.1" description = "Python HTTP for Humans." category = "dev" optional = false @@ -1159,14 +1169,14 @@ py = ">=1.4.26,<2.0.0" [[package]] name = "ruamel.yaml" -version = "0.17.17" +version = "0.17.21" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" category = "dev" optional = false python-versions = ">=3" [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.1.2", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.10\""} +"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""} [package.extras] docs = ["ryd"] @@ -1182,7 +1192,7 @@ python-versions = ">=3.5" [[package]] name = "s3transfer" -version = "0.5.0" +version = "0.5.2" description = "An Amazon S3 Transfer Manager" category = "main" optional = false @@ -1222,17 +1232,9 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - [[package]] name = "tomli" -version = "1.2.2" +version = "1.2.3" description = "A lil' TOML parser" category = "dev" optional = false @@ -1240,11 +1242,11 @@ python-versions = ">=3.6" [[package]] name = "typed-ast" -version = "1.4.3" +version = "1.5.4" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "types-requests" @@ -1259,7 +1261,7 @@ types-urllib3 = "<1.27" [[package]] name = "types-urllib3" -version = "1.26.17" +version = "1.26.22" description = "Typing stubs for urllib3" category = "dev" optional = false @@ -1275,20 +1277,20 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.11" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +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)"] [[package]] name = "watchdog" -version = "2.1.6" +version = "2.1.9" description = "Filesystem events monitoring" category = "dev" optional = false @@ -1299,7 +1301,7 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wrapt" -version = "1.13.3" +version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." category = "main" optional = false @@ -1340,8 +1342,7 @@ content-hash = "1ca9e5592ed8552f431e2984e71a2a3e7f52e4125efba36ea2d336bffcd3d437 [metadata.files] atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] attrs = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, @@ -1364,12 +1365,12 @@ black = [ {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, ] boto3 = [ - {file = "boto3-1.21.44-py3-none-any.whl", hash = "sha256:0789842ca7d722723d7e9fae2158aea6f304c14df08929f9c62b6a277705ff39"}, - {file = "boto3-1.21.44.tar.gz", hash = "sha256:1300661bd4defa42d7e019d515fbfd2984170cf3f5c0bf6bc275fbd9498faf5f"}, + {file = "boto3-1.23.10-py3-none-any.whl", hash = "sha256:40d08614f17a69075e175c02c5d5aab69a6153fd50e40fa7057b913ac7bf40e7"}, + {file = "boto3-1.23.10.tar.gz", hash = "sha256:2a4395e3241c20eef441d7443a5e6eaa0ee3f7114653fb9d9cef41587526f7bd"}, ] botocore = [ - {file = "botocore-1.24.44-py3-none-any.whl", hash = "sha256:ed07772c924984e5b3c1005f7ba4600cebd4169c23307cf6e92cccadf0b5d2e7"}, - {file = "botocore-1.24.44.tar.gz", hash = "sha256:0030a11eac972be46859263820885ba650503622c5acfe58966f482d42cc538d"}, + {file = "botocore-1.26.10-py3-none-any.whl", hash = "sha256:8a4a984bf901ccefe40037da11ba2abd1ddbcb3b490a492b7f218509c99fc12f"}, + {file = "botocore-1.26.10.tar.gz", hash = "sha256:5df2cf7ebe34377470172bd0bbc582cf98c5cbd02da0909a14e9e2885ab3ae9c"}, ] cattrs = [ {file = "cattrs-1.0.0-py2.py3-none-any.whl", hash = "sha256:616972ae3dfa6e623a40ad3cb845420e64942989152774ab055e5c2b2f89f997"}, @@ -1378,20 +1379,20 @@ cattrs = [ {file = "cattrs-22.1.0.tar.gz", hash = "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6"}, ] certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, + {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, + {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.8.tar.gz", hash = "sha256:735e240d9a8506778cd7a453d97e817e536bb1fc29f4f6961ce297b9c7a917b0"}, - {file = "charset_normalizer-2.0.8-py3-none-any.whl", hash = "sha256:83fcdeb225499d6344c8f7f34684c2981270beacc32ede2e669e94f7fa544405"}, + {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-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, ] colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, ] constructs = [ {file = "constructs-10.1.1-py3-none-any.whl", hash = "sha256:c1f3deb196f54e070ded3c92c4339f73ef2b6022d35fb34908c0ebfa7ef8a640"}, @@ -1455,15 +1456,16 @@ decorator = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] dnspython = [ - {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, - {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, ] email-validator = [ {file = "email_validator-1.2.1-py2.py3-none-any.whl", hash = "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c"}, {file = "email_validator-1.2.1.tar.gz", hash = "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8"}, ] eradicate = [ - {file = "eradicate-2.0.0.tar.gz", hash = "sha256:27434596f2c5314cc9b31410c93d8f7e8885747399773cd088d3adea647a60c8"}, + {file = "eradicate-2.1.0-py3-none-any.whl", hash = "sha256:8bfaca181db9227dc88bdbce4d051a9627604c2243e7d85324f6d6ce0fd08bb2"}, + {file = "eradicate-2.1.0.tar.gz", hash = "sha256:aac7384ab25b1bf21c4c012de9b4bf8398945a14c98c911545b2ea50ab558014"}, ] exceptiongroup = [ {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"}, @@ -1481,10 +1483,6 @@ flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] -flake8-black = [ - {file = "flake8-black-0.2.3.tar.gz", hash = "sha256:c199844bc1b559d91195ebe8620216f21ed67f2cc1ff6884294c91a0d2492684"}, - {file = "flake8_black-0.2.3-py3-none-any.whl", hash = "sha256:cc080ba5b3773b69ba102b6617a00cc4ecbad8914109690cfda4d565ea435d96"}, -] flake8-bugbear = [ {file = "flake8-bugbear-22.7.1.tar.gz", hash = "sha256:e450976a07e4f9d6c043d4f72b17ec1baf717fe37f7997009c8ae58064f88305"}, {file = "flake8_bugbear-22.7.1-py3-none-any.whl", hash = "sha256:db5d7a831ef4412a224b26c708967ff816818cabae415e76b8c58df156c4b8e5"}, @@ -1510,8 +1508,8 @@ flake8-fixme = [ {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"}, ] flake8-isort = [ - {file = "flake8-isort-4.1.2.post0.tar.gz", hash = "sha256:dee69bc3c09f0832df88acf795845db8a6673b79237371a05fa927ce095248e5"}, - {file = "flake8_isort-4.1.2.post0-py3-none-any.whl", hash = "sha256:4f95b40706dbb507cff872b34683283662e945d6028d3c8257e69de5fc6b7446"}, + {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, + {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, ] flake8-variables-names = [ {file = "flake8_variables_names-0.0.4.tar.gz", hash = "sha256:d6fa0571a807c72940b5773827c5760421ea6f8206595ff0a8ecfa01e42bf2cf"}, @@ -1520,8 +1518,8 @@ future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] ghp-import = [ - {file = "ghp-import-2.0.2.tar.gz", hash = "sha256:947b3771f11be850c852c64b561c600fdddf794bab363060854c1ee7ad05e071"}, - {file = "ghp_import-2.0.2-py3-none-any.whl", hash = "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46"}, + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, ] gitdb = [ {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, @@ -1576,28 +1574,12 @@ markdown = [ {file = "Markdown-3.3.5.tar.gz", hash = "sha256:26e9546bfbcde5fcd072bd8f612c9c1b6e2677cb8aadbdf65206674f46dde069"}, ] 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"}, @@ -1606,27 +1588,14 @@ markupsafe = [ {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"}, @@ -1636,12 +1605,6 @@ markupsafe = [ {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"}, @@ -1659,8 +1622,8 @@ mike = [ {file = "mike-0.6.0.tar.gz", hash = "sha256:6d6239de2a60d733da2f34617e9b9a14c4b5437423b47e524f14dc96d6ce5f2f"}, ] mkdocs = [ - {file = "mkdocs-1.2.3-py3-none-any.whl", hash = "sha256:a1fa8c2d0c1305d7fc2b9d9f607c71778572a8b110fb26642aa00296c9e6d072"}, - {file = "mkdocs-1.2.3.tar.gz", hash = "sha256:89f5a094764381cda656af4298727c9f53dc3e602983087e1fe96ea1df24f4c1"}, + {file = "mkdocs-1.2.4-py3-none-any.whl", hash = "sha256:f108e7ab5a7ed3e30826dbf82f37638f0d90d11161644616cc4f01a1e2ab3940"}, + {file = "mkdocs-1.2.4.tar.gz", hash = "sha256:8e7970a26183487fe2a1041940c6fd03aa0dbe5549e50c3e7194f565cb3c678a"}, ] mkdocs-git-revision-date-plugin = [ {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, @@ -1702,6 +1665,10 @@ mypy-boto3-appconfig = [ {file = "mypy-boto3-appconfig-1.24.29.tar.gz", hash = "sha256:10583d309a9db99babfbe85d3b6467b49b3509a57e4f8771da239f6d5cb3731b"}, {file = "mypy_boto3_appconfig-1.24.29-py3-none-any.whl", hash = "sha256:e9d9e2e25fdd82bffc6262dc184edf5d0d3d9fbb0ab35e597a1ea57ba13d4d80"}, ] +mypy-boto3-cloudformation = [ + {file = "mypy-boto3-cloudformation-1.24.36.post1.tar.gz", hash = "sha256:ed7df9ae3a8390a145229122a1489d0a58bbf9986cb54f0d7a65ed54f12c8e63"}, + {file = "mypy_boto3_cloudformation-1.24.36.post1-py3-none-any.whl", hash = "sha256:b39020c13a876bb18908aad22326478d0ac3faec0bdac0d2c11dc318c9dcf149"}, +] mypy-boto3-cloudwatch = [ {file = "mypy-boto3-cloudwatch-1.24.35.tar.gz", hash = "sha256:92a818e2ea330f9afb5f8f9c15df47934736041e3ccfd696ffc0774bad14e0aa"}, {file = "mypy_boto3_cloudwatch-1.24.35-py3-none-any.whl", hash = "sha256:28947763d70cdac24aca25779cd5b00cd995636f5815fac3d95009430ce02b72"}, @@ -1714,6 +1681,10 @@ mypy-boto3-lambda = [ {file = "mypy-boto3-lambda-1.24.0.tar.gz", hash = "sha256:ab425f941d0d50a2b8a20cc13cebe03c3097b122259bf00e7b295d284814bd6f"}, {file = "mypy_boto3_lambda-1.24.0-py3-none-any.whl", hash = "sha256:a286a464513adf50847bda8573f2dc7adc348234827d1ac0200e610ee9a09b80"}, ] +mypy-boto3-s3 = [ + {file = "mypy-boto3-s3-1.24.36.post1.tar.gz", hash = "sha256:3bd7e06f9ade5059eae2181d7a9f1a41e7fa807ad3e94c01c9901838e87e0abe"}, + {file = "mypy_boto3_s3-1.24.36.post1-py3-none-any.whl", hash = "sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a"}, +] mypy-boto3-secretsmanager = [ {file = "mypy-boto3-secretsmanager-1.24.11.post3.tar.gz", hash = "sha256:f153b3f5ff2c65664a906fb2c97a6598a57da9f1da77679dbaf541051dcff36e"}, {file = "mypy_boto3_secretsmanager-1.24.11.post3-py3-none-any.whl", hash = "sha256:d9655d568f7fd8fe05265613b85fba55ab6e4dcd078989af1ef9f0ffe4b45019"}, @@ -1739,11 +1710,10 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pbr = [ - {file = "pbr-5.8.0-py2.py3-none-any.whl", hash = "sha256:176e8560eaf61e127817ef93d8a844803abb27a4d4637f0ff3bb783129be2e0a"}, - {file = "pbr-5.8.0.tar.gz", hash = "sha256:672d8ebee84921862110f23fcec2acea191ef58543d34dfe9ef3d9f13c31cddf"}, + {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"}, + {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, ] pdoc3 = [ - {file = "pdoc3-0.10.0-py3-none-any.whl", hash = "sha256:ba45d1ada1bd987427d2bf5cdec30b2631a3ff5fb01f6d0e77648a572ce6028b"}, {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, ] platformdirs = [ @@ -1811,16 +1781,16 @@ pyflakes = [ {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pymdown-extensions = [ {file = "pymdown-extensions-9.1.tar.gz", hash = "sha256:74247f2c80f1d9e3c7242abe1c16317da36c6f26c7ad4b8a7f457f0ec20f0365"}, {file = "pymdown_extensions-9.1-py3-none-any.whl", hash = "sha256:b03e66f91f33af4a6e7a0e20c740313522995f69a03d86316b1449766c473d0e"}, ] pyparsing = [ - {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, - {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pytest = [ {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, @@ -1905,35 +1875,39 @@ python-snappy = [ {file = "python_snappy-0.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:03bb511380fca2a13325b6f16fe8234c8e12da9660f0258cd45d9a02ffc916af"}, ] pyyaml = [ - {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, - {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, - {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, - {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"}, - {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, - {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, - {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"}, - {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, - {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, - {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"}, - {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, - {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, - {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"}, - {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"}, - {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, - {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, - {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] pyyaml-env-tag = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, @@ -1944,16 +1918,16 @@ radon = [ {file = "radon-5.1.0.tar.gz", hash = "sha256:cb1d8752e5f862fb9e20d82b5f758cbc4fb1237c92c9a66450ea0ea7bf29aeee"}, ] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] retry = [ {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"}, {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"}, ] "ruamel.yaml" = [ - {file = "ruamel.yaml-0.17.17-py3-none-any.whl", hash = "sha256:9af3ec5d7f8065582f3aa841305465025d0afd26c5fb54e15b964e11838fc74f"}, - {file = "ruamel.yaml-0.17.17.tar.gz", hash = "sha256:9751de4cbb57d4bfbf8fc394e125ed4a2f170fbff3dc3d78abf50be85924f8be"}, + {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, + {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, ] "ruamel.yaml.clib" = [ {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, @@ -1983,8 +1957,8 @@ retry = [ {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, ] s3transfer = [ - {file = "s3transfer-0.5.0-py3-none-any.whl", hash = "sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"}, - {file = "s3transfer-0.5.0.tar.gz", hash = "sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c"}, + {file = "s3transfer-0.5.2-py3-none-any.whl", hash = "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971"}, + {file = "s3transfer-0.5.2.tar.gz", hash = "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1998,139 +1972,141 @@ stevedore = [ {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, ] -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-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, ] typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] types-requests = [ {file = "types-requests-2.28.8.tar.gz", hash = "sha256:7a9f7b152d594a1c18dd4932cdd2596b8efbeedfd73caa4e4abb3755805b4685"}, {file = "types_requests-2.28.8-py3-none-any.whl", hash = "sha256:b0421f9f2d0dd0f8df2c75f974686517ca67473f05b466232d4c6384d765ad7a"}, ] types-urllib3 = [ - {file = "types-urllib3-1.26.17.tar.gz", hash = "sha256:73fd274524c3fc7cd8cd9ceb0cb67ed99b45f9cb2831013e46d50c1451044800"}, - {file = "types_urllib3-1.26.17-py3-none-any.whl", hash = "sha256:0d027fcd27dbb3cb532453b4d977e05bc1e13aefd70519866af211b3003d895d"}, + {file = "types-urllib3-1.26.22.tar.gz", hash = "sha256:b05af90e73889e688094008a97ca95788db8bf3736e2776fd43fb6b171485d94"}, + {file = "types_urllib3-1.26.22-py3-none-any.whl", hash = "sha256:09a8783e1002472e8d1e1f3792d4c5cca1fffebb9b48ee1512aae6d16fe186bc"}, ] typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] -urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, -] +urllib3 = [] watchdog = [ - {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, - {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"}, - {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"}, - {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"}, - {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"}, - {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"}, - {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"}, - {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"}, - {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"}, - {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"}, - {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"}, - {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"}, - {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"}, - {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"}, - {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"}, - {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"}, - {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"}, + {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, + {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, + {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"}, + {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"}, + {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"}, + {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"}, + {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"}, + {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"}, + {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"}, + {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"}, + {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"}, + {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"}, + {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"}, + {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"}, + {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"}, + {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"}, + {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"}, + {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"}, ] wrapt = [ - {file = "wrapt-1.13.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229"}, - {file = "wrapt-1.13.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80"}, - {file = "wrapt-1.13.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca"}, - {file = "wrapt-1.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056"}, - {file = "wrapt-1.13.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096"}, - {file = "wrapt-1.13.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33"}, - {file = "wrapt-1.13.3-cp310-cp310-win32.whl", hash = "sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f"}, - {file = "wrapt-1.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3"}, - {file = "wrapt-1.13.3-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755"}, - {file = "wrapt-1.13.3-cp35-cp35m-win32.whl", hash = "sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851"}, - {file = "wrapt-1.13.3-cp35-cp35m-win_amd64.whl", hash = "sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13"}, - {file = "wrapt-1.13.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade"}, - {file = "wrapt-1.13.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf"}, - {file = "wrapt-1.13.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125"}, - {file = "wrapt-1.13.3-cp36-cp36m-win32.whl", hash = "sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36"}, - {file = "wrapt-1.13.3-cp36-cp36m-win_amd64.whl", hash = "sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10"}, - {file = "wrapt-1.13.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709"}, - {file = "wrapt-1.13.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2"}, - {file = "wrapt-1.13.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b"}, - {file = "wrapt-1.13.3-cp37-cp37m-win32.whl", hash = "sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829"}, - {file = "wrapt-1.13.3-cp37-cp37m-win_amd64.whl", hash = "sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"}, - {file = "wrapt-1.13.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554"}, - {file = "wrapt-1.13.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b"}, - {file = "wrapt-1.13.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce"}, - {file = "wrapt-1.13.3-cp38-cp38-win32.whl", hash = "sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79"}, - {file = "wrapt-1.13.3-cp38-cp38-win_amd64.whl", hash = "sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb"}, - {file = "wrapt-1.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32"}, - {file = "wrapt-1.13.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e"}, - {file = "wrapt-1.13.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640"}, - {file = "wrapt-1.13.3-cp39-cp39-win32.whl", hash = "sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374"}, - {file = "wrapt-1.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb"}, - {file = "wrapt-1.13.3.tar.gz", hash = "sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185"}, + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] xenon = [ {file = "xenon-0.9.0-py2.py3-none-any.whl", hash = "sha256:994c80c7f1c6d40596b600b93734d85a5739208f31895ef99f1e4d362caf9e35"}, diff --git a/pyproject.toml b/pyproject.toml index 0a3a0babd51..7613ffc0170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ coverage = {extras = ["toml"], version = "^6.2"} pytest = "^7.0.1" black = "^21.12b0" flake8 = "^4.0.1" -flake8-black = "^0.2.3" flake8-builtins = "^1.5.3" flake8-comprehensions = "^3.7.0" flake8-debugger = "^4.0.0" @@ -66,6 +65,8 @@ pytest-benchmark = "^3.4.1" mypy-boto3-cloudwatch = "^1.24.35" mypy-boto3-lambda = "^1.24.0" mypy-boto3-xray = "^1.24.0" +mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" } types-requests = "^2.28.8" typing-extensions = { version = "^4.3.0", python = ">=3.7" } python-snappy = "^0.6.1" diff --git a/tests/e2e/metrics/conftest.py b/tests/e2e/metrics/conftest.py new file mode 100644 index 00000000000..0f4ca8e58c2 --- /dev/null +++ b/tests/e2e/metrics/conftest.py @@ -0,0 +1,21 @@ +import pytest + +from tests.e2e.metrics.infrastructure import MetricsStack +from tests.e2e.utils.infrastructure import deploy_once + + +@pytest.fixture(autouse=True, scope="module") +def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): + """Setup and teardown logic for E2E test infrastructure + + Parameters + ---------- + request : fixtures.SubRequest + test fixture containing metadata about test execution + + Yields + ------ + Dict[str, str] + CloudFormation Outputs from deployed infrastructure + """ + yield from deploy_once(stack=MetricsStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id) diff --git a/tests/e2e/metrics/handlers/basic_handler.py b/tests/e2e/metrics/handlers/basic_handler.py index dd2f486d980..ef5e079e604 100644 --- a/tests/e2e/metrics/handlers/basic_handler.py +++ b/tests/e2e/metrics/handlers/basic_handler.py @@ -1,14 +1,17 @@ -import os - from aws_lambda_powertools import Metrics -from aws_lambda_powertools.metrics import MetricUnit - -METRIC_NAME = os.environ["METRIC_NAME"] -metrics = Metrics() +my_metrics = Metrics() -@metrics.log_metrics +@my_metrics.log_metrics def lambda_handler(event, context): - metrics.add_metric(name=METRIC_NAME, unit=MetricUnit.Count, value=1) + metrics, namespace, service = event.get("metrics"), event.get("namespace"), event.get("service") + + # Maintenance: create a public method to set these explicitly + my_metrics.namespace = namespace + my_metrics.service = service + + for metric in metrics: + my_metrics.add_metric(**metric) + return "success" diff --git a/tests/e2e/metrics/handlers/cold_start.py b/tests/e2e/metrics/handlers/cold_start.py new file mode 100644 index 00000000000..20f2ad16f85 --- /dev/null +++ b/tests/e2e/metrics/handlers/cold_start.py @@ -0,0 +1,12 @@ +from aws_lambda_powertools import Metrics + +my_metrics = Metrics() + + +@my_metrics.log_metrics(capture_cold_start_metric=True) +def lambda_handler(event, context): + # Maintenance: create a public method to set these explicitly + my_metrics.namespace = event.get("namespace") + my_metrics.service = event.get("service") + + return "success" diff --git a/tests/e2e/metrics/infrastructure.py b/tests/e2e/metrics/infrastructure.py new file mode 100644 index 00000000000..e01fb16b02e --- /dev/null +++ b/tests/e2e/metrics/infrastructure.py @@ -0,0 +1,11 @@ +from pathlib import Path + +from tests.e2e.utils.infrastructure import BaseInfrastructureV2 + + +class MetricsStack(BaseInfrastructureV2): + def __init__(self, handlers_dir: Path, feature_name: str = "metrics") -> None: + super().__init__(feature_name, handlers_dir) + + def create_resources(self): + self.create_lambda_functions() diff --git a/tests/e2e/metrics/test_metrics.py b/tests/e2e/metrics/test_metrics.py index 7d3aa7efa61..f1a31bb3c82 100644 --- a/tests/e2e/metrics/test_metrics.py +++ b/tests/e2e/metrics/test_metrics.py @@ -1,40 +1,69 @@ -import datetime -import uuid +import json -import boto3 import pytest -from e2e import conftest -from e2e.utils import helpers +from tests.e2e.utils import helpers -@pytest.fixture(scope="module") -def config() -> conftest.LambdaConfig: - return { - "parameters": {}, - "environment_variables": { - "POWERTOOLS_METRICS_NAMESPACE": "powertools-e2e-metric", - "POWERTOOLS_SERVICE_NAME": "test-powertools-service", - "METRIC_NAME": f"business-metric-{str(uuid.uuid4()).replace('-','_')}", - }, - } +@pytest.fixture +def basic_handler_fn(infrastructure: dict) -> str: + return infrastructure.get("BasicHandler", "") -def test_basic_lambda_metric_visible(execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig): + +@pytest.fixture +def basic_handler_fn_arn(infrastructure: dict) -> str: + return infrastructure.get("BasicHandlerArn", "") + + +@pytest.fixture +def cold_start_fn(infrastructure: dict) -> str: + return infrastructure.get("ColdStart", "") + + +@pytest.fixture +def cold_start_fn_arn(infrastructure: dict) -> str: + return infrastructure.get("ColdStartArn", "") + + +METRIC_NAMESPACE = "powertools-e2e-metric" + + +def test_basic_lambda_metric_is_visible(basic_handler_fn: str, basic_handler_fn_arn: str): # GIVEN - start_date = execute_lambda.get_lambda_execution_time() - end_date = start_date + datetime.timedelta(minutes=5) + metric_name = helpers.build_metric_name() + service = helpers.build_service_name() + dimensions = helpers.build_add_dimensions_input(service=service) + metrics = helpers.build_multiple_add_metric_input(metric_name=metric_name, value=1, quantity=3) # WHEN + event = json.dumps({"metrics": metrics, "service": service, "namespace": METRIC_NAMESPACE}) + _, execution_time = helpers.trigger_lambda(lambda_arn=basic_handler_fn_arn, payload=event) + + metrics = helpers.get_metrics( + namespace=METRIC_NAMESPACE, start_date=execution_time, metric_name=metric_name, dimensions=dimensions + ) + + # THEN + metric_data = metrics.get("Values", []) + assert metric_data and metric_data[0] == 3.0 + + +def test_cold_start_metric(cold_start_fn_arn: str, cold_start_fn: str): + # GIVEN + metric_name = "ColdStart" + service = helpers.build_service_name() + dimensions = helpers.build_add_dimensions_input(function_name=cold_start_fn, service=service) + + # WHEN we invoke twice + event = json.dumps({"service": service, "namespace": METRIC_NAMESPACE}) + + _, execution_time = helpers.trigger_lambda(lambda_arn=cold_start_fn_arn, payload=event) + _, _ = helpers.trigger_lambda(lambda_arn=cold_start_fn_arn, payload=event) + metrics = helpers.get_metrics( - start_date=start_date, - end_date=end_date, - namespace=config["environment_variables"]["POWERTOOLS_METRICS_NAMESPACE"], - metric_name=config["environment_variables"]["METRIC_NAME"], - service_name=config["environment_variables"]["POWERTOOLS_SERVICE_NAME"], - cw_client=boto3.client(service_name="cloudwatch"), + namespace=METRIC_NAMESPACE, start_date=execution_time, metric_name=metric_name, dimensions=dimensions ) # THEN - assert metrics.get("Timestamps") and len(metrics.get("Timestamps")) == 1 - assert metrics.get("Values") and len(metrics.get("Values")) == 1 - assert metrics.get("Values") and metrics.get("Values")[0] == 1 + metric_data = metrics.get("Values", []) + assert metric_data and metric_data[0] == 1.0 diff --git a/tests/e2e/utils/asset.py b/tests/e2e/utils/asset.py new file mode 100644 index 00000000000..0bc7b5dfabe --- /dev/null +++ b/tests/e2e/utils/asset.py @@ -0,0 +1,120 @@ +import io +import json +import zipfile +from pathlib import Path +from typing import List, Optional + +import boto3 +import botocore.exceptions +from mypy_boto3_s3 import S3Client + +from aws_lambda_powertools import Logger +from tests.e2e.utils.models import AssetTemplateConfig, TemplateAssembly + +logger = Logger(service="e2e-utils") + + +class Asset: + def __init__( + self, config: AssetTemplateConfig, account_id: str, region: str, boto3_client: Optional[S3Client] = None + ) -> None: + """CDK Asset logic to verify existence and resolve deeply nested configuration + + Parameters + ---------- + config : AssetTemplateConfig + CDK Asset configuration found in synthesized template + account_id : str + AWS Account ID + region : str + AWS Region + boto3_client : Optional["S3Client"], optional + S3 client instance for asset operations, by default None + """ + self.config = config + self.s3 = boto3_client or boto3.client("s3") + self.account_id = account_id + self.region = region + self.asset_path = config.source.path + self.asset_packaging = config.source.packaging + self.object_key = config.destinations.current_account_current_region.object_key + self._bucket = config.destinations.current_account_current_region.bucket_name + self.bucket_name = self._resolve_bucket_name() + + @property + def is_zip(self): + return self.asset_packaging == "zip" + + def exists_in_s3(self, key: str) -> bool: + try: + return self.s3.head_object(Bucket=self.bucket_name, Key=key) is not None + except botocore.exceptions.ClientError: + return False + + def _resolve_bucket_name(self) -> str: + return self._bucket.replace("${AWS::AccountId}", self.account_id).replace("${AWS::Region}", self.region) + + +class Assets: + def __init__( + self, asset_manifest: Path, account_id: str, region: str, boto3_client: Optional[S3Client] = None + ) -> None: + """CDK Assets logic to find each asset, compress, and upload + + Parameters + ---------- + asset_manifest : Path + Asset manifest JSON file (self.__synthesize) + account_id : str + AWS Account ID + region : str + AWS Region + boto3_client : Optional[S3Client], optional + S3 client instance for asset operations, by default None + """ + self.asset_manifest = asset_manifest + self.account_id = account_id + self.region = region + self.s3 = boto3_client or boto3.client("s3") + self.assets = self._find_assets_from_template() + self.assets_location = str(self.asset_manifest.parent) + + def upload(self): + """Drop-in replacement for cdk-assets package s3 upload part. + https://www.npmjs.com/package/cdk-assets. + We use custom solution to avoid dependencies from nodejs ecosystem. + We follow the same design cdk-assets: + https://github.com/aws/aws-cdk-rfcs/blob/master/text/0092-asset-publishing.md. + """ + for asset in self.assets: + if not asset.is_zip: + logger.debug(f"Asset '{asset.object_key}' is not zip. Skipping upload.") + continue + + if asset.exists_in_s3(key=asset.object_key): + logger.debug(f"Asset '{asset.object_key}' already exists in S3. Skipping upload.") + continue + + archive = self._compress_assets(asset) + logger.debug("Uploading archive to S3") + self.s3.upload_fileobj(Fileobj=archive, Bucket=asset.bucket_name, Key=asset.object_key) + logger.debug("Successfully uploaded") + + def _find_assets_from_template(self) -> List[Asset]: + data = json.loads(self.asset_manifest.read_text()) + template = TemplateAssembly(**data) + return [ + Asset(config=asset_config, account_id=self.account_id, region=self.region) + for asset_config in template.files.values() + ] + + def _compress_assets(self, asset: Asset) -> io.BytesIO: + buf = io.BytesIO() + asset_dir = f"{self.assets_location}/{asset.asset_path}" + asset_files = list(Path(asset_dir).iterdir()) + with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as archive: + for asset_file in asset_files: + logger.debug(f"Adding file '{asset_file}' to the archive.") + archive.write(asset_file, arcname=asset_file.relative_to(asset_dir)) + buf.seek(0) + return buf diff --git a/tests/e2e/utils/helpers.py b/tests/e2e/utils/helpers.py index 3f88f44f933..6827ac12d90 100644 --- a/tests/e2e/utils/helpers.py +++ b/tests/e2e/utils/helpers.py @@ -1,17 +1,22 @@ import json -from datetime import datetime +import secrets +from datetime import datetime, timedelta from functools import lru_cache -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Tuple, Union -from mypy_boto3_cloudwatch import type_defs +import boto3 from mypy_boto3_cloudwatch.client import CloudWatchClient +from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataQueryTypeDef, MetricDataResultTypeDef from mypy_boto3_lambda.client import LambdaClient +from mypy_boto3_lambda.type_defs import InvocationResponseTypeDef from mypy_boto3_xray.client import XRayClient from pydantic import BaseModel from retry import retry - # Helper methods && Class +from aws_lambda_powertools.metrics import MetricUnit + + class Log(BaseModel): level: str location: str @@ -33,9 +38,12 @@ class TraceSegment(BaseModel): annotations: Dict = {} -def trigger_lambda(lambda_arn: str, client: LambdaClient): - response = client.invoke(FunctionName=lambda_arn, InvocationType="RequestResponse") - return response +def trigger_lambda( + lambda_arn: str, payload: str, client: Optional[LambdaClient] = None +) -> Tuple[InvocationResponseTypeDef, datetime]: + client = client or boto3.client("lambda") + execution_time = datetime.utcnow() + return client.invoke(FunctionName=lambda_arn, InvocationType="RequestResponse", Payload=payload), execution_time @lru_cache(maxsize=10, typed=False) @@ -55,28 +63,61 @@ def get_logs(lambda_function_name: str, log_client: CloudWatchClient, start_time return filtered_logs -@lru_cache(maxsize=10, typed=False) -@retry(ValueError, delay=1, jitter=1, tries=20) +@retry(ValueError, delay=2, jitter=1.5, tries=10) def get_metrics( namespace: str, - cw_client: CloudWatchClient, start_date: datetime, metric_name: str, - service_name: str, + dimensions: Optional[List[DimensionTypeDef]] = None, + cw_client: Optional[CloudWatchClient] = None, end_date: Optional[datetime] = None, -) -> type_defs.MetricDataResultTypeDef: + period: int = 60, + stat: str = "Sum", +) -> MetricDataResultTypeDef: + """Fetch CloudWatch Metrics + + It takes into account eventual consistency with up to 10 retries and 1s jitter. + + Parameters + ---------- + namespace : str + Metric Namespace + start_date : datetime + Start window to fetch metrics + metric_name : str + Metric name + dimensions : Optional[List[DimensionTypeDef]], optional + List of Metric Dimension, by default None + cw_client : Optional[CloudWatchClient], optional + Boto3 CloudWatch low-level client (boto3.client("cloudwatch"), by default None + end_date : Optional[datetime], optional + End window to fetch metrics, by default start_date + 2 minutes window + period : int, optional + Time period to fetch metrics for, by default 60 + stat : str, optional + Aggregation function to use when fetching metrics, by default "Sum" + + Returns + ------- + MetricDataResultTypeDef + _description_ + + Raises + ------ + ValueError + When no metric is found within retry window + """ + cw_client = cw_client or boto3.client("cloudwatch") + end_date = end_date or start_date + timedelta(minutes=2) + + metric_query = build_metric_query_data( + namespace=namespace, metric_name=metric_name, period=period, stat=stat, dimensions=dimensions + ) + response = cw_client.get_metric_data( - MetricDataQueries=[ - { - "Id": "m1", - "Expression": f'SELECT MAX("{metric_name}") from SCHEMA("{namespace}",service) \ - where service=\'{service_name}\'', - "ReturnData": True, - "Period": 600, - }, - ], + MetricDataQueries=metric_query, StartTime=start_date, - EndTime=end_date if end_date else datetime.utcnow(), + EndTime=end_date or datetime.utcnow(), ) result = response["MetricDataResults"][0] if not result["Values"]: @@ -85,7 +126,10 @@ def get_metrics( @retry(ValueError, delay=1, jitter=1, tries=10) -def get_traces(filter_expression: str, xray_client: XRayClient, start_date: datetime, end_date: datetime) -> Dict: +def get_traces( + filter_expression: str, start_date: datetime, end_date: datetime, xray_client: Optional[XRayClient] = None +) -> Dict: + xray_client = xray_client or boto3.client("xray") paginator = xray_client.get_paginator("get_trace_summaries") response_iterator = paginator.paginate( StartTime=start_date, @@ -129,3 +173,104 @@ def find_meta(segment: dict, result: List): ) if x_subsegment.get("subsegments"): find_meta(segment=x_subsegment, result=result) + + +# Maintenance: Build a separate module for builders +def build_metric_name() -> str: + return f"test_metric{build_random_value()}" + + +def build_service_name() -> str: + return f"test_service{build_random_value()}" + + +def build_random_value(nbytes: int = 10) -> str: + return secrets.token_urlsafe(nbytes).replace("-", "") + + +def build_metric_query_data( + namespace: str, + metric_name: str, + period: int = 60, + stat: str = "Sum", + dimensions: Optional[List[DimensionTypeDef]] = None, +) -> List[MetricDataQueryTypeDef]: + dimensions = dimensions or [] + data_query: List[MetricDataQueryTypeDef] = [ + { + "Id": metric_name.lower(), + "MetricStat": { + "Metric": {"Namespace": namespace, "MetricName": metric_name}, + "Period": period, + "Stat": stat, + }, + "ReturnData": True, + } + ] + + if dimensions: + data_query[0]["MetricStat"]["Metric"]["Dimensions"] = dimensions + + return data_query + + +def build_add_metric_input(metric_name: str, value: float, unit: str = MetricUnit.Count.value) -> Dict: + """Create a metric input to be used with Metrics.add_metric() + + Parameters + ---------- + metric_name : str + metric name + value : float + metric value + unit : str, optional + metric unit, by default Count + + Returns + ------- + Dict + Metric input + """ + return {"name": metric_name, "unit": unit, "value": value} + + +def build_multiple_add_metric_input( + metric_name: str, value: float, unit: str = MetricUnit.Count.value, quantity: int = 1 +) -> Dict: + """Create list of metrics input to be used with Metrics.add_metric() + + Parameters + ---------- + metric_name : str + metric name + value : float + metric value + unit : str, optional + metric unit, by default Count + quantity : int, optional + number of metrics to be created, by default 1 + + Returns + ------- + List[Dict] + List of metrics + """ + return [{"name": metric_name, "unit": unit, "value": value} for _ in range(quantity)] + + +def build_add_dimensions_input(**dimensions) -> List[DimensionTypeDef]: + """Create dimensions input to be used with either get_metrics or Metrics.add_dimension() + + Parameters + ---------- + name : str + dimension name + value : float + dimension value + + Returns + ------- + Dict + Metric dimension input + """ + return [{"Name": name, "Value": value} for name, value in dimensions.items()] diff --git a/tests/e2e/utils/infrastructure.py b/tests/e2e/utils/infrastructure.py index 001ae0e6346..fb3c3c02ce2 100644 --- a/tests/e2e/utils/infrastructure.py +++ b/tests/e2e/utils/infrastructure.py @@ -6,12 +6,18 @@ from abc import ABC, abstractmethod from enum import Enum from pathlib import Path -from typing import Dict, List, Tuple, Type +from typing import Dict, Generator, List, Optional, Tuple, Type +from uuid import uuid4 import boto3 +import pytest import yaml from aws_cdk import App, AssetStaging, BundlingOptions, CfnOutput, DockerImage, RemovalPolicy, Stack, aws_logs from aws_cdk.aws_lambda import Code, Function, LayerVersion, Runtime, Tracing +from filelock import FileLock +from mypy_boto3_cloudformation import CloudFormationClient + +from tests.e2e.utils.asset import Assets PYTHON_RUNTIME_VERSION = f"V{''.join(map(str, sys.version_info[:2]))}" @@ -24,11 +30,11 @@ class PythonVersion(Enum): class BaseInfrastructureStack(ABC): @abstractmethod - def synthesize() -> Tuple[dict, str]: + def synthesize(self) -> Tuple[dict, str]: ... @abstractmethod - def __call__() -> Tuple[dict, str]: + def __call__(self) -> Tuple[dict, str]: ... @@ -115,7 +121,7 @@ def __init__(self, stack_name: str, handlers_dir: str, config: dict) -> None: session = boto3.Session() self.s3_client = session.client("s3") self.lambda_client = session.client("lambda") - self.cf_client = session.client("cloudformation") + self.cfn = session.client("cloudformation") self.s3_resource = session.resource("s3") self.account_id = session.client("sts").get_caller_identity()["Account"] self.region = session.region_name @@ -134,7 +140,7 @@ def deploy(self, Stack: Type[BaseInfrastructureStack]) -> Dict[str, str]: return self._transform_output(response["Stacks"][0]["Outputs"]) def delete(self): - self.cf_client.delete_stack(StackName=self.stack_name) + self.cfn.delete_stack(StackName=self.stack_name) def _upload_assets(self, asset_root_dir: str, asset_manifest_file: str): """ @@ -177,16 +183,16 @@ def _find_files(self, directory: str) -> List: return file_paths def _deploy_stack(self, stack_name: str, template: dict): - response = self.cf_client.create_stack( + response = self.cfn.create_stack( StackName=stack_name, TemplateBody=yaml.dump(template), TimeoutInMinutes=10, OnFailure="ROLLBACK", Capabilities=["CAPABILITY_IAM"], ) - waiter = self.cf_client.get_waiter("stack_create_complete") + waiter = self.cfn.get_waiter("stack_create_complete") waiter.wait(StackName=stack_name, WaiterConfig={"Delay": 10, "MaxAttempts": 50}) - response = self.cf_client.describe_stacks(StackName=stack_name) + response = self.cfn.describe_stacks(StackName=stack_name) return response def _find_assets(self, asset_template: str, account_id: str, region: str): @@ -210,3 +216,224 @@ def _find_assets(self, asset_template: str, account_id: str, region: str): def _transform_output(self, outputs: dict): return {output["OutputKey"]: output["OutputValue"] for output in outputs if output["OutputKey"]} + + +class BaseInfrastructureV2(ABC): + def __init__(self, feature_name: str, handlers_dir: Path) -> None: + self.stack_name = f"test-{feature_name}-{uuid4()}" + self.handlers_dir = handlers_dir + self.stack_outputs: Dict[str, str] = {} + self.app = App() + self.stack = Stack(self.app, self.stack_name) + self.session = boto3.Session() + self.cfn: CloudFormationClient = self.session.client("cloudformation") + + # NOTE: CDK stack account and region are tokens, we need to resolve earlier + self.account_id = self.session.client("sts").get_caller_identity()["Account"] + self.region = self.session.region_name + + def create_lambda_functions(self, function_props: Optional[Dict] = None): + """Create Lambda functions available under handlers_dir + + It creates CloudFormation Outputs for every function found in PascalCase. For example, + {handlers_dir}/basic_handler.py creates `BasicHandler` and `BasicHandlerArn` outputs. + + + Parameters + ---------- + function_props: Optional[Dict] + CDK Lambda FunctionProps as dictionary to override defaults + + Examples + -------- + + Creating Lambda functions available in the handlers directory + + ```python + self.create_lambda_functions() + ``` + + Creating Lambda functions and override runtime to Python 3.7 + + ```python + from aws_cdk.aws_lambda import Runtime + + self.create_lambda_functions(function_props={"runtime": Runtime.PYTHON_3_7) + ``` + """ + handlers = list(self.handlers_dir.rglob("*.py")) + source = Code.from_asset(f"{self.handlers_dir}") + props_override = function_props or {} + + for fn in handlers: + fn_name = fn.stem + function_settings = { + "id": f"{fn_name}-lambda", + "code": source, + "handler": f"{fn_name}.lambda_handler", + "tracing": Tracing.ACTIVE, + "runtime": Runtime.PYTHON_3_9, + "layers": [ + LayerVersion.from_layer_version_arn( + self.stack, + f"{fn_name}-lambda-powertools", + f"arn:aws:lambda:{self.region}:017000801446:layer:AWSLambdaPowertoolsPython:29", + ) + ], + **props_override, + } + + function_python = Function(self.stack, **function_settings) + + aws_logs.LogGroup( + self.stack, + id=f"{fn_name}-lg", + log_group_name=f"/aws/lambda/{function_python.function_name}", + retention=aws_logs.RetentionDays.ONE_DAY, + removal_policy=RemovalPolicy.DESTROY, + ) + + # CFN Outputs only support hyphen + fn_name_pascal_case = fn_name.title().replace("_", "") # basic_handler -> BasicHandler + self._add_resource_output( + name=fn_name_pascal_case, value=function_python.function_name, arn=function_python.function_arn + ) + + def deploy(self) -> Dict[str, str]: + """Creates CloudFormation Stack and return stack outputs as dict + + Returns + ------- + Dict[str, str] + CloudFormation Stack Outputs with output key and value + """ + template, asset_manifest_file = self._synthesize() + assets = Assets(asset_manifest=asset_manifest_file, account_id=self.account_id, region=self.region) + assets.upload() + return self._deploy_stack(self.stack_name, template) + + def delete(self): + """Delete CloudFormation Stack""" + self.cfn.delete_stack(StackName=self.stack_name) + + @abstractmethod + def create_resources(self): + """Create any necessary CDK resources. It'll be called before deploy + + Examples + ------- + + Creating a S3 bucket and export name and ARN + + ```python + def created_resources(self): + s3 = s3.Bucket(self.stack, "MyBucket") + + # This will create MyBucket and MyBucketArn CloudFormation Output + self._add_resource_output(name="MyBucket", value=s3.bucket_name, arn_value=bucket.bucket_arn) + ``` + + Creating Lambda functions available in the handlers directory + + ```python + def created_resources(self): + self.create_lambda_functions() + ``` + """ + ... + + def _synthesize(self) -> Tuple[Dict, Path]: + self.create_resources() + cloud_assembly = self.app.synth() + cf_template: Dict = cloud_assembly.get_stack_by_name(self.stack_name).template + cloud_assembly_assets_manifest_path: str = ( + cloud_assembly.get_stack_by_name(self.stack_name).dependencies[0].file # type: ignore[attr-defined] + ) + return cf_template, Path(cloud_assembly_assets_manifest_path) + + def _deploy_stack(self, stack_name: str, template: Dict) -> Dict[str, str]: + self.cfn.create_stack( + StackName=stack_name, + TemplateBody=yaml.dump(template), + TimeoutInMinutes=10, + OnFailure="ROLLBACK", + Capabilities=["CAPABILITY_IAM"], + ) + waiter = self.cfn.get_waiter("stack_create_complete") + waiter.wait(StackName=stack_name, WaiterConfig={"Delay": 10, "MaxAttempts": 50}) + + stack_details = self.cfn.describe_stacks(StackName=stack_name) + stack_outputs = stack_details["Stacks"][0]["Outputs"] + self.stack_outputs = { + output["OutputKey"]: output["OutputValue"] for output in stack_outputs if output["OutputKey"] + } + + return self.stack_outputs + + def _add_resource_output(self, name: str, value: str, arn: str): + """Add both resource value and ARN as Outputs to facilitate tests. + + This will create two outputs: {Name} and {Name}Arn + + Parameters + ---------- + name : str + CloudFormation Output Key + value : str + CloudFormation Output Value + arn : str + CloudFormation Output Value for ARN + """ + CfnOutput(self.stack, f"{name}", value=value) + CfnOutput(self.stack, f"{name}Arn", value=arn) + + +def deploy_once( + stack: Type[BaseInfrastructureV2], + request: pytest.FixtureRequest, + tmp_path_factory: pytest.TempPathFactory, + worker_id: str, +) -> Generator[Dict[str, str], None, None]: + """Deploys provided stack once whether CPU parallelization is enabled or not + + Parameters + ---------- + stack : Type[BaseInfrastructureV2] + stack class to instantiate and deploy, for example MetricStack. + Not to be confused with class instance (MetricStack()). + request : pytest.FixtureRequest + pytest request fixture to introspect absolute path to test being executed + tmp_path_factory : pytest.TempPathFactory + pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up + worker_id : str + pytest-xdist worker identification to detect whether parallelization is enabled + + Yields + ------ + Generator[Dict[str, str], None, None] + stack CloudFormation outputs + """ + handlers_dir = f"{request.path.parent}/handlers" + stack = stack(handlers_dir=Path(handlers_dir)) + + try: + if worker_id == "master": + # no parallelization, deploy stack and let fixture be cached + yield stack.deploy() + else: + # tmp dir shared by all workers + root_tmp_dir = tmp_path_factory.getbasetemp().parent + + cache = root_tmp_dir / "cache.json" + with FileLock(f"{cache}.lock"): + # If cache exists, return stack outputs back + # otherwise it's the first run by the main worker + # deploy and return stack outputs so subsequent workers can reuse + if cache.is_file(): + stack_outputs = json.loads(cache.read_text()) + else: + stack_outputs: Dict = stack.deploy() + cache.write_text(json.dumps(stack_outputs)) + yield stack_outputs + finally: + stack.delete() diff --git a/tests/e2e/utils/models.py b/tests/e2e/utils/models.py new file mode 100644 index 00000000000..0c3f81070d5 --- /dev/null +++ b/tests/e2e/utils/models.py @@ -0,0 +1,31 @@ +from typing import Dict + +from pydantic import BaseModel, Field + + +class AssetTemplateConfigSource(BaseModel): + path: str + packaging: str + + +class AssetTemplateConfigDestinationsAccount(BaseModel): + bucket_name: str = Field(str, alias="bucketName") + object_key: str = Field(str, alias="objectKey") + assume_role_arn: str = Field(str, alias="assumeRoleArn") + + +class AssetTemplateConfigDestinations(BaseModel): + current_account_current_region: AssetTemplateConfigDestinationsAccount = Field( + AssetTemplateConfigDestinationsAccount, alias="current_account-current_region" + ) + + +class AssetTemplateConfig(BaseModel): + source: AssetTemplateConfigSource + destinations: AssetTemplateConfigDestinations + + +class TemplateAssembly(BaseModel): + version: str + files: Dict[str, AssetTemplateConfig] + docker_images: Dict = Field(Dict, alias="dockerImages") From b71e7ddcb948a9483aa8432331ed0de76abe5a5b Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 12 Aug 2022 17:26:37 +0200 Subject: [PATCH 28/59] chore(ci): remove area/utilities conflicting label --- .github/boring-cyborg.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml index 0e2b7a2dc81..ecd67fc20d9 100644 --- a/.github/boring-cyborg.yml +++ b/.github/boring-cyborg.yml @@ -37,14 +37,13 @@ labelPRBasedOnFilePath: area/feature_flags: - aws_lambda_powertools/feature_flags/* - aws_lambda_powertools/feature_flags/**/* - area/jmespath_util: + area/jmespath: - aws_lambda_powertools/utilities/jmespath_utils/* area/typing: - aws_lambda_powertools/utilities/typing/* - mypy.ini - area/utilities: - - aws_lambda_powertools/utilities/* - - aws_lambda_powertools/utilities/**/* + area/commons: + - aws_lambda_powertools/shared/* documentation: - docs/* From d989028b2008310069b53dc88d71de39690cd3e2 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 12 Aug 2022 17:54:43 +0200 Subject: [PATCH 29/59] chore(ci): remove conventional changelog commit to reduce noise --- .github/workflows/reusable_publish_changelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml index 16fd2c06d18..9bd0959d1c8 100644 --- a/.github/workflows/reusable_publish_changelog.yml +++ b/.github/workflows/reusable_publish_changelog.yml @@ -29,5 +29,5 @@ jobs: - name: Update Changelog in trunk run: | git add CHANGELOG.md - git commit -m "chore(ci): update changelog with latest changes" + git commit -m "update changelog with latest changes" git push origin HEAD:refs/heads/develop From b0c946297e806bc0ebc0092ad40bd05178a8023a Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 12 Aug 2022 15:56:51 +0000 Subject: [PATCH 30/59] update changelog with latest changes --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae964561674..9823a915238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,14 +27,14 @@ ## Maintenance -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes +* **ci:** remove conventional changelog commit to reduce noise +* **ci:** remove area/utilities conflicting label * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes +* **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) +* **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) From 3b16fcc720572a033b202e8ae96c6626bf10c1f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 18:10:44 +0200 Subject: [PATCH 31/59] chore(deps): bump pydantic from 1.9.1 to 1.9.2 (#1448) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 175 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 107 insertions(+), 68 deletions(-) diff --git a/poetry.lock b/poetry.lock index 16259c0b393..08dc7dc6dd7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -131,7 +131,7 @@ python-versions = "*" attrs = ">=17.3" [package.extras] -dev = ["bumpversion", "wheel", "watchdog", "flake8", "tox", "coverage", "sphinx", "pytest", "hypothesis", "pendulum"] +dev = ["pendulum", "hypothesis", "pytest", "sphinx", "coverage", "tox", "flake8", "watchdog", "wheel", "bumpversion"] [[package]] name = "cattrs" @@ -236,12 +236,12 @@ optional = true python-versions = ">=3.6,<4.0" [package.extras] -dnssec = ["cryptography (>=2.6,<37.0)"] -curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] -doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.20)"] wmi = ["wmi (>=1.5.1,<2.0.0)"] +trio = ["trio (>=0.14,<0.20)"] +curio = ["sniffio (>=1.1,<2.0)", "curio (>=1.2,<2.0)"] +doh = ["requests-toolbelt (>=0.9.1,<0.10.0)", "requests (>=2.23.0,<3.0.0)", "httpx (>=0.21.1)", "h2 (>=4.1.0)"] +idna = ["idna (>=2.1,<4.0)"] +dnssec = ["cryptography (>=2.6,<37.0)"] [[package]] name = "email-validator" @@ -337,7 +337,7 @@ python-versions = "*" flake8 = "*" [package.extras] -test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] +test = ["pytest-cov", "pytest", "mock", "coveralls", "coverage"] [[package]] name = "flake8-comprehensions" @@ -428,7 +428,7 @@ python-versions = "*" python-dateutil = ">=2.8.1" [package.extras] -dev = ["twine", "markdown", "flake8", "wheel"] +dev = ["wheel", "flake8", "markdown", "twine"] [[package]] name = "gitdb" @@ -489,8 +489,8 @@ python-versions = ">=3.6" zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [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)", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest-mypy", "pytest-black (>=0.3.7)", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=6)"] +docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] [[package]] name = "iniconfig" @@ -509,10 +509,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -pipfile_deprecated_finder = ["pipreqs", "requirementslib"] -requirements_deprecated_finder = ["pipreqs", "pip-api"] -colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +requirements_deprecated_finder = ["pip-api", "pipreqs"] +pipfile_deprecated_finder = ["requirementslib", "pipreqs"] [[package]] name = "jinja2" @@ -547,8 +547,8 @@ python-versions = "~=3.6" [package.dependencies] attrs = ">=21.2,<22.0" cattrs = [ - {version = ">=1.0.0,<1.1.0", markers = "python_version < \"3.7\""}, {version = ">=1.8,<22.2", markers = "python_version >= \"3.7\""}, + {version = ">=1.0.0,<1.1.0", markers = "python_version < \"3.7\""}, ] importlib-resources = {version = "*", markers = "python_version < \"3.7\""} python-dateutil = "*" @@ -592,7 +592,7 @@ optional = false python-versions = ">=3.6" [package.extras] -testing = ["coverage", "pyyaml"] +testing = ["pyyaml", "coverage"] [[package]] name = "markupsafe" @@ -633,8 +633,8 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] -test = ["coverage", "flake8 (>=3.0)"] +test = ["flake8 (>=3.0)", "coverage"] +dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] [[package]] name = "mkdocs" @@ -870,8 +870,8 @@ optional = false python-versions = ">=3.6" [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)"] +test = ["pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "pytest (>=6)", "appdirs (==1.4.4)"] +docs = ["sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)", "Sphinx (>=4)"] [[package]] name = "pluggy" @@ -885,8 +885,8 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["pytest-benchmark", "pytest"] +dev = ["tox", "pre-commit"] [[package]] name = "publication" @@ -922,7 +922,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydantic" -version = "1.9.1" +version = "1.9.2" description = "Data validation and settings management using python type hints" category = "main" optional = true @@ -933,8 +933,8 @@ dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} typing-extensions = ">=3.7.4.3" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +dotenv = ["python-dotenv (>=0.10.4)"] [[package]] name = "pyflakes" @@ -994,7 +994,7 @@ py = ">=1.8.2" tomli = ">=1.0.0" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["xmlschema", "requests", "pygments (>=2.7.2)", "nose", "mock", "hypothesis (>=3.56)", "argcomplete"] [[package]] name = "pytest-asyncio" @@ -1008,7 +1008,7 @@ python-versions = ">= 3.6" pytest = ">=5.4.0" [package.extras] -testing = ["coverage", "hypothesis (>=5.7.1)"] +testing = ["hypothesis (>=5.7.1)", "coverage"] [[package]] name = "pytest-benchmark" @@ -1023,9 +1023,9 @@ py-cpuinfo = "*" pytest = ">=3.8" [package.extras] -aspect = ["aspectlib"] +histogram = ["pygaljs", "pygal"] elasticsearch = ["elasticsearch"] -histogram = ["pygal", "pygaljs"] +aspect = ["aspectlib"] [[package]] name = "pytest-cov" @@ -1040,7 +1040,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] [[package]] name = "pytest-forked" @@ -1152,8 +1152,8 @@ idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +socks = ["win-inet-pton", "PySocks (>=1.5.6,!=1.5.7)"] [[package]] name = "retry" @@ -1284,9 +1284,9 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <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)"] +secure = ["ipaddress", "certifi", "idna (>=2.0.0)", "cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] +brotli = ["brotlipy (>=0.6.0)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] [[package]] name = "watchdog" @@ -1338,7 +1338,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "1ca9e5592ed8552f431e2984e71a2a3e7f52e4125efba36ea2d336bffcd3d437" +content-hash = "8ff9eceb9c59b4269ebd8887c33f5a99fda0b6718439404a828f2de9e7dd421a" [metadata.files] atomicwrites = [ @@ -1574,12 +1574,28 @@ markdown = [ {file = "Markdown-3.3.5.tar.gz", hash = "sha256:26e9546bfbcde5fcd072bd8f612c9c1b6e2677cb8aadbdf65206674f46dde069"}, ] 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"}, @@ -1588,14 +1604,27 @@ markupsafe = [ {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"}, @@ -1605,6 +1634,12 @@ markupsafe = [ {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"}, @@ -1714,6 +1749,7 @@ pbr = [ {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, ] pdoc3 = [ + {file = "pdoc3-0.10.0-py3-none-any.whl", hash = "sha256:ba45d1ada1bd987427d2bf5cdec30b2631a3ff5fb01f6d0e77648a572ce6028b"}, {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, ] platformdirs = [ @@ -1740,41 +1776,41 @@ pycodestyle = [ {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pydantic = [ - {file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"}, - {file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"}, - {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"}, - {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"}, - {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"}, - {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"}, - {file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"}, - {file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"}, - {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"}, - {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"}, - {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"}, - {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"}, - {file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"}, - {file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"}, - {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"}, - {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"}, - {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"}, - {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"}, - {file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"}, - {file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"}, - {file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"}, - {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"}, - {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"}, - {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"}, - {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"}, - {file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"}, - {file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"}, - {file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"}, - {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"}, - {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"}, - {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"}, - {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"}, - {file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"}, - {file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"}, - {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"}, + {file = "pydantic-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e"}, + {file = "pydantic-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08"}, + {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c"}, + {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131"}, + {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"}, + {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567"}, + {file = "pydantic-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044"}, + {file = "pydantic-1.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555"}, + {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84"}, + {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f"}, + {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb"}, + {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b"}, + {file = "pydantic-1.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001"}, + {file = "pydantic-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56"}, + {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4"}, + {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f"}, + {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979"}, + {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d"}, + {file = "pydantic-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7"}, + {file = "pydantic-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3"}, + {file = "pydantic-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa"}, + {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3"}, + {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0"}, + {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8"}, + {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8"}, + {file = "pydantic-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326"}, + {file = "pydantic-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801"}, + {file = "pydantic-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44"}, + {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747"}, + {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453"}, + {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb"}, + {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15"}, + {file = "pydantic-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55"}, + {file = "pydantic-1.9.2-py3-none-any.whl", hash = "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e"}, + {file = "pydantic-1.9.2.tar.gz", hash = "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d"}, ] pyflakes = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, @@ -2014,7 +2050,10 @@ typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] -urllib3 = [] +urllib3 = [ + {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, + {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, +] watchdog = [ {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, From d36b0d96afb6d6cf9aba6b9db0eeb0c4a19830ef Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 12 Aug 2022 16:14:13 +0000 Subject: [PATCH 32/59] update changelog with latest changes --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9823a915238..39bc6e823b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,12 +27,13 @@ ## Maintenance +* **ci:** update changelog with latest changes * **ci:** remove conventional changelog commit to reduce noise * **ci:** remove area/utilities conflicting label * **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes -* **ci:** update changelog with latest changes +* **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) * **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) * **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) From 83fb6ccd12aed69fbdf4a2c99911defbe9497130 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Fri, 12 Aug 2022 19:05:03 +0200 Subject: [PATCH 33/59] fix(ci): del flake8 direct dep over py3.6 conflicts and docs failure --- poetry.lock | 251 +++++++++++++++++++++---------------------------- pyproject.toml | 3 +- 2 files changed, 109 insertions(+), 145 deletions(-) diff --git a/poetry.lock b/poetry.lock index 08dc7dc6dd7..c178b0c694c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,10 +15,10 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -tests_no_zope = ["cloudpickle", "pytest-mypy-plugins", "mypy", "six", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] -tests = ["cloudpickle", "zope.interface", "pytest-mypy-plugins", "mypy", "six", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] -docs = ["sphinx-notfound-page", "zope.interface", "sphinx", "furo"] -dev = ["cloudpickle", "pre-commit", "sphinx-notfound-page", "sphinx", "furo", "zope.interface", "pytest-mypy-plugins", "mypy", "six", "pytest (>=4.3.0)", "pympler", "hypothesis", "coverage[toml] (>=5.0.2)"] +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 = "aws-cdk-lib" @@ -76,16 +76,16 @@ platformdirs = ">=2" tomli = ">=0.2.6,<2.0.0" typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = [ - {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, ] [package.extras] -uvloop = ["uvloop (>=0.15.2)"] -python2 = ["typed-ast (>=1.4.3)"] -jupyter = ["tokenize-rt (>=3.2.0)", "ipython (>=7.8.0)"] -d = ["aiohttp (>=3.7.4)"] colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" @@ -131,7 +131,7 @@ python-versions = "*" attrs = ">=17.3" [package.extras] -dev = ["pendulum", "hypothesis", "pytest", "sphinx", "coverage", "tox", "flake8", "watchdog", "wheel", "bumpversion"] +dev = ["bumpversion", "wheel", "watchdog", "flake8", "tox", "coverage", "sphinx", "pytest", "hypothesis", "pendulum"] [[package]] name = "cattrs" @@ -236,12 +236,12 @@ optional = true python-versions = ">=3.6,<4.0" [package.extras] -wmi = ["wmi (>=1.5.1,<2.0.0)"] -trio = ["trio (>=0.14,<0.20)"] -curio = ["sniffio (>=1.1,<2.0)", "curio (>=1.2,<2.0)"] -doh = ["requests-toolbelt (>=0.9.1,<0.10.0)", "requests (>=2.23.0,<3.0.0)", "httpx (>=0.21.1)", "h2 (>=4.1.0)"] -idna = ["idna (>=2.1,<4.0)"] dnssec = ["cryptography (>=2.6,<37.0)"] +curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.20)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "email-validator" @@ -298,17 +298,17 @@ devel = ["colorama", "jsonschema", "json-spec", "pylint", "pytest", "pytest-benc [[package]] name = "flake8" -version = "4.0.1" +version = "3.9.2" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "flake8-bugbear" @@ -323,7 +323,7 @@ attrs = ">=19.2.0" flake8 = ">=3.0.0" [package.extras] -dev = ["pre-commit", "hypothesmith (>=0.2)", "hypothesis", "coverage"] +dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] [[package]] name = "flake8-builtins" @@ -337,7 +337,7 @@ python-versions = "*" flake8 = "*" [package.extras] -test = ["pytest-cov", "pytest", "mock", "coveralls", "coverage"] +test = ["coverage", "coveralls", "mock", "pytest", "pytest-cov"] [[package]] name = "flake8-comprehensions" @@ -366,7 +366,7 @@ six = "*" [[package]] name = "flake8-eradicate" -version = "1.2.1" +version = "1.3.0" description = "Flake8 plugin to find commented out code" category = "dev" optional = false @@ -375,7 +375,7 @@ python-versions = ">=3.6,<4.0" [package.dependencies] attrs = "*" eradicate = ">=2.0,<3.0" -flake8 = ">=3.5,<5" +flake8 = ">=3.5,<6" [[package]] name = "flake8-fixme" @@ -428,7 +428,7 @@ python-versions = "*" python-dateutil = ">=2.8.1" [package.extras] -dev = ["wheel", "flake8", "markdown", "twine"] +dev = ["twine", "markdown", "flake8", "wheel"] [[package]] name = "gitdb" @@ -463,19 +463,20 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "4.2.0" +version = "4.12.0" description = "Read metadata from Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -testing = ["importlib-resources (>=1.3)", "pytest-mypy", "pytest-black (>=0.3.7)", "flufl.flake8", "pyfakefs", "pep517", "packaging", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=4.6)"] -docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "importlib-resources" @@ -489,8 +490,8 @@ python-versions = ">=3.6" zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] -testing = ["pytest-mypy", "pytest-black (>=0.3.7)", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=6)"] -docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] +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)", "pytest-black (>=0.3.7)", "pytest-mypy"] [[package]] name = "iniconfig" @@ -509,10 +510,10 @@ optional = false python-versions = ">=3.6.1,<4.0" [package.extras] -plugins = ["setuptools"] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] -requirements_deprecated_finder = ["pip-api", "pipreqs"] -pipfile_deprecated_finder = ["requirementslib", "pipreqs"] +plugins = ["setuptools"] [[package]] name = "jinja2" @@ -547,8 +548,8 @@ python-versions = "~=3.6" [package.dependencies] attrs = ">=21.2,<22.0" cattrs = [ - {version = ">=1.8,<22.2", markers = "python_version >= \"3.7\""}, {version = ">=1.0.0,<1.1.0", markers = "python_version < \"3.7\""}, + {version = ">=1.8,<22.2", markers = "python_version >= \"3.7\""}, ] importlib-resources = {version = "*", markers = "python_version < \"3.7\""} python-dateutil = "*" @@ -585,14 +586,17 @@ restructuredText = ["rst2ansi"] [[package]] name = "markdown" -version = "3.3.5" +version = "3.3.7" description = "Python implementation of Markdown." category = "dev" optional = false python-versions = ">=3.6" +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + [package.extras] -testing = ["pyyaml", "coverage"] +testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" @@ -633,12 +637,12 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -test = ["flake8 (>=3.0)", "coverage"] -dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] +dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] +test = ["coverage", "flake8 (>=3.0)"] [[package]] name = "mkdocs" -version = "1.2.4" +version = "1.3.1" description = "Project documentation with Markdown." category = "dev" optional = false @@ -647,9 +651,9 @@ python-versions = ">=3.6" [package.dependencies] click = ">=3.3" ghp-import = ">=1.0" -importlib-metadata = ">=3.10" -Jinja2 = ">=2.10.1" -Markdown = ">=3.2.1" +importlib-metadata = ">=4.3" +Jinja2 = ">=2.10.2" +Markdown = ">=3.2.1,<3.4" mergedeep = ">=1.3.4" packaging = ">=20.5" PyYAML = ">=3.10" @@ -674,19 +678,19 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-material" -version = "8.2.7" -description = "A Material Design theme for MkDocs" +version = "8.3.9" +description = "Documentation that simply works" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -jinja2 = ">=2.11.1,<3.1" +jinja2 = ">=3.0.2" markdown = ">=3.2" -mkdocs = ">=1.2.3" -mkdocs-material-extensions = ">=1.0" -pygments = ">=2.10" -pymdown-extensions = ">=9.0" +mkdocs = ">=1.3.0" +mkdocs-material-extensions = ">=1.0.3" +pygments = ">=2.12" +pymdown-extensions = ">=9.4" [[package]] name = "mkdocs-material-extensions" @@ -711,9 +715,9 @@ typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=3.10" [package.extras] -reports = ["lxml"] -python2 = ["typed-ast (>=1.4.0,<2)"] dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] [[package]] name = "mypy-boto3-appconfig" @@ -843,7 +847,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pbr" -version = "5.9.0" +version = "5.10.0" description = "Python Build Reasonableness" category = "dev" optional = false @@ -870,8 +874,8 @@ optional = false python-versions = ">=3.6" [package.extras] -test = ["pytest-mock (>=3.6)", "pytest-cov (>=2.7)", "pytest (>=6)", "appdirs (==1.4.4)"] -docs = ["sphinx-autodoc-typehints (>=1.12)", "proselint (>=0.10.2)", "furo (>=2021.7.5b38)", "Sphinx (>=4)"] +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" @@ -885,8 +889,8 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] -testing = ["pytest-benchmark", "pytest"] -dev = ["tox", "pre-commit"] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "publication" @@ -914,11 +918,11 @@ python-versions = "*" [[package]] name = "pycodestyle" -version = "2.8.0" +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.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pydantic" @@ -938,7 +942,7 @@ dotenv = ["python-dotenv (>=0.10.4)"] [[package]] name = "pyflakes" -version = "2.4.0" +version = "2.3.1" description = "passive checker of Python programs" category = "dev" optional = false @@ -954,14 +958,14 @@ python-versions = ">=3.6" [[package]] name = "pymdown-extensions" -version = "9.1" +version = "9.5" description = "Extension pack for Python Markdown." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -Markdown = ">=3.2" +markdown = ">=3.2" [[package]] name = "pyparsing" @@ -972,7 +976,7 @@ optional = false python-versions = ">=3.6" [package.extras] -diagrams = ["railroad-diagrams", "jinja2"] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" @@ -994,7 +998,7 @@ py = ">=1.8.2" tomli = ">=1.0.0" [package.extras] -testing = ["xmlschema", "requests", "pygments (>=2.7.2)", "nose", "mock", "hypothesis (>=3.56)", "argcomplete"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-asyncio" @@ -1008,7 +1012,7 @@ python-versions = ">= 3.6" pytest = ">=5.4.0" [package.extras] -testing = ["hypothesis (>=5.7.1)", "coverage"] +testing = ["coverage", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-benchmark" @@ -1023,9 +1027,9 @@ py-cpuinfo = "*" pytest = ">=3.8" [package.extras] -histogram = ["pygaljs", "pygal"] -elasticsearch = ["elasticsearch"] aspect = ["aspectlib"] +elasticsearch = ["elasticsearch"] +histogram = ["pygal", "pygaljs"] [[package]] name = "pytest-cov" @@ -1040,7 +1044,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-forked" @@ -1066,7 +1070,7 @@ python-versions = ">=3.6" pytest = ">=5.0" [package.extras] -dev = ["pytest-asyncio", "tox", "pre-commit"] +dev = ["pre-commit", "tox", "pytest-asyncio"] [[package]] name = "pytest-xdist" @@ -1082,9 +1086,9 @@ pytest = ">=6.2.0" pytest-forked = "*" [package.extras] -testing = ["filelock"] -setproctitle = ["setproctitle"] psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] [[package]] name = "python-dateutil" @@ -1152,8 +1156,8 @@ idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] -socks = ["win-inet-pton", "PySocks (>=1.5.6,!=1.5.7)"] [[package]] name = "retry" @@ -1284,9 +1288,9 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <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)"] -secure = ["ipaddress", "certifi", "idna (>=2.0.0)", "cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] -brotli = ["brotlipy (>=0.6.0)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] [[package]] name = "watchdog" @@ -1322,15 +1326,15 @@ requests = ">=2.0,<3.0" [[package]] name = "zipp" -version = "3.6.0" +version = "3.8.1" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -testing = ["pytest-mypy", "pytest-black (>=0.3.7)", "func-timeout", "jaraco.itertools", "pytest-enabler (>=1.0.1)", "pytest-cov", "pytest-flake8", "pytest-checkdocs (>=2.4)", "pytest (>=4.6)"] -docs = ["rst.linker (>=1.9)", "jaraco.packaging (>=8.2)", "sphinx"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [extras] pydantic = ["pydantic", "email-validator"] @@ -1338,7 +1342,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "8ff9eceb9c59b4269ebd8887c33f5a99fda0b6718439404a828f2de9e7dd421a" +content-hash = "f9e26c18e24673e05314f2664f1442157e34b70ba4bdb9f912d149df96003eb9" [metadata.files] atomicwrites = [ @@ -1480,8 +1484,8 @@ fastjsonschema = [ {file = "fastjsonschema-2.16.1.tar.gz", hash = "sha256:d6fa3ffbe719768d70e298b9fb847484e2bdfdb7241ed052b8d57a9294a8c334"}, ] flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] flake8-bugbear = [ {file = "flake8-bugbear-22.7.1.tar.gz", hash = "sha256:e450976a07e4f9d6c043d4f72b17ec1baf717fe37f7997009c8ae58064f88305"}, @@ -1500,8 +1504,8 @@ flake8-debugger = [ {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"}, ] flake8-eradicate = [ - {file = "flake8-eradicate-1.2.1.tar.gz", hash = "sha256:e486f8ab7e2dba3667223688e9239158fbf4ecaa88125e2283bcda81171412b7"}, - {file = "flake8_eradicate-1.2.1-py3-none-any.whl", hash = "sha256:00d77faefb64cef18b3c1b48a004c3a2ad663aa3cf85650f422437d25ece6441"}, + {file = "flake8-eradicate-1.3.0.tar.gz", hash = "sha256:e4c98f00d17dc8653e3388cac2624cd81e9735de2fd4a8dcf99029633ebd7a63"}, + {file = "flake8_eradicate-1.3.0-py3-none-any.whl", hash = "sha256:85a71e0c5f4e07f7c6c5fec520483561fd6bd295417d622855bdeade99242e3d"}, ] flake8-fixme = [ {file = "flake8-fixme-1.1.1.tar.gz", hash = "sha256:50cade07d27a4c30d4f12351478df87339e67640c83041b664724bda6d16f33a"}, @@ -1534,8 +1538,8 @@ idna = [ {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] importlib-resources = [ {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, @@ -1570,32 +1574,16 @@ mando = [ {file = "mando-0.6.4.tar.gz", hash = "sha256:79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"}, ] markdown = [ - {file = "Markdown-3.3.5-py3-none-any.whl", hash = "sha256:0d2d09f75cb8d1ffc6770c65c61770b23a61708101f47bda416a002a0edbc480"}, - {file = "Markdown-3.3.5.tar.gz", hash = "sha256:26e9546bfbcde5fcd072bd8f612c9c1b6e2677cb8aadbdf65206674f46dde069"}, + {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, + {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, ] 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"}, @@ -1604,27 +1592,14 @@ markupsafe = [ {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"}, @@ -1634,12 +1609,6 @@ markupsafe = [ {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"}, @@ -1657,15 +1626,15 @@ mike = [ {file = "mike-0.6.0.tar.gz", hash = "sha256:6d6239de2a60d733da2f34617e9b9a14c4b5437423b47e524f14dc96d6ce5f2f"}, ] mkdocs = [ - {file = "mkdocs-1.2.4-py3-none-any.whl", hash = "sha256:f108e7ab5a7ed3e30826dbf82f37638f0d90d11161644616cc4f01a1e2ab3940"}, - {file = "mkdocs-1.2.4.tar.gz", hash = "sha256:8e7970a26183487fe2a1041940c6fd03aa0dbe5549e50c3e7194f565cb3c678a"}, + {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, + {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, ] mkdocs-git-revision-date-plugin = [ {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, ] mkdocs-material = [ - {file = "mkdocs-material-8.2.7.tar.gz", hash = "sha256:3314d94ccc11481b1a3aa4f7babb4fb2bc47daa2fa8ace2463665952116f409b"}, - {file = "mkdocs_material-8.2.7-py2.py3-none-any.whl", hash = "sha256:20c13aa0a54841e1f1c080edb0e3573407884e4abea51ee25573061189bec83e"}, + {file = "mkdocs-material-8.3.9.tar.gz", hash = "sha256:dc82b667d2a83f0de581b46a6d0949732ab77e7638b87ea35b770b33bc02e75a"}, + {file = "mkdocs_material-8.3.9-py2.py3-none-any.whl", hash = "sha256:263f2721f3abe533b61f7c8bed435a0462620912742c919821ac2d698b4bfe67"}, ] mkdocs-material-extensions = [ {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, @@ -1745,11 +1714,10 @@ pathspec = [ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pbr = [ - {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"}, - {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, + {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"}, + {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"}, ] pdoc3 = [ - {file = "pdoc3-0.10.0-py3-none-any.whl", hash = "sha256:ba45d1ada1bd987427d2bf5cdec30b2631a3ff5fb01f6d0e77648a572ce6028b"}, {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, ] platformdirs = [ @@ -1772,8 +1740,8 @@ py-cpuinfo = [ {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, ] pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pydantic = [ {file = "pydantic-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e"}, @@ -1813,16 +1781,16 @@ pydantic = [ {file = "pydantic-1.9.2.tar.gz", hash = "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d"}, ] pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pymdown-extensions = [ - {file = "pymdown-extensions-9.1.tar.gz", hash = "sha256:74247f2c80f1d9e3c7242abe1c16317da36c6f26c7ad4b8a7f457f0ec20f0365"}, - {file = "pymdown_extensions-9.1-py3-none-any.whl", hash = "sha256:b03e66f91f33af4a6e7a0e20c740313522995f69a03d86316b1449766c473d0e"}, + {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, + {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, ] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, @@ -2050,10 +2018,7 @@ typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] -urllib3 = [ - {file = "urllib3-1.26.11-py2.py3-none-any.whl", hash = "sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc"}, - {file = "urllib3-1.26.11.tar.gz", hash = "sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a"}, -] +urllib3 = [] watchdog = [ {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, @@ -2152,6 +2117,6 @@ xenon = [ {file = "xenon-0.9.0.tar.gz", hash = "sha256:d2b9cb6c6260f771a432c1e588e51fddb17858f88f73ef641e7532f7a5f58fb8"}, ] zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, ] diff --git a/pyproject.toml b/pyproject.toml index 7613ffc0170..481652d9c30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ email-validator = {version = "*", optional = true } coverage = {extras = ["toml"], version = "^6.2"} pytest = "^7.0.1" black = "^21.12b0" -flake8 = "^4.0.1" flake8-builtins = "^1.5.3" flake8-comprehensions = "^3.7.0" flake8-debugger = "^4.0.0" @@ -53,7 +52,6 @@ flake8-bugbear = "^22.7.1" mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^0.6.0" mypy = "^0.971" -mkdocs-material = "^8.2.7" mypy-boto3-secretsmanager = "^1.24.11" mypy-boto3-ssm = "^1.24.0" mypy-boto3-appconfig = "^1.24.29" @@ -70,6 +68,7 @@ mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" } types-requests = "^2.28.8" typing-extensions = { version = "^4.3.0", python = ">=3.7" } python-snappy = "^0.6.1" +mkdocs-material = { version = "^8.3.9", python = ">=3.7" } [tool.poetry.extras] pydantic = ["pydantic", "email-validator"] From f226c2ce754e3f430cf9d57e285f0312e168bd9f Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 12 Aug 2022 17:11:24 +0000 Subject: [PATCH 34/59] update changelog with latest changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39bc6e823b0..50344f53179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Bug Fixes +* **ci:** del flake8 direct dep over py3.6 conflicts and docs failure * **ci:** move from pip-tools to poetry on layers reusable workflow * **ci:** move from pip-tools to poetry on layers to fix conflicts * **ci:** typo and bust gh actions cache From 4cccc69639e4f09eeca2867d01076beac4ef5d32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Aug 2022 10:55:11 +0200 Subject: [PATCH 35/59] chore(deps): bump release-drafter/release-drafter from 5.20.0 to 5.20.1 (#1458) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ruben Fonseca --- .github/workflows/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 54d8c5ea723..e1d0ddce268 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -10,6 +10,6 @@ jobs: update_release_draft: runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@ac463ffd9cc4c6ad5682af93dc3e3591c4657ee3 # v5.20.0 + - uses: release-drafter/release-drafter@06a49bf28488e030d35ca2ac6dbf7f408a481779 # v5.20.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 1d7a4ab854a16d6b366e16643d90dfb1fa62a219 Mon Sep 17 00:00:00 2001 From: Ran Isenberg <60175085+ran-isenberg@users.noreply.github.com> Date: Thu, 18 Aug 2022 11:57:00 +0300 Subject: [PATCH 36/59] feat(parser): add support for Lambda Function URL (#1442) Co-authored-by: Ruben Fonseca --- .../utilities/parser/envelopes/__init__.py | 2 + .../parser/envelopes/lambda_function_url.py | 32 +++++ .../utilities/parser/models/__init__.py | 2 + .../utilities/parser/models/apigwv2.py | 2 +- .../parser/models/lambda_function_url.py | 18 +++ docs/utilities/parser.md | 3 + tests/functional/parser/schemas.py | 5 + .../parser/test_lambda_function_url.py | 128 ++++++++++++++++++ 8 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 aws_lambda_powertools/utilities/parser/envelopes/lambda_function_url.py create mode 100644 aws_lambda_powertools/utilities/parser/models/lambda_function_url.py create mode 100644 tests/functional/parser/test_lambda_function_url.py diff --git a/aws_lambda_powertools/utilities/parser/envelopes/__init__.py b/aws_lambda_powertools/utilities/parser/envelopes/__init__.py index 1b118d28117..7d42fd81ad6 100644 --- a/aws_lambda_powertools/utilities/parser/envelopes/__init__.py +++ b/aws_lambda_powertools/utilities/parser/envelopes/__init__.py @@ -5,6 +5,7 @@ from .dynamodb import DynamoDBStreamEnvelope from .event_bridge import EventBridgeEnvelope from .kinesis import KinesisDataStreamEnvelope +from .lambda_function_url import LambdaFunctionUrlEnvelope from .sns import SnsEnvelope, SnsSqsEnvelope from .sqs import SqsEnvelope @@ -15,6 +16,7 @@ "DynamoDBStreamEnvelope", "EventBridgeEnvelope", "KinesisDataStreamEnvelope", + "LambdaFunctionUrlEnvelope", "SnsEnvelope", "SnsSqsEnvelope", "SqsEnvelope", diff --git a/aws_lambda_powertools/utilities/parser/envelopes/lambda_function_url.py b/aws_lambda_powertools/utilities/parser/envelopes/lambda_function_url.py new file mode 100644 index 00000000000..e54fb081b65 --- /dev/null +++ b/aws_lambda_powertools/utilities/parser/envelopes/lambda_function_url.py @@ -0,0 +1,32 @@ +import logging +from typing import Any, Dict, Optional, Type, Union + +from ..models import LambdaFunctionUrlModel +from ..types import Model +from .base import BaseEnvelope + +logger = logging.getLogger(__name__) + + +class LambdaFunctionUrlEnvelope(BaseEnvelope): + """Lambda function URL envelope to extract data within body key""" + + def parse(self, data: Optional[Union[Dict[str, Any], Any]], model: Type[Model]) -> Optional[Model]: + """Parses data found with model provided + + Parameters + ---------- + data : Dict + Lambda event to be parsed + model : Type[Model] + Data model provided to parse after extracting data using envelope + + Returns + ------- + Any + Parsed detail payload with model provided + """ + logger.debug(f"Parsing incoming data with Lambda function URL model {LambdaFunctionUrlModel}") + parsed_envelope: LambdaFunctionUrlModel = LambdaFunctionUrlModel.parse_obj(data) + logger.debug(f"Parsing event payload in `detail` with {model}") + return self._parse(data=parsed_envelope.body, model=model) diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index e3fb50a2d5d..11ab6501fa9 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -18,6 +18,7 @@ from .dynamodb import DynamoDBStreamChangedRecordModel, DynamoDBStreamModel, DynamoDBStreamRecordModel from .event_bridge import EventBridgeModel from .kinesis import KinesisDataStreamModel, KinesisDataStreamRecord, KinesisDataStreamRecordPayload +from .lambda_function_url import LambdaFunctionUrlModel from .s3 import S3Model, S3RecordModel from .s3_object_event import ( S3ObjectConfiguration, @@ -66,6 +67,7 @@ "KinesisDataStreamModel", "KinesisDataStreamRecord", "KinesisDataStreamRecordPayload", + "LambdaFunctionUrlModel", "S3Model", "S3RecordModel", "S3ObjectLambdaEvent", diff --git a/aws_lambda_powertools/utilities/parser/models/apigwv2.py b/aws_lambda_powertools/utilities/parser/models/apigwv2.py index f97dad3bcb0..cb1f830bb47 100644 --- a/aws_lambda_powertools/utilities/parser/models/apigwv2.py +++ b/aws_lambda_powertools/utilities/parser/models/apigwv2.py @@ -20,7 +20,7 @@ class RequestContextV2AuthorizerIam(BaseModel): principalOrgId: Optional[str] userArn: Optional[str] userId: Optional[str] - cognitoIdentity: RequestContextV2AuthorizerIamCognito + cognitoIdentity: Optional[RequestContextV2AuthorizerIamCognito] class RequestContextV2AuthorizerJwt(BaseModel): diff --git a/aws_lambda_powertools/utilities/parser/models/lambda_function_url.py b/aws_lambda_powertools/utilities/parser/models/lambda_function_url.py new file mode 100644 index 00000000000..2088ab9fa04 --- /dev/null +++ b/aws_lambda_powertools/utilities/parser/models/lambda_function_url.py @@ -0,0 +1,18 @@ +from aws_lambda_powertools.utilities.parser.models import APIGatewayProxyEventV2Model + + +class LambdaFunctionUrlModel(APIGatewayProxyEventV2Model): + """AWS Lambda Function URL model + + Notes: + ----- + Lambda Function URL follows the API Gateway HTTP APIs Payload Format Version 2.0. + + Keys related to API Gateway features not available in Function URL use a sentinel value (e.g.`routeKey`, `stage`). + + Documentation: + - https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html + - https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads + """ + + pass diff --git a/docs/utilities/parser.md b/docs/utilities/parser.md index cb69cf9699b..97b005a9fb5 100644 --- a/docs/utilities/parser.md +++ b/docs/utilities/parser.md @@ -2,6 +2,7 @@ title: Parser description: Utility --- + This utility provides data parsing and deep validation using [Pydantic](https://pydantic-docs.helpmanual.io/). @@ -166,6 +167,7 @@ Parser comes with the following built-in models: | **SnsModel** | Lambda Event Source payload for Amazon Simple Notification Service | | **APIGatewayProxyEventModel** | Lambda Event Source payload for Amazon API Gateway | | **APIGatewayProxyEventV2Model** | Lambda Event Source payload for Amazon API Gateway v2 payload | +| **LambdaFunctionUrlModel** | Lambda Event Source payload for Lambda Function URL payload | ### extending built-in models @@ -305,6 +307,7 @@ Parser comes with the following built-in envelopes, where `Model` in the return | **SnsSqsEnvelope** | 1. Parses data using `SqsModel`.
2. Parses SNS records in `body` key using `SnsNotificationModel`.
3. Parses data in `Message` key using your model and return them in a list. | `List[Model]` | | **ApiGatewayEnvelope** | 1. Parses data using `APIGatewayProxyEventModel`.
2. Parses `body` key using your model and returns it. | `Model` | | **ApiGatewayV2Envelope** | 1. Parses data using `APIGatewayProxyEventV2Model`.
2. Parses `body` key using your model and returns it. | `Model` | +| **LambdaFunctionUrlEnvelope** | 1. Parses data using `LambdaFunctionUrlModel`.
2. Parses `body` key using your model and returns it. | `Model` | ### Bringing your own envelope diff --git a/tests/functional/parser/schemas.py b/tests/functional/parser/schemas.py index 8ff56f703a7..79a74f8eb53 100644 --- a/tests/functional/parser/schemas.py +++ b/tests/functional/parser/schemas.py @@ -86,3 +86,8 @@ class MyCloudWatchBusiness(BaseModel): class MyApiGatewayBusiness(BaseModel): message: str username: str + + +class MyALambdaFuncUrlBusiness(BaseModel): + message: str + username: str diff --git a/tests/functional/parser/test_lambda_function_url.py b/tests/functional/parser/test_lambda_function_url.py new file mode 100644 index 00000000000..a63a4e25884 --- /dev/null +++ b/tests/functional/parser/test_lambda_function_url.py @@ -0,0 +1,128 @@ +from aws_lambda_powertools.utilities.parser import envelopes, event_parser +from aws_lambda_powertools.utilities.parser.models import LambdaFunctionUrlModel +from aws_lambda_powertools.utilities.typing import LambdaContext +from tests.functional.parser.schemas import MyALambdaFuncUrlBusiness +from tests.functional.utils import load_event + + +@event_parser(model=MyALambdaFuncUrlBusiness, envelope=envelopes.LambdaFunctionUrlEnvelope) +def handle_lambda_func_url_with_envelope(event: MyALambdaFuncUrlBusiness, _: LambdaContext): + assert event.message == "Hello" + assert event.username == "Ran" + + +@event_parser(model=LambdaFunctionUrlModel) +def handle_lambda_func_url_event(event: LambdaFunctionUrlModel, _: LambdaContext): + return event + + +def test_lambda_func_url_event_with_envelope(): + event = load_event("lambdaFunctionUrlEvent.json") + event["body"] = '{"message": "Hello", "username": "Ran"}' + handle_lambda_func_url_with_envelope(event, LambdaContext()) + + +def test_lambda_function_url_event(): + json_event = load_event("lambdaFunctionUrlEvent.json") + event: LambdaFunctionUrlModel = handle_lambda_func_url_event(json_event, LambdaContext()) + + assert event.version == "2.0" + assert event.routeKey == "$default" + + assert event.rawQueryString == "" + + assert event.cookies is None + + headers = event.headers + assert len(headers) == 20 + + assert event.queryStringParameters is None + + assert event.isBase64Encoded is False + assert event.body is None + assert event.pathParameters is None + assert event.stageVariables is None + + request_context = event.requestContext + + assert request_context.accountId == "anonymous" + assert request_context.apiId is not None + assert request_context.domainName == ".lambda-url.us-east-1.on.aws" + assert request_context.domainPrefix == "" + assert request_context.requestId == "id" + assert request_context.routeKey == "$default" + assert request_context.stage == "$default" + assert request_context.time is not None + convert_time = int(round(request_context.timeEpoch.timestamp() * 1000)) + assert convert_time == 1659687279885 + assert request_context.authorizer is None + + http = request_context.http + assert http.method == "GET" + assert http.path == "/" + assert http.protocol == "HTTP/1.1" + assert str(http.sourceIp) == "123.123.123.123/32" + assert http.userAgent == "agent" + + assert request_context.authorizer is None + + +def test_lambda_function_url_event_iam(): + json_event = load_event("lambdaFunctionUrlIAMEvent.json") + event: LambdaFunctionUrlModel = handle_lambda_func_url_event(json_event, LambdaContext()) + + assert event.version == "2.0" + assert event.routeKey == "$default" + + assert event.rawQueryString == "parameter1=value1¶meter1=value2¶meter2=value" + + cookies = event.cookies + assert len(cookies) == 2 + assert cookies[0] == "cookie1" + + headers = event.headers + assert len(headers) == 2 + + query_string_parameters = event.queryStringParameters + assert len(query_string_parameters) == 2 + assert query_string_parameters.get("parameter2") == "value" + + assert event.isBase64Encoded is False + assert event.body == "Hello from client!" + assert event.pathParameters is None + assert event.stageVariables is None + + request_context = event.requestContext + + assert request_context.accountId == "123456789012" + assert request_context.apiId is not None + assert request_context.domainName == ".lambda-url.us-west-2.on.aws" + assert request_context.domainPrefix == "" + assert request_context.requestId == "id" + assert request_context.routeKey == "$default" + assert request_context.stage == "$default" + assert request_context.time is not None + convert_time = int(round(request_context.timeEpoch.timestamp() * 1000)) + assert convert_time == 1583348638390 + + http = request_context.http + assert http.method == "POST" + assert http.path == "/my/path" + assert http.protocol == "HTTP/1.1" + assert str(http.sourceIp) == "123.123.123.123/32" + assert http.userAgent == "agent" + + authorizer = request_context.authorizer + assert authorizer is not None + assert authorizer.jwt is None + assert authorizer.lambda_value is None + + iam = authorizer.iam + assert iam is not None + assert iam.accessKey == "AKIA..." + assert iam.accountId == "111122223333" + assert iam.callerId == "AIDA..." + assert iam.cognitoIdentity is None + assert iam.principalOrgId is None + assert iam.userId == "AIDA..." + assert iam.userArn == "arn:aws:iam::111122223333:user/example-user" From 4ab9013a62243f465bd4db5f52dfd198c65fc45d Mon Sep 17 00:00:00 2001 From: Release bot Date: Thu, 18 Aug 2022 08:57:22 +0000 Subject: [PATCH 37/59] update changelog with latest changes --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50344f53179..1eae7ea6dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,14 +26,19 @@ * **layer:** upgrade to 1.27.0 * **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) +## Features + +* **parser:** add support for Lambda Function URL ([#1442](https://github.com/awslabs/aws-lambda-powertools-python/issues/1442)) + ## Maintenance +* **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** remove conventional changelog commit to reduce noise * **ci:** remove area/utilities conflicting label -* **ci:** reduce payload and only send prod notification * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes +* **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/awslabs/aws-lambda-powertools-python/issues/1458)) * **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) * **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) * **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) From 4d0f1a22dd3e70deb8006d5445355ec34910fd89 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Thu, 18 Aug 2022 14:52:28 +0200 Subject: [PATCH 38/59] chore(tests): refactor E2E tracer to ease maintenance, writing tests and parallelization (#1457) --- .flake8 | 3 + tests/e2e/conftest.py | 5 +- tests/e2e/logger/test_logger.py | 17 +- tests/e2e/metrics/conftest.py | 8 +- tests/e2e/metrics/test_metrics.py | 28 +-- tests/e2e/tracer/conftest.py | 25 ++ tests/e2e/tracer/handlers/async_capture.py | 16 ++ tests/e2e/tracer/handlers/basic_handler.py | 23 +- tests/e2e/tracer/infrastructure.py | 17 ++ tests/e2e/tracer/test_tracer.py | 100 +++++--- tests/e2e/utils/asset.py | 31 ++- tests/e2e/utils/data_builder/__init__.py | 13 + tests/e2e/utils/data_builder/common.py | 9 + tests/e2e/utils/data_builder/metrics.py | 116 +++++++++ tests/e2e/utils/data_builder/traces.py | 39 +++ tests/e2e/utils/data_fetcher/__init__.py | 4 + tests/e2e/utils/data_fetcher/common.py | 15 ++ tests/e2e/utils/data_fetcher/logs.py | 39 +++ tests/e2e/utils/data_fetcher/metrics.py | 71 ++++++ tests/e2e/utils/data_fetcher/traces.py | 267 ++++++++++++++++++++ tests/e2e/utils/helpers.py | 276 --------------------- tests/e2e/utils/infrastructure.py | 4 +- tests/e2e/utils/models.py | 31 --- 23 files changed, 763 insertions(+), 394 deletions(-) create mode 100644 tests/e2e/tracer/conftest.py create mode 100644 tests/e2e/tracer/handlers/async_capture.py create mode 100644 tests/e2e/tracer/infrastructure.py create mode 100644 tests/e2e/utils/data_builder/__init__.py create mode 100644 tests/e2e/utils/data_builder/common.py create mode 100644 tests/e2e/utils/data_builder/metrics.py create mode 100644 tests/e2e/utils/data_builder/traces.py create mode 100644 tests/e2e/utils/data_fetcher/__init__.py create mode 100644 tests/e2e/utils/data_fetcher/common.py create mode 100644 tests/e2e/utils/data_fetcher/logs.py create mode 100644 tests/e2e/utils/data_fetcher/metrics.py create mode 100644 tests/e2e/utils/data_fetcher/traces.py delete mode 100644 tests/e2e/utils/helpers.py delete mode 100644 tests/e2e/utils/models.py diff --git a/.flake8 b/.flake8 index 6c0c78fa967..c7d6486ed48 100644 --- a/.flake8 +++ b/.flake8 @@ -3,6 +3,9 @@ exclude = docs, .eggs, setup.py, example, .aws-sam, .git, dist, *.md, *.yaml, ex ignore = E203, E266, W503, BLK100, W291, I004 max-line-length = 120 max-complexity = 15 +per-file-ignores = + tests/e2e/utils/data_builder/__init__.py:F401 + tests/e2e/utils/data_fetcher/__init__.py:F401 [isort] multi_line_output = 3 diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 4be6a26c6a6..3865be4d3e7 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -5,6 +5,8 @@ import boto3 +from tests.e2e.utils import data_fetcher, infrastructure + # We only need typing_extensions for python versions <3.8 if sys.version_info >= (3, 8): from typing import TypedDict @@ -14,7 +16,6 @@ from typing import Dict, Generator, Optional import pytest -from e2e.utils import helpers, infrastructure class LambdaConfig(TypedDict): @@ -61,5 +62,5 @@ def execute_lambda(create_infrastructure) -> InfrastructureOutput: session = boto3.Session() client = session.client("lambda") for _, arn in create_infrastructure.items(): - helpers.trigger_lambda(lambda_arn=arn, client=client) + data_fetcher.get_lambda_response(lambda_arn=arn, client=client) return InfrastructureOutput(arns=create_infrastructure, execution_time=execution_time) diff --git a/tests/e2e/logger/test_logger.py b/tests/e2e/logger/test_logger.py index ea27b93740b..992cf779275 100644 --- a/tests/e2e/logger/test_logger.py +++ b/tests/e2e/logger/test_logger.py @@ -1,7 +1,8 @@ import boto3 import pytest from e2e import conftest -from e2e.utils import helpers + +from tests.e2e.utils import data_fetcher @pytest.fixture(scope="module") @@ -23,7 +24,7 @@ def test_basic_lambda_logs_visible(execute_lambda: conftest.InfrastructureOutput cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert any( @@ -42,7 +43,7 @@ def test_basic_lambda_no_debug_logs_visible( cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert not any( @@ -66,7 +67,7 @@ def test_basic_lambda_contextual_data_logged(execute_lambda: conftest.Infrastruc cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert all(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_keys) @@ -81,7 +82,7 @@ def test_basic_lambda_additional_key_persistence_basic_lambda( cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert any( @@ -100,7 +101,7 @@ def test_basic_lambda_empty_event_logged(execute_lambda: conftest.Infrastructure cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert any(log.message == {} for log in filtered_logs) @@ -122,7 +123,7 @@ def test_no_context_lambda_contextual_data_not_logged(execute_lambda: conftest.I cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert not any(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_missing_keys) @@ -136,7 +137,7 @@ def test_no_context_lambda_event_not_logged(execute_lambda: conftest.Infrastruct cw_client = boto3.client("logs") # WHEN - filtered_logs = helpers.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) # THEN assert not any(log.message == {} for log in filtered_logs) diff --git a/tests/e2e/metrics/conftest.py b/tests/e2e/metrics/conftest.py index 0f4ca8e58c2..18f4564e714 100644 --- a/tests/e2e/metrics/conftest.py +++ b/tests/e2e/metrics/conftest.py @@ -10,8 +10,12 @@ def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.Temp Parameters ---------- - request : fixtures.SubRequest - test fixture containing metadata about test execution + request : pytest.FixtureRequest + pytest request fixture to introspect absolute path to test being executed + tmp_path_factory : pytest.TempPathFactory + pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up + worker_id : str + pytest-xdist worker identification to detect whether parallelization is enabled Yields ------ diff --git a/tests/e2e/metrics/test_metrics.py b/tests/e2e/metrics/test_metrics.py index f1a31bb3c82..01d1ba2fbf1 100644 --- a/tests/e2e/metrics/test_metrics.py +++ b/tests/e2e/metrics/test_metrics.py @@ -2,7 +2,7 @@ import pytest -from tests.e2e.utils import helpers +from tests.e2e.utils import data_builder, data_fetcher @pytest.fixture @@ -30,40 +30,40 @@ def cold_start_fn_arn(infrastructure: dict) -> str: def test_basic_lambda_metric_is_visible(basic_handler_fn: str, basic_handler_fn_arn: str): # GIVEN - metric_name = helpers.build_metric_name() - service = helpers.build_service_name() - dimensions = helpers.build_add_dimensions_input(service=service) - metrics = helpers.build_multiple_add_metric_input(metric_name=metric_name, value=1, quantity=3) + metric_name = data_builder.build_metric_name() + service = data_builder.build_service_name() + dimensions = data_builder.build_add_dimensions_input(service=service) + metrics = data_builder.build_multiple_add_metric_input(metric_name=metric_name, value=1, quantity=3) # WHEN event = json.dumps({"metrics": metrics, "service": service, "namespace": METRIC_NAMESPACE}) - _, execution_time = helpers.trigger_lambda(lambda_arn=basic_handler_fn_arn, payload=event) + _, execution_time = data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=event) - metrics = helpers.get_metrics( + my_metrics = data_fetcher.get_metrics( namespace=METRIC_NAMESPACE, start_date=execution_time, metric_name=metric_name, dimensions=dimensions ) # THEN - metric_data = metrics.get("Values", []) + metric_data = my_metrics.get("Values", []) assert metric_data and metric_data[0] == 3.0 def test_cold_start_metric(cold_start_fn_arn: str, cold_start_fn: str): # GIVEN metric_name = "ColdStart" - service = helpers.build_service_name() - dimensions = helpers.build_add_dimensions_input(function_name=cold_start_fn, service=service) + service = data_builder.build_service_name() + dimensions = data_builder.build_add_dimensions_input(function_name=cold_start_fn, service=service) # WHEN we invoke twice event = json.dumps({"service": service, "namespace": METRIC_NAMESPACE}) - _, execution_time = helpers.trigger_lambda(lambda_arn=cold_start_fn_arn, payload=event) - _, _ = helpers.trigger_lambda(lambda_arn=cold_start_fn_arn, payload=event) + _, execution_time = data_fetcher.get_lambda_response(lambda_arn=cold_start_fn_arn, payload=event) + _, _ = data_fetcher.get_lambda_response(lambda_arn=cold_start_fn_arn, payload=event) - metrics = helpers.get_metrics( + my_metrics = data_fetcher.get_metrics( namespace=METRIC_NAMESPACE, start_date=execution_time, metric_name=metric_name, dimensions=dimensions ) # THEN - metric_data = metrics.get("Values", []) + metric_data = my_metrics.get("Values", []) assert metric_data and metric_data[0] == 1.0 diff --git a/tests/e2e/tracer/conftest.py b/tests/e2e/tracer/conftest.py new file mode 100644 index 00000000000..599d7ab4ca8 --- /dev/null +++ b/tests/e2e/tracer/conftest.py @@ -0,0 +1,25 @@ +import pytest + +from tests.e2e.tracer.infrastructure import TracerStack +from tests.e2e.utils.infrastructure import deploy_once + + +@pytest.fixture(autouse=True, scope="module") +def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): + """Setup and teardown logic for E2E test infrastructure + + Parameters + ---------- + request : pytest.FixtureRequest + pytest request fixture to introspect absolute path to test being executed + tmp_path_factory : pytest.TempPathFactory + pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up + worker_id : str + pytest-xdist worker identification to detect whether parallelization is enabled + + Yields + ------ + Dict[str, str] + CloudFormation Outputs from deployed infrastructure + """ + yield from deploy_once(stack=TracerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id) diff --git a/tests/e2e/tracer/handlers/async_capture.py b/tests/e2e/tracer/handlers/async_capture.py new file mode 100644 index 00000000000..b19840a6f69 --- /dev/null +++ b/tests/e2e/tracer/handlers/async_capture.py @@ -0,0 +1,16 @@ +import asyncio +from uuid import uuid4 + +from aws_lambda_powertools import Tracer +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() + + +@tracer.capture_method +async def async_get_users(): + return [{"id": f"{uuid4()}"} for _ in range(5)] + + +def lambda_handler(event: dict, context: LambdaContext): + return asyncio.run(async_get_users()) diff --git a/tests/e2e/tracer/handlers/basic_handler.py b/tests/e2e/tracer/handlers/basic_handler.py index d074b30796f..ba94c845ace 100644 --- a/tests/e2e/tracer/handlers/basic_handler.py +++ b/tests/e2e/tracer/handlers/basic_handler.py @@ -1,25 +1,16 @@ -import asyncio -import os +from uuid import uuid4 from aws_lambda_powertools import Tracer from aws_lambda_powertools.utilities.typing import LambdaContext -tracer = Tracer(service="e2e-tests-app") +tracer = Tracer() -ANNOTATION_KEY = os.environ["ANNOTATION_KEY"] -ANNOTATION_VALUE = os.environ["ANNOTATION_VALUE"] -ANNOTATION_ASYNC_VALUE = os.environ["ANNOTATION_ASYNC_VALUE"] + +@tracer.capture_method +def get_todos(): + return [{"id": f"{uuid4()}", "completed": False} for _ in range(5)] @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext): - tracer.put_annotation(key=ANNOTATION_KEY, value=ANNOTATION_VALUE) - tracer.put_metadata(key=ANNOTATION_KEY, value=ANNOTATION_VALUE) - return asyncio.run(collect_payment()) - - -@tracer.capture_method -async def collect_payment() -> str: - tracer.put_annotation(key=ANNOTATION_KEY, value=ANNOTATION_ASYNC_VALUE) - tracer.put_metadata(key=ANNOTATION_KEY, value=ANNOTATION_ASYNC_VALUE) - return "success" + return get_todos() diff --git a/tests/e2e/tracer/infrastructure.py b/tests/e2e/tracer/infrastructure.py new file mode 100644 index 00000000000..bd40fd2ca13 --- /dev/null +++ b/tests/e2e/tracer/infrastructure.py @@ -0,0 +1,17 @@ +from pathlib import Path + +from tests.e2e.utils.data_builder import build_service_name +from tests.e2e.utils.infrastructure import BaseInfrastructureV2 + + +class TracerStack(BaseInfrastructureV2): + # Maintenance: Tracer doesn't support dynamic service injection (tracer.py L310) + # we could move after handler response or adopt env vars usage in e2e tests + SERVICE_NAME: str = build_service_name() + + def __init__(self, handlers_dir: Path, feature_name: str = "tracer") -> None: + super().__init__(feature_name, handlers_dir) + + def create_resources(self) -> None: + env_vars = {"POWERTOOLS_SERVICE_NAME": self.SERVICE_NAME} + self.create_lambda_functions(function_props={"environment": env_vars}) diff --git a/tests/e2e/tracer/test_tracer.py b/tests/e2e/tracer/test_tracer.py index c2af4386749..06dde811ef1 100644 --- a/tests/e2e/tracer/test_tracer.py +++ b/tests/e2e/tracer/test_tracer.py @@ -1,51 +1,69 @@ -import datetime -import uuid - -import boto3 import pytest -from e2e import conftest -from e2e.utils import helpers + +from tests.e2e.tracer.handlers import async_capture, basic_handler +from tests.e2e.tracer.infrastructure import TracerStack +from tests.e2e.utils import data_builder, data_fetcher + + +@pytest.fixture +def basic_handler_fn_arn(infrastructure: dict) -> str: + return infrastructure.get("BasicHandlerArn", "") + + +@pytest.fixture +def basic_handler_fn(infrastructure: dict) -> str: + return infrastructure.get("BasicHandler", "") + + +@pytest.fixture +def async_fn_arn(infrastructure: dict) -> str: + return infrastructure.get("AsyncCaptureArn", "") + + +@pytest.fixture +def async_fn(infrastructure: dict) -> str: + return infrastructure.get("AsyncCapture", "") -@pytest.fixture(scope="module") -def config() -> conftest.LambdaConfig: - return { - "parameters": {"tracing": "ACTIVE"}, - "environment_variables": { - "ANNOTATION_KEY": f"e2e-tracer-{str(uuid.uuid4()).replace('-','_')}", - "ANNOTATION_VALUE": "stored", - "ANNOTATION_ASYNC_VALUE": "payments", - }, - } +def test_lambda_handler_trace_is_visible(basic_handler_fn_arn: str, basic_handler_fn: str): + # GIVEN + handler_name = basic_handler.lambda_handler.__name__ + handler_subsegment = f"## {handler_name}" + handler_metadata_key = f"{handler_name} response" + + method_name = basic_handler.get_todos.__name__ + method_subsegment = f"## {method_name}" + handler_metadata_key = f"{method_name} response" + + trace_query = data_builder.build_trace_default_query(function_name=basic_handler_fn) + + # WHEN + _, execution_time = data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn) + data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn) + + # THEN + trace = data_fetcher.get_traces(start_date=execution_time, filter_expression=trace_query, minimum_traces=2) + + assert len(trace.get_annotation(key="ColdStart", value=True)) == 1 + assert len(trace.get_metadata(key=handler_metadata_key, namespace=TracerStack.SERVICE_NAME)) == 2 + assert len(trace.get_metadata(key=handler_metadata_key, namespace=TracerStack.SERVICE_NAME)) == 2 + assert len(trace.get_subsegment(name=handler_subsegment)) == 2 + assert len(trace.get_subsegment(name=method_subsegment)) == 2 -def test_basic_lambda_async_trace_visible(execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig): +def test_async_trace_is_visible(async_fn_arn: str, async_fn: str): # GIVEN - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn") - start_date = execute_lambda.get_lambda_execution_time() - end_date = start_date + datetime.timedelta(minutes=5) - trace_filter_exporession = f'service("{lambda_name}")' + async_fn_name = async_capture.async_get_users.__name__ + async_fn_name_subsegment = f"## {async_fn_name}" + async_fn_name_metadata_key = f"{async_fn_name} response" + + trace_query = data_builder.build_trace_default_query(function_name=async_fn) # WHEN - trace = helpers.get_traces( - start_date=start_date, - end_date=end_date, - filter_expression=trace_filter_exporession, - xray_client=boto3.client("xray"), - ) + _, execution_time = data_fetcher.get_lambda_response(lambda_arn=async_fn_arn) # THEN - info = helpers.find_trace_additional_info(trace=trace) - print(info) - handler_trace_segment = [trace_segment for trace_segment in info if trace_segment.name == "## lambda_handler"][0] - collect_payment_trace_segment = [ - trace_segment for trace_segment in info if trace_segment.name == "## collect_payment" - ][0] - - annotation_key = config["environment_variables"]["ANNOTATION_KEY"] - expected_value = config["environment_variables"]["ANNOTATION_VALUE"] - expected_async_value = config["environment_variables"]["ANNOTATION_ASYNC_VALUE"] - - assert handler_trace_segment.annotations["Service"] == "e2e-tests-app" - assert handler_trace_segment.metadata["e2e-tests-app"][annotation_key] == expected_value - assert collect_payment_trace_segment.metadata["e2e-tests-app"][annotation_key] == expected_async_value + trace = data_fetcher.get_traces(start_date=execution_time, filter_expression=trace_query) + + assert len(trace.get_subsegment(name=async_fn_name_subsegment)) == 1 + assert len(trace.get_metadata(key=async_fn_name_metadata_key, namespace=TracerStack.SERVICE_NAME)) == 1 diff --git a/tests/e2e/utils/asset.py b/tests/e2e/utils/asset.py index 0bc7b5dfabe..04d368a6ff4 100644 --- a/tests/e2e/utils/asset.py +++ b/tests/e2e/utils/asset.py @@ -2,18 +2,45 @@ import json import zipfile from pathlib import Path -from typing import List, Optional +from typing import Dict, List, Optional import boto3 import botocore.exceptions from mypy_boto3_s3 import S3Client +from pydantic import BaseModel, Field from aws_lambda_powertools import Logger -from tests.e2e.utils.models import AssetTemplateConfig, TemplateAssembly logger = Logger(service="e2e-utils") +class AssetManifest(BaseModel): + path: str + packaging: str + + +class AssetTemplateConfigDestinationsAccount(BaseModel): + bucket_name: str = Field(str, alias="bucketName") + object_key: str = Field(str, alias="objectKey") + assume_role_arn: str = Field(str, alias="assumeRoleArn") + + +class AssetTemplateConfigDestinations(BaseModel): + current_account_current_region: AssetTemplateConfigDestinationsAccount = Field( + AssetTemplateConfigDestinationsAccount, alias="current_account-current_region" + ) + + +class AssetTemplateConfig(BaseModel): + source: AssetManifest + destinations: AssetTemplateConfigDestinations + + +class TemplateAssembly(BaseModel): + version: str + files: Dict[str, AssetTemplateConfig] + + class Asset: def __init__( self, config: AssetTemplateConfig, account_id: str, region: str, boto3_client: Optional[S3Client] = None diff --git a/tests/e2e/utils/data_builder/__init__.py b/tests/e2e/utils/data_builder/__init__.py new file mode 100644 index 00000000000..72c216faa76 --- /dev/null +++ b/tests/e2e/utils/data_builder/__init__.py @@ -0,0 +1,13 @@ +from tests.e2e.utils.data_builder.common import build_random_value, build_service_name +from tests.e2e.utils.data_builder.metrics import ( + build_add_dimensions_input, + build_add_metric_input, + build_metric_name, + build_metric_query_data, + build_multiple_add_metric_input, +) +from tests.e2e.utils.data_builder.traces import ( + build_put_annotations_input, + build_put_metadata_input, + build_trace_default_query, +) diff --git a/tests/e2e/utils/data_builder/common.py b/tests/e2e/utils/data_builder/common.py new file mode 100644 index 00000000000..f28778ffed3 --- /dev/null +++ b/tests/e2e/utils/data_builder/common.py @@ -0,0 +1,9 @@ +import secrets + + +def build_service_name() -> str: + return f"test_service{build_random_value()}" + + +def build_random_value(nbytes: int = 10) -> str: + return secrets.token_urlsafe(nbytes).replace("-", "") diff --git a/tests/e2e/utils/data_builder/metrics.py b/tests/e2e/utils/data_builder/metrics.py new file mode 100644 index 00000000000..d14f4ae3567 --- /dev/null +++ b/tests/e2e/utils/data_builder/metrics.py @@ -0,0 +1,116 @@ +from typing import Dict, List, Optional + +from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataQueryTypeDef + +from aws_lambda_powertools.metrics import MetricUnit +from tests.e2e.utils.data_builder.common import build_random_value + + +def build_metric_query_data( + namespace: str, + metric_name: str, + period: int = 60, + stat: str = "Sum", + dimensions: Optional[List[DimensionTypeDef]] = None, +) -> List[MetricDataQueryTypeDef]: + """Create input for CloudWatch GetMetricData API call + + Parameters + ---------- + namespace : str + Metric namespace to search for + metric_name : str + Metric name to search for + period : int, optional + Time period in seconds to search metrics, by default 60 + stat : str, optional + Aggregate function to use for results, by default "Sum" + dimensions : Optional[List[DimensionTypeDef]], optional + Metric dimensions to search for, by default None + + Returns + ------- + List[MetricDataQueryTypeDef] + _description_ + """ + dimensions = dimensions or [] + data_query: List[MetricDataQueryTypeDef] = [ + { + "Id": metric_name.lower(), + "MetricStat": { + "Metric": {"Namespace": namespace, "MetricName": metric_name}, + "Period": period, + "Stat": stat, + }, + "ReturnData": True, + } + ] + + if dimensions: + data_query[0]["MetricStat"]["Metric"]["Dimensions"] = dimensions + + return data_query + + +def build_add_metric_input(metric_name: str, value: float, unit: str = MetricUnit.Count.value) -> Dict: + """Create a metric input to be used with Metrics.add_metric() + + Parameters + ---------- + metric_name : str + metric name + value : float + metric value + unit : str, optional + metric unit, by default Count + + Returns + ------- + Dict + Metric input + """ + return {"name": metric_name, "unit": unit, "value": value} + + +def build_multiple_add_metric_input( + metric_name: str, value: float, unit: str = MetricUnit.Count.value, quantity: int = 1 +) -> List[Dict]: + """Create list of metrics input to be used with Metrics.add_metric() + + Parameters + ---------- + metric_name : str + metric name + value : float + metric value + unit : str, optional + metric unit, by default Count + quantity : int, optional + number of metrics to be created, by default 1 + + Returns + ------- + List[Dict] + List of metrics + """ + return [{"name": metric_name, "unit": unit, "value": value} for _ in range(quantity)] + + +def build_add_dimensions_input(**dimensions) -> List[DimensionTypeDef]: + """Create dimensions input to be used with either get_metrics or Metrics.add_dimension() + + Parameters + ---------- + dimensions : str + key=value pair as dimension + + Returns + ------- + List[DimensionTypeDef] + Metric dimension input + """ + return [{"Name": name, "Value": value} for name, value in dimensions.items()] + + +def build_metric_name() -> str: + return f"test_metric{build_random_value()}" diff --git a/tests/e2e/utils/data_builder/traces.py b/tests/e2e/utils/data_builder/traces.py new file mode 100644 index 00000000000..59350c8ff68 --- /dev/null +++ b/tests/e2e/utils/data_builder/traces.py @@ -0,0 +1,39 @@ +from typing import Any, Dict, List, Optional + + +def build_trace_default_query(function_name: str) -> str: + return f'service("{function_name}")' + + +def build_put_annotations_input(**annotations: str) -> List[Dict]: + """Create trace annotations input to be used with Tracer.put_annotation() + + Parameters + ---------- + annotations : str + annotations in key=value form + + Returns + ------- + List[Dict] + List of put annotations input + """ + return [{"key": key, "value": value} for key, value in annotations.items()] + + +def build_put_metadata_input(namespace: Optional[str] = None, **metadata: Any) -> List[Dict]: + """Create trace metadata input to be used with Tracer.put_metadata() + + All metadata will be under `test` namespace + + Parameters + ---------- + metadata : Any + metadata in key=value form + + Returns + ------- + List[Dict] + List of put metadata input + """ + return [{"key": key, "value": value, "namespace": namespace} for key, value in metadata.items()] diff --git a/tests/e2e/utils/data_fetcher/__init__.py b/tests/e2e/utils/data_fetcher/__init__.py new file mode 100644 index 00000000000..43024f9946f --- /dev/null +++ b/tests/e2e/utils/data_fetcher/__init__.py @@ -0,0 +1,4 @@ +from tests.e2e.utils.data_fetcher.common import get_lambda_response +from tests.e2e.utils.data_fetcher.logs import get_logs +from tests.e2e.utils.data_fetcher.metrics import get_metrics +from tests.e2e.utils.data_fetcher.traces import get_traces diff --git a/tests/e2e/utils/data_fetcher/common.py b/tests/e2e/utils/data_fetcher/common.py new file mode 100644 index 00000000000..2de8838dc74 --- /dev/null +++ b/tests/e2e/utils/data_fetcher/common.py @@ -0,0 +1,15 @@ +from datetime import datetime +from typing import Optional, Tuple + +import boto3 +from mypy_boto3_lambda import LambdaClient +from mypy_boto3_lambda.type_defs import InvocationResponseTypeDef + + +def get_lambda_response( + lambda_arn: str, payload: Optional[str] = None, client: Optional[LambdaClient] = None +) -> Tuple[InvocationResponseTypeDef, datetime]: + client = client or boto3.client("lambda") + payload = payload or "" + execution_time = datetime.utcnow() + return client.invoke(FunctionName=lambda_arn, InvocationType="RequestResponse", Payload=payload), execution_time diff --git a/tests/e2e/utils/data_fetcher/logs.py b/tests/e2e/utils/data_fetcher/logs.py new file mode 100644 index 00000000000..e8211eeea30 --- /dev/null +++ b/tests/e2e/utils/data_fetcher/logs.py @@ -0,0 +1,39 @@ +import json +from functools import lru_cache +from typing import List, Optional, Union + +from mypy_boto3_cloudwatch import CloudWatchClient +from pydantic import BaseModel +from retry import retry + + +class Log(BaseModel): + level: str + location: str + message: Union[dict, str] + timestamp: str + service: str + cold_start: Optional[bool] + function_name: Optional[str] + function_memory_size: Optional[str] + function_arn: Optional[str] + function_request_id: Optional[str] + xray_trace_id: Optional[str] + extra_info: Optional[str] + + +@lru_cache(maxsize=10, typed=False) +@retry(ValueError, delay=1, jitter=1, tries=20) +def get_logs(lambda_function_name: str, log_client: CloudWatchClient, start_time: int, **kwargs: dict) -> List[Log]: + response = log_client.filter_log_events(logGroupName=f"/aws/lambda/{lambda_function_name}", startTime=start_time) + if not response["events"]: + raise ValueError("Empty response from Cloudwatch Logs. Repeating...") + filtered_logs = [] + for event in response["events"]: + try: + message = Log(**json.loads(event["message"])) + except json.decoder.JSONDecodeError: + continue + filtered_logs.append(message) + + return filtered_logs diff --git a/tests/e2e/utils/data_fetcher/metrics.py b/tests/e2e/utils/data_fetcher/metrics.py new file mode 100644 index 00000000000..5a017f0a845 --- /dev/null +++ b/tests/e2e/utils/data_fetcher/metrics.py @@ -0,0 +1,71 @@ +from datetime import datetime, timedelta +from typing import List, Optional + +import boto3 +from mypy_boto3_cloudwatch import CloudWatchClient +from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataResultTypeDef +from retry import retry + +from tests.e2e.utils.data_builder import build_metric_query_data + + +@retry(ValueError, delay=2, jitter=1.5, tries=10) +def get_metrics( + namespace: str, + start_date: datetime, + metric_name: str, + dimensions: Optional[List[DimensionTypeDef]] = None, + cw_client: Optional[CloudWatchClient] = None, + end_date: Optional[datetime] = None, + period: int = 60, + stat: str = "Sum", +) -> MetricDataResultTypeDef: + """Fetch CloudWatch Metrics + + It takes into account eventual consistency with up to 10 retries and 1.5s jitter. + + Parameters + ---------- + namespace : str + Metric Namespace + start_date : datetime + Start window to fetch metrics + metric_name : str + Metric name + dimensions : Optional[List[DimensionTypeDef]], optional + List of Metric Dimension, by default None + cw_client : Optional[CloudWatchClient], optional + Boto3 CloudWatch low-level client (boto3.client("cloudwatch"), by default None + end_date : Optional[datetime], optional + End window to fetch metrics, by default start_date + 2 minutes window + period : int, optional + Time period to fetch metrics for, by default 60 + stat : str, optional + Aggregation function to use when fetching metrics, by default "Sum" + + Returns + ------- + MetricDataResultTypeDef + Dict with metric values found + + Raises + ------ + ValueError + When no metric is found within retry window + """ + cw_client = cw_client or boto3.client("cloudwatch") + end_date = end_date or start_date + timedelta(minutes=2) + + metric_query = build_metric_query_data( + namespace=namespace, metric_name=metric_name, period=period, stat=stat, dimensions=dimensions + ) + + response = cw_client.get_metric_data( + MetricDataQueries=metric_query, + StartTime=start_date, + EndTime=end_date or datetime.utcnow(), + ) + result = response["MetricDataResults"][0] + if not result["Values"]: + raise ValueError("Empty response from Cloudwatch. Repeating...") + return result diff --git a/tests/e2e/utils/data_fetcher/traces.py b/tests/e2e/utils/data_fetcher/traces.py new file mode 100644 index 00000000000..827109112df --- /dev/null +++ b/tests/e2e/utils/data_fetcher/traces.py @@ -0,0 +1,267 @@ +import json +from datetime import datetime, timedelta +from typing import Any, Dict, Generator, List, Optional + +import boto3 +from botocore.paginate import PageIterator +from mypy_boto3_xray.client import XRayClient +from mypy_boto3_xray.type_defs import TraceSummaryTypeDef +from pydantic import BaseModel +from retry import retry + + +class TraceSubsegment(BaseModel): + id: str # noqa: A003 VNE003 # id is a field we can't change + name: str + start_time: float + end_time: float + aws: Optional[dict] + subsegments: Optional[List["TraceSubsegment"]] + annotations: Optional[Dict[str, Any]] + metadata: Optional[Dict[str, Dict[str, Any]]] + + +class TraceDocument(BaseModel): + id: str # noqa: A003 VNE003 # id is a field we can't change + name: str + start_time: float + end_time: float + trace_id: str + parent_id: Optional[str] + aws: Dict + origin: str + subsegments: Optional[List[TraceSubsegment]] + + +class TraceFetcher: + default_exclude_seg_name: List = ["Initialization", "Invocation", "Overhead"] + + def __init__( + self, + filter_expression: str, + start_date: datetime, + end_date: Optional[datetime] = None, + xray_client: Optional[XRayClient] = None, + exclude_segment_name: Optional[List[str]] = None, + resource_name: Optional[List[str]] = None, + origin: Optional[List[str]] = None, + minimum_traces: int = 1, + ): + """Fetch and expose traces from X-Ray based on parameters + + Data is recursively fetched in the following order: + + * Trace summaries + * Trace IDs + * Traces + * Segments + * Subsegments + * Nested Subsegments + + Parameters + ---------- + filter_expression : str + AWS X-Ray Filter Expressions + see: https://docs.aws.amazon.com/xray/latest/devguide/xray-console-filters.html + start_date : datetime + Start date range to filter traces + end_date : Optional[datetime], optional + End date range to filter traces, by default 5 minutes past start_date + xray_client : Optional[XRayClient], optional + AWS X-Ray SDK Client, by default boto3.client('xray') + exclude_segment_name : Optional[List[str]], optional + Name of segments to exclude, by default ["Initialization", "Invocation", "Overhead"] + resource_name : Optional[List[str]], optional + Name of resource to filter traces (e.g., function name), by default None + origin : Optional[List[str]], optional + Trace origin name to filter traces, by default ["AWS::Lambda::Function"] + minimum_traces : int + Minimum number of traces to be retrieved before exhausting retry attempts + """ + self.filter_expression = filter_expression + self.start_date = start_date + self.end_date = end_date or self.start_date + timedelta(minutes=5) + self.xray_client: XRayClient = xray_client or boto3.client("xray") + self.trace_documents: Dict[str, TraceDocument] = {} + self.subsegments: List[TraceSubsegment] = [] + self.exclude_segment_name = exclude_segment_name or self.default_exclude_seg_name + self.resource_name = resource_name + self.origin = origin or ["AWS::Lambda::Function"] + self.annotations: List[Dict[str, Any]] = [] + self.metadata: List[Dict[str, Dict[str, Any]]] = [] + self.minimum_traces = minimum_traces + + paginator = self.xray_client.get_paginator("get_trace_summaries") + pages = paginator.paginate( + StartTime=self.start_date, + EndTime=self.end_date, + TimeRangeType="Event", + Sampling=False, + FilterExpression=self.filter_expression, + ) + + trace_ids = self._get_trace_ids(pages) + self.trace_documents = self._get_trace_documents(trace_ids) + self.subsegments = self._get_subsegments() + + def get_annotation(self, key: str, value: Optional[any] = None) -> List: + return [ + annotation + for annotation in self.annotations + if (value is not None and annotation.get(key) == value) or (value is None and key in annotation) + ] + + def get_metadata(self, key: str, namespace: str = "") -> List[Dict[str, Any]]: + seen = [] + for meta in self.metadata: + metadata = meta.get(namespace, {}) + if key in metadata: + seen.append(metadata) + return seen + + def get_subsegment(self, name: str) -> List: + return [seg for seg in self.subsegments if seg.name == name] + + def _find_nested_subsegments(self, subsegments: List[TraceSubsegment]) -> Generator[TraceSubsegment, None, None]: + """Recursively yield any subsegment that we might be interested. + + It excludes any subsegments contained in exclude_segment_name. + Since these are nested, subsegment name might be '## lambda_handler'. + + It also populates annotations and metadata nested in subsegments. + + Parameters + ---------- + subsegment : TraceSubsegment + subsegment to traverse + seen : List + list of subsegments to be updated + """ + for seg in subsegments: + if seg.name not in self.exclude_segment_name: + if seg.annotations: + self.annotations.append(seg.annotations) + + if seg.metadata: + self.metadata.append(seg.metadata) + + yield seg + + if seg.subsegments: + # recursively iterate over any arbitrary number of subsegments + yield from self._find_nested_subsegments(seg.subsegments) + + def _get_subsegments(self) -> List[TraceSubsegment]: + """Find subsegments and potentially any nested subsegments + + It excludes any subsegments contained in exclude_segment_name. + Since these are top-level, subsegment name might be 'Overhead/Invocation, etc.'. + + Returns + ------- + List[TraceSubsegment] + List of subsegments + """ + seen = [] + for document in self.trace_documents.values(): + if document.subsegments: + seen.extend(self._find_nested_subsegments(document.subsegments)) + + return seen + + def _get_trace_ids(self, pages: PageIterator) -> List[str]: + """Get list of trace IDs found + + Parameters + ---------- + pages : PageIterator + Paginated streaming response from AWS X-Ray + + Returns + ------- + List[str] + Trace IDs + + Raises + ------ + ValueError + When no traces are available within time range and filter expression + """ + summaries: List[TraceSummaryTypeDef] = [trace["TraceSummaries"] for trace in pages if trace["TraceSummaries"]] + if not summaries: + raise ValueError("Empty response from X-Ray. Repeating...") + + trace_ids = [trace["Id"] for trace in summaries[0]] # type: ignore[index] # TypedDict not being recognized + if len(trace_ids) < self.minimum_traces: + raise ValueError( + f"Number of traces found doesn't meet minimum required ({self.minimum_traces}). Repeating..." + ) + + return trace_ids + + def _get_trace_documents(self, trace_ids: List[str]) -> Dict[str, TraceDocument]: + """Find trace documents available in each trace segment + + Returns + ------- + Dict[str, TraceDocument] + Trace documents grouped by their ID + """ + traces = self.xray_client.batch_get_traces(TraceIds=trace_ids) + documents: Dict = {} + segments = [seg for trace in traces["Traces"] for seg in trace["Segments"]] + for seg in segments: + trace_document = TraceDocument(**json.loads(seg["Document"])) + if trace_document.origin in self.origin or trace_document.name == self.resource_name: + documents[trace_document.id] = trace_document + return documents + + +@retry(ValueError, delay=5, jitter=1.5, tries=10) +def get_traces( + filter_expression: str, + start_date: datetime, + end_date: Optional[datetime] = None, + xray_client: Optional[XRayClient] = None, + exclude_segment_name: Optional[List[str]] = None, + resource_name: Optional[List[str]] = None, + origin: Optional[List[str]] = None, + minimum_traces: int = 1, +) -> TraceFetcher: + """Fetch traces from AWS X-Ray + + Parameters + ---------- + filter_expression : str + AWS X-Ray Filter Expressions + see: https://docs.aws.amazon.com/xray/latest/devguide/xray-console-filters.html + start_date : datetime + Start date range to filter traces + end_date : Optional[datetime], optional + End date range to filter traces, by default 5 minutes past start_date + xray_client : Optional[XRayClient], optional + AWS X-Ray SDK Client, by default boto3.client('xray') + exclude_segment_name : Optional[List[str]], optional + Name of segments to exclude, by default ["Initialization", "Invocation", "Overhead"] + resource_name : Optional[List[str]], optional + Name of resource to filter traces (e.g., function name), by default None + origin : Optional[List[str]], optional + Trace origin name to filter traces, by default ["AWS::Lambda::Function"] + minimum_traces : int + Minimum number of traces to be retrieved before exhausting retry attempts + + Returns + ------- + TraceFetcher + TraceFetcher instance with trace data available as properties and methods + """ + return TraceFetcher( + filter_expression=filter_expression, + start_date=start_date, + end_date=end_date, + xray_client=xray_client, + exclude_segment_name=exclude_segment_name, + resource_name=resource_name, + origin=origin, + minimum_traces=minimum_traces, + ) diff --git a/tests/e2e/utils/helpers.py b/tests/e2e/utils/helpers.py deleted file mode 100644 index 6827ac12d90..00000000000 --- a/tests/e2e/utils/helpers.py +++ /dev/null @@ -1,276 +0,0 @@ -import json -import secrets -from datetime import datetime, timedelta -from functools import lru_cache -from typing import Dict, List, Optional, Tuple, Union - -import boto3 -from mypy_boto3_cloudwatch.client import CloudWatchClient -from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataQueryTypeDef, MetricDataResultTypeDef -from mypy_boto3_lambda.client import LambdaClient -from mypy_boto3_lambda.type_defs import InvocationResponseTypeDef -from mypy_boto3_xray.client import XRayClient -from pydantic import BaseModel -from retry import retry - -# Helper methods && Class -from aws_lambda_powertools.metrics import MetricUnit - - -class Log(BaseModel): - level: str - location: str - message: Union[dict, str] - timestamp: str - service: str - cold_start: Optional[bool] - function_name: Optional[str] - function_memory_size: Optional[str] - function_arn: Optional[str] - function_request_id: Optional[str] - xray_trace_id: Optional[str] - extra_info: Optional[str] - - -class TraceSegment(BaseModel): - name: str - metadata: Dict = {} - annotations: Dict = {} - - -def trigger_lambda( - lambda_arn: str, payload: str, client: Optional[LambdaClient] = None -) -> Tuple[InvocationResponseTypeDef, datetime]: - client = client or boto3.client("lambda") - execution_time = datetime.utcnow() - return client.invoke(FunctionName=lambda_arn, InvocationType="RequestResponse", Payload=payload), execution_time - - -@lru_cache(maxsize=10, typed=False) -@retry(ValueError, delay=1, jitter=1, tries=20) -def get_logs(lambda_function_name: str, log_client: CloudWatchClient, start_time: int, **kwargs: dict) -> List[Log]: - response = log_client.filter_log_events(logGroupName=f"/aws/lambda/{lambda_function_name}", startTime=start_time) - if not response["events"]: - raise ValueError("Empty response from Cloudwatch Logs. Repeating...") - filtered_logs = [] - for event in response["events"]: - try: - message = Log(**json.loads(event["message"])) - except json.decoder.JSONDecodeError: - continue - filtered_logs.append(message) - - return filtered_logs - - -@retry(ValueError, delay=2, jitter=1.5, tries=10) -def get_metrics( - namespace: str, - start_date: datetime, - metric_name: str, - dimensions: Optional[List[DimensionTypeDef]] = None, - cw_client: Optional[CloudWatchClient] = None, - end_date: Optional[datetime] = None, - period: int = 60, - stat: str = "Sum", -) -> MetricDataResultTypeDef: - """Fetch CloudWatch Metrics - - It takes into account eventual consistency with up to 10 retries and 1s jitter. - - Parameters - ---------- - namespace : str - Metric Namespace - start_date : datetime - Start window to fetch metrics - metric_name : str - Metric name - dimensions : Optional[List[DimensionTypeDef]], optional - List of Metric Dimension, by default None - cw_client : Optional[CloudWatchClient], optional - Boto3 CloudWatch low-level client (boto3.client("cloudwatch"), by default None - end_date : Optional[datetime], optional - End window to fetch metrics, by default start_date + 2 minutes window - period : int, optional - Time period to fetch metrics for, by default 60 - stat : str, optional - Aggregation function to use when fetching metrics, by default "Sum" - - Returns - ------- - MetricDataResultTypeDef - _description_ - - Raises - ------ - ValueError - When no metric is found within retry window - """ - cw_client = cw_client or boto3.client("cloudwatch") - end_date = end_date or start_date + timedelta(minutes=2) - - metric_query = build_metric_query_data( - namespace=namespace, metric_name=metric_name, period=period, stat=stat, dimensions=dimensions - ) - - response = cw_client.get_metric_data( - MetricDataQueries=metric_query, - StartTime=start_date, - EndTime=end_date or datetime.utcnow(), - ) - result = response["MetricDataResults"][0] - if not result["Values"]: - raise ValueError("Empty response from Cloudwatch. Repeating...") - return result - - -@retry(ValueError, delay=1, jitter=1, tries=10) -def get_traces( - filter_expression: str, start_date: datetime, end_date: datetime, xray_client: Optional[XRayClient] = None -) -> Dict: - xray_client = xray_client or boto3.client("xray") - paginator = xray_client.get_paginator("get_trace_summaries") - response_iterator = paginator.paginate( - StartTime=start_date, - EndTime=end_date, - TimeRangeType="Event", - Sampling=False, - FilterExpression=filter_expression, - ) - - traces = [trace["TraceSummaries"][0]["Id"] for trace in response_iterator if trace["TraceSummaries"]] - if not traces: - raise ValueError("Empty response from X-RAY. Repeating...") - - trace_details = xray_client.batch_get_traces( - TraceIds=traces, - ) - - return trace_details - - -def find_trace_additional_info(trace: Dict) -> List[TraceSegment]: - """Find all trace annotations and metadata and return them to the caller""" - info = [] - for segment in trace["Traces"][0]["Segments"]: - document = json.loads(segment["Document"]) - if document["origin"] == "AWS::Lambda::Function": - for subsegment in document["subsegments"]: - if subsegment["name"] == "Invocation": - find_meta(segment=subsegment, result=info) - return info - - -def find_meta(segment: dict, result: List): - for x_subsegment in segment["subsegments"]: - result.append( - TraceSegment( - name=x_subsegment["name"], - metadata=x_subsegment.get("metadata", {}), - annotations=x_subsegment.get("annotations", {}), - ) - ) - if x_subsegment.get("subsegments"): - find_meta(segment=x_subsegment, result=result) - - -# Maintenance: Build a separate module for builders -def build_metric_name() -> str: - return f"test_metric{build_random_value()}" - - -def build_service_name() -> str: - return f"test_service{build_random_value()}" - - -def build_random_value(nbytes: int = 10) -> str: - return secrets.token_urlsafe(nbytes).replace("-", "") - - -def build_metric_query_data( - namespace: str, - metric_name: str, - period: int = 60, - stat: str = "Sum", - dimensions: Optional[List[DimensionTypeDef]] = None, -) -> List[MetricDataQueryTypeDef]: - dimensions = dimensions or [] - data_query: List[MetricDataQueryTypeDef] = [ - { - "Id": metric_name.lower(), - "MetricStat": { - "Metric": {"Namespace": namespace, "MetricName": metric_name}, - "Period": period, - "Stat": stat, - }, - "ReturnData": True, - } - ] - - if dimensions: - data_query[0]["MetricStat"]["Metric"]["Dimensions"] = dimensions - - return data_query - - -def build_add_metric_input(metric_name: str, value: float, unit: str = MetricUnit.Count.value) -> Dict: - """Create a metric input to be used with Metrics.add_metric() - - Parameters - ---------- - metric_name : str - metric name - value : float - metric value - unit : str, optional - metric unit, by default Count - - Returns - ------- - Dict - Metric input - """ - return {"name": metric_name, "unit": unit, "value": value} - - -def build_multiple_add_metric_input( - metric_name: str, value: float, unit: str = MetricUnit.Count.value, quantity: int = 1 -) -> Dict: - """Create list of metrics input to be used with Metrics.add_metric() - - Parameters - ---------- - metric_name : str - metric name - value : float - metric value - unit : str, optional - metric unit, by default Count - quantity : int, optional - number of metrics to be created, by default 1 - - Returns - ------- - List[Dict] - List of metrics - """ - return [{"name": metric_name, "unit": unit, "value": value} for _ in range(quantity)] - - -def build_add_dimensions_input(**dimensions) -> List[DimensionTypeDef]: - """Create dimensions input to be used with either get_metrics or Metrics.add_dimension() - - Parameters - ---------- - name : str - dimension name - value : float - dimension value - - Returns - ------- - Dict - Metric dimension input - """ - return [{"Name": name, "Value": value} for name, value in dimensions.items()] diff --git a/tests/e2e/utils/infrastructure.py b/tests/e2e/utils/infrastructure.py index fb3c3c02ce2..ced6d70a1ad 100644 --- a/tests/e2e/utils/infrastructure.py +++ b/tests/e2e/utils/infrastructure.py @@ -312,12 +312,12 @@ def deploy(self) -> Dict[str, str]: assets.upload() return self._deploy_stack(self.stack_name, template) - def delete(self): + def delete(self) -> None: """Delete CloudFormation Stack""" self.cfn.delete_stack(StackName=self.stack_name) @abstractmethod - def create_resources(self): + def create_resources(self) -> None: """Create any necessary CDK resources. It'll be called before deploy Examples diff --git a/tests/e2e/utils/models.py b/tests/e2e/utils/models.py deleted file mode 100644 index 0c3f81070d5..00000000000 --- a/tests/e2e/utils/models.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Dict - -from pydantic import BaseModel, Field - - -class AssetTemplateConfigSource(BaseModel): - path: str - packaging: str - - -class AssetTemplateConfigDestinationsAccount(BaseModel): - bucket_name: str = Field(str, alias="bucketName") - object_key: str = Field(str, alias="objectKey") - assume_role_arn: str = Field(str, alias="assumeRoleArn") - - -class AssetTemplateConfigDestinations(BaseModel): - current_account_current_region: AssetTemplateConfigDestinationsAccount = Field( - AssetTemplateConfigDestinationsAccount, alias="current_account-current_region" - ) - - -class AssetTemplateConfig(BaseModel): - source: AssetTemplateConfigSource - destinations: AssetTemplateConfigDestinations - - -class TemplateAssembly(BaseModel): - version: str - files: Dict[str, AssetTemplateConfig] - docker_images: Dict = Field(Dict, alias="dockerImages") From 787ead2743d49bc9dd8e0e900b927db702e1c353 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Fri, 19 Aug 2022 10:04:57 +0100 Subject: [PATCH 39/59] docs(validation): snippets split, improved, and lint (#1449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Release bot Co-authored-by: Peter Schutt Co-authored-by: heitorlessa Co-authored-by: Rúben Fonseca --- docs/utilities/jmespath_functions.md | 2 +- docs/utilities/validation.md | 375 ++++-------------- .../validation/src/custom_format_function.py | 34 ++ .../validation/src/custom_format_payload.json | 4 + .../validation/src/custom_format_schema.py | 26 ++ ...ng_started_validator_decorator_function.py | 45 +++ ...g_started_validator_decorator_payload.json | 6 + ...ting_started_validator_decorator_schema.py | 60 +++ ...g_started_validator_standalone_function.py | 30 ++ ..._started_validator_standalone_payload.json | 6 + ...ing_started_validator_standalone_schema.py | 33 ++ ...g_started_validator_unwrapping_function.py | 36 ++ ..._started_validator_unwrapping_payload.json | 17 + ...ing_started_validator_unwrapping_schema.py | 59 +++ ...nwrapping_popular_event_source_function.py | 24 ++ ...wrapping_popular_event_source_payload.json | 16 + .../unwrapping_popular_event_source_schema.py | 26 ++ 17 files changed, 490 insertions(+), 309 deletions(-) create mode 100644 examples/validation/src/custom_format_function.py create mode 100644 examples/validation/src/custom_format_payload.json create mode 100644 examples/validation/src/custom_format_schema.py create mode 100644 examples/validation/src/getting_started_validator_decorator_function.py create mode 100644 examples/validation/src/getting_started_validator_decorator_payload.json create mode 100644 examples/validation/src/getting_started_validator_decorator_schema.py create mode 100644 examples/validation/src/getting_started_validator_standalone_function.py create mode 100644 examples/validation/src/getting_started_validator_standalone_payload.json create mode 100644 examples/validation/src/getting_started_validator_standalone_schema.py create mode 100644 examples/validation/src/getting_started_validator_unwrapping_function.py create mode 100644 examples/validation/src/getting_started_validator_unwrapping_payload.json create mode 100644 examples/validation/src/getting_started_validator_unwrapping_schema.py create mode 100644 examples/validation/src/unwrapping_popular_event_source_function.py create mode 100644 examples/validation/src/unwrapping_popular_event_source_payload.json create mode 100644 examples/validation/src/unwrapping_popular_event_source_schema.py diff --git a/docs/utilities/jmespath_functions.md b/docs/utilities/jmespath_functions.md index 45250ea0fcd..209bf4fffe9 100644 --- a/docs/utilities/jmespath_functions.md +++ b/docs/utilities/jmespath_functions.md @@ -134,7 +134,7 @@ This sample will decode the base64 value within the `data` key, and deserialize === "powertools_base64_jmespath_function.py" - ```python hl_lines="7 10 37 48 52 54 56" + ```python hl_lines="7 10 37 49 53 55 57" --8<-- "examples/jmespath_functions/src/powertools_base64_jmespath_function.py" ``` diff --git a/docs/utilities/validation.md b/docs/utilities/validation.md index ec795c99bef..c9cd5813086 100644 --- a/docs/utilities/validation.md +++ b/docs/utilities/validation.md @@ -3,6 +3,8 @@ title: Validation description: Utility --- + + This utility provides JSON Schema validation for events and responses, including JMESPath support to unwrap events before validation. ## Key features @@ -13,14 +15,17 @@ This utility provides JSON Schema validation for events and responses, including ## Getting started -???+ tip "Tip: Using JSON Schemas for the first time?" - Check this [step-by-step tour in the official JSON Schema website](https://json-schema.org/learn/getting-started-step-by-step.html){target="_blank"}. +???+ tip + All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}. You can validate inbound and outbound events using [`validator` decorator](#validator-decorator). You can also use the standalone `validate` function, if you want more control over the validation process such as handling a validation error. -We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/){target="_blank"} library. +???+ tip "Tip: Using JSON Schemas for the first time?" + Check this [step-by-step tour in the official JSON Schema website](https://json-schema.org/learn/getting-started-step-by-step.html){target="_blank"}. + + We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.github.io/python-fastjsonschema/){target="_blank"} library. ???+ warning Both `validator` decorator and `validate` standalone function expects your JSON Schema to be a **dictionary**, not a filename. @@ -31,31 +36,22 @@ We support any JSONSchema draft supported by [fastjsonschema](https://horejsek.g It will fail fast with `SchemaValidationError` exception if event or response doesn't conform with given JSON Schema. -=== "validator_decorator.py" +=== "getting_started_validator_decorator_function.py" - ```python hl_lines="3 5" - from aws_lambda_powertools.utilities.validation import validator + ```python hl_lines="8 27 28 42" + --8<-- "examples/validation/src/getting_started_validator_decorator_function.py" + ``` - import schemas +=== "getting_started_validator_decorator_schema.py" - @validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) - def handler(event, context): - return event - ``` + ```python hl_lines="10 12 17 19 24 26 28 44 46 51 53" + --8<-- "examples/validation/src/getting_started_validator_decorator_schema.py" + ``` -=== "event.json" +=== "getting_started_validator_decorator_payload.json" ```json - { - "message": "hello world", - "username": "lessa" - } - ``` - -=== "schemas.py" - - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + --8<-- "examples/validation/src/getting_started_validator_decorator_payload.json" ``` ???+ note @@ -67,71 +63,48 @@ It will fail fast with `SchemaValidationError` exception if event or response do You can also gracefully handle schema validation errors by catching `SchemaValidationError` exception. -=== "validator_decorator.py" +=== "getting_started_validator_standalone_function.py" - ```python hl_lines="8" - from aws_lambda_powertools.utilities.validation import validate - from aws_lambda_powertools.utilities.validation.exceptions import SchemaValidationError + ```python hl_lines="5 16 17 26" + --8<-- "examples/validation/src/getting_started_validator_standalone_function.py" + ``` - import schemas +=== "getting_started_validator_standalone_schema.py" - def handler(event, context): - try: - validate(event=event, schema=schemas.INPUT) - except SchemaValidationError as e: - # do something before re-raising - raise - - return event - ``` + ```python hl_lines="7 8 10 12 17 19 24 26 28" + --8<-- "examples/validation/src/getting_started_validator_standalone_schema.py" + ``` -=== "event.json" +=== "getting_started_validator_standalone_payload.json" ```json - { - "data": "hello world", - "username": "lessa" - } - ``` - -=== "schemas.py" - - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + --8<-- "examples/validation/src/getting_started_validator_standalone_payload.json" ``` ### Unwrapping events prior to validation -You might want to validate only a portion of your event - This is where the `envelope` parameter is for. +You might want to validate only a portion of your event - This is what the `envelope` parameter is for. Envelopes are [JMESPath expressions](https://jmespath.org/tutorial.html) to extract a portion of JSON you want before applying JSON Schema validation. Here is a sample custom EventBridge event, where we only validate what's inside the `detail` key: -=== "unwrapping_events.py" +=== "getting_started_validator_unwrapping_function.py" - We use the `envelope` parameter to extract the payload inside the `detail` key before validating. + ```python hl_lines="2 6 12" + --8<-- "examples/validation/src/getting_started_validator_unwrapping_function.py" + ``` - ```python hl_lines="5" - from aws_lambda_powertools.utilities.validation import validator +=== "getting_started_validator_unwrapping_schema.py" - import schemas + ```python hl_lines="9-14 23 25 28 33 36 41 44 48 51" + --8<-- "examples/validation/src/getting_started_validator_unwrapping_schema.py" + ``` - @validator(inbound_schema=schemas.INPUT, envelope="detail") - def handler(event, context): - return event - ``` - -=== "sample_wrapped_event.json" - - ```python hl_lines="11-14" - --8<-- "docs/shared/validation_basic_eventbridge_event.json" - ``` +=== "getting_started_validator_unwrapping_payload.json" -=== "schemas.py" - - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json + --8<-- "examples/validation/src/getting_started_validator_unwrapping_payload.json" ``` This is quite powerful because you can use JMESPath Query language to extract records from [arrays](https://jmespath.org/tutorial.html#list-and-slice-projections), combine [pipe](https://jmespath.org/tutorial.html#pipe-expressions) and [function expressions](https://jmespath.org/tutorial.html#functions). @@ -140,30 +113,24 @@ When combined, these features allow you to extract what you need before validati ### Built-in envelopes -This utility comes with built-in envelopes to easily extract the payload from popular event sources. +We provide built-in envelopes to easily extract the payload from popular event sources. -=== "unwrapping_popular_event_sources.py" +=== "unwrapping_popular_event_source_function.py" - ```python hl_lines="5 7" - from aws_lambda_powertools.utilities.validation import envelopes, validator + ```python hl_lines="2 7 12" + --8<-- "examples/validation/src/unwrapping_popular_event_source_function.py" + ``` - import schemas +=== "unwrapping_popular_event_source_schema.py" - @validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) - def handler(event, context): - return event - ``` + ```python hl_lines="7 9 12 17 20" + --8<-- "examples/validation/src/unwrapping_popular_event_source_schema.py" + ``` -=== "sample_wrapped_event.json" +=== "unwrapping_popular_event_source_payload.json" - ```python hl_lines="11-14" - --8<-- "docs/shared/validation_basic_eventbridge_event.json" - ``` - -=== "schemas.py" - - ```python hl_lines="7 14 16 23 39 45 47 52" - --8<-- "docs/shared/validation_basic_jsonschema.py" + ```json hl_lines="12 13" + --8<-- "examples/validation/src/unwrapping_popular_event_source_payload.json" ``` Here is a handy table with built-in envelopes along with their JMESPath expressions in case you want to build your own. @@ -186,243 +153,35 @@ Here is a handy table with built-in envelopes along with their JMESPath expressi ???+ note JSON Schema DRAFT 7 [has many new built-in formats](https://json-schema.org/understanding-json-schema/reference/string.html#format){target="_blank"} such as date, time, and specifically a regex format which might be a better replacement for a custom format, if you do have control over the schema. -JSON Schemas with custom formats like `int64` will fail validation. If you have these, you can pass them using `formats` parameter: +JSON Schemas with custom formats like `awsaccountid` will fail validation. If you have these, you can pass them using `formats` parameter: ```json title="custom_json_schema_type_format.json" { - "lastModifiedTime": { - "format": "int64", - "type": "integer" + "accountid": { + "format": "awsaccountid", + "type": "string" } } ``` For each format defined in a dictionary key, you must use a regex, or a function that returns a boolean to instruct the validator on how to proceed when encountering that type. -=== "validate_custom_format.py" +=== "custom_format_function.py" - ```python hl_lines="5-8 10" - from aws_lambda_powertools.utilities.validation import validate + ```python hl_lines="5 8 10 11 17 27" + --8<-- "examples/validation/src/custom_format_function.py" + ``` - import schema +=== "custom_format_schema.py" - custom_format = { - "int64": True, # simply ignore it, - "positive": lambda x: False if x < 0 else True - } + ```python hl_lines="7 9 12 13 17 20" + --8<-- "examples/validation/src/custom_format_schema.py" + ``` - validate(event=event, schema=schemas.INPUT, formats=custom_format) - ``` +=== "custom_format_payload.json" -=== "schemas.py" - - ```python hl_lines="68" 91 93" - INPUT = { - "$schema": "http://json-schema.org/draft-04/schema#", - "definitions": { - "AWSAPICallViaCloudTrail": { - "properties": { - "additionalEventData": {"$ref": "#/definitions/AdditionalEventData"}, - "awsRegion": {"type": "string"}, - "errorCode": {"type": "string"}, - "errorMessage": {"type": "string"}, - "eventID": {"type": "string"}, - "eventName": {"type": "string"}, - "eventSource": {"type": "string"}, - "eventTime": {"format": "date-time", "type": "string"}, - "eventType": {"type": "string"}, - "eventVersion": {"type": "string"}, - "recipientAccountId": {"type": "string"}, - "requestID": {"type": "string"}, - "requestParameters": {"$ref": "#/definitions/RequestParameters"}, - "resources": {"items": {"type": "object"}, "type": "array"}, - "responseElements": {"type": ["object", "null"]}, - "sourceIPAddress": {"type": "string"}, - "userAgent": {"type": "string"}, - "userIdentity": {"$ref": "#/definitions/UserIdentity"}, - "vpcEndpointId": {"type": "string"}, - "x-amazon-open-api-schema-readOnly": {"type": "boolean"}, - }, - "required": [ - "eventID", - "awsRegion", - "eventVersion", - "responseElements", - "sourceIPAddress", - "eventSource", - "requestParameters", - "resources", - "userAgent", - "readOnly", - "userIdentity", - "eventType", - "additionalEventData", - "vpcEndpointId", - "requestID", - "eventTime", - "eventName", - "recipientAccountId", - ], - "type": "object", - }, - "AdditionalEventData": { - "properties": { - "objectRetentionInfo": {"$ref": "#/definitions/ObjectRetentionInfo"}, - "x-amz-id-2": {"type": "string"}, - }, - "required": ["x-amz-id-2"], - "type": "object", - }, - "Attributes": { - "properties": { - "creationDate": {"format": "date-time", "type": "string"}, - "mfaAuthenticated": {"type": "string"}, - }, - "required": ["mfaAuthenticated", "creationDate"], - "type": "object", - }, - "LegalHoldInfo": { - "properties": { - "isUnderLegalHold": {"type": "boolean"}, - "lastModifiedTime": {"format": "int64", "type": "integer"}, - }, - "type": "object", - }, - "ObjectRetentionInfo": { - "properties": { - "legalHoldInfo": {"$ref": "#/definitions/LegalHoldInfo"}, - "retentionInfo": {"$ref": "#/definitions/RetentionInfo"}, - }, - "type": "object", - }, - "RequestParameters": { - "properties": { - "bucketName": {"type": "string"}, - "key": {"type": "string"}, - "legal-hold": {"type": "string"}, - "retention": {"type": "string"}, - }, - "required": ["bucketName", "key"], - "type": "object", - }, - "RetentionInfo": { - "properties": { - "lastModifiedTime": {"format": "int64", "type": "integer"}, - "retainUntilMode": {"type": "string"}, - "retainUntilTime": {"format": "int64", "type": "integer"}, - }, - "type": "object", - }, - "SessionContext": { - "properties": {"attributes": {"$ref": "#/definitions/Attributes"}}, - "required": ["attributes"], - "type": "object", - }, - "UserIdentity": { - "properties": { - "accessKeyId": {"type": "string"}, - "accountId": {"type": "string"}, - "arn": {"type": "string"}, - "principalId": {"type": "string"}, - "sessionContext": {"$ref": "#/definitions/SessionContext"}, - "type": {"type": "string"}, - }, - "required": ["accessKeyId", "sessionContext", "accountId", "principalId", "type", "arn"], - "type": "object", - }, - }, - "properties": { - "account": {"type": "string"}, - "detail": {"$ref": "#/definitions/AWSAPICallViaCloudTrail"}, - "detail-type": {"type": "string"}, - "id": {"type": "string"}, - "region": {"type": "string"}, - "resources": {"items": {"type": "string"}, "type": "array"}, - "source": {"type": "string"}, - "time": {"format": "date-time", "type": "string"}, - "version": {"type": "string"}, - }, - "required": ["detail-type", "resources", "id", "source", "time", "detail", "region", "version", "account"], - "title": "AWSAPICallViaCloudTrail", - "type": "object", - "x-amazon-events-detail-type": "AWS API Call via CloudTrail", - "x-amazon-events-source": "aws.s3", - } - ``` - -=== "event.json" - - ```json - { - "account": "123456789012", - "detail": { - "additionalEventData": { - "AuthenticationMethod": "AuthHeader", - "CipherSuite": "ECDHE-RSA-AES128-GCM-SHA256", - "SignatureVersion": "SigV4", - "bytesTransferredIn": 0, - "bytesTransferredOut": 0, - "x-amz-id-2": "ejUr9Nd/4IO1juF/a6GOcu+PKrVX6dOH6jDjQOeCJvtARUqzxrhHGrhEt04cqYtAZVqcSEXYqo0=", - }, - "awsRegion": "us-west-1", - "eventCategory": "Data", - "eventID": "be4fdb30-9508-4984-b071-7692221899ae", - "eventName": "HeadObject", - "eventSource": "s3.amazonaws.com", - "eventTime": "2020-12-22T10:05:29Z", - "eventType": "AwsApiCall", - "eventVersion": "1.07", - "managementEvent": False, - "readOnly": True, - "recipientAccountId": "123456789012", - "requestID": "A123B1C123D1E123", - "requestParameters": { - "Host": "lambda-artifacts-deafc19498e3f2df.s3.us-west-1.amazonaws.com", - "bucketName": "lambda-artifacts-deafc19498e3f2df", - "key": "path1/path2/path3/file.zip", - }, - "resources": [ - { - "ARN": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df/path1/path2/path3/file.zip", - "type": "AWS::S3::Object", - }, - { - "ARN": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df", - "accountId": "123456789012", - "type": "AWS::S3::Bucket", - }, - ], - "responseElements": None, - "sourceIPAddress": "AWS Internal", - "userAgent": "AWS Internal", - "userIdentity": { - "accessKeyId": "ABCDEFGHIJKLMNOPQR12", - "accountId": "123456789012", - "arn": "arn:aws:sts::123456789012:assumed-role/role-name1/1234567890123", - "invokedBy": "AWS Internal", - "principalId": "ABCDEFGHIJKLMN1OPQRST:1234567890123", - "sessionContext": { - "attributes": {"creationDate": "2020-12-09T09:58:24Z", "mfaAuthenticated": "false"}, - "sessionIssuer": { - "accountId": "123456789012", - "arn": "arn:aws:iam::123456789012:role/role-name1", - "principalId": "ABCDEFGHIJKLMN1OPQRST", - "type": "Role", - "userName": "role-name1", - }, - }, - "type": "AssumedRole", - }, - "vpcEndpointId": "vpce-a123cdef", - }, - "detail-type": "AWS API Call via CloudTrail", - "id": "e0bad426-0a70-4424-b53a-eb902ebf5786", - "region": "us-west-1", - "resources": [], - "source": "aws.s3", - "time": "2020-12-22T10:05:29Z", - "version": "0", - } + ```json hl_lines="12 13" + --8<-- "examples/validation/src/custom_format_payload.json" ``` ### Built-in JMESPath functions diff --git a/examples/validation/src/custom_format_function.py b/examples/validation/src/custom_format_function.py new file mode 100644 index 00000000000..bf589018c5c --- /dev/null +++ b/examples/validation/src/custom_format_function.py @@ -0,0 +1,34 @@ +import json +import re + +import boto3 +import custom_format_schema as schemas + +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + +# awsaccountid must have 12 digits +custom_format = {"awsaccountid": lambda value: re.match(r"^(\d{12})$", value)} + + +def lambda_handler(event, context: LambdaContext) -> dict: + try: + # validate input using custom json format + validate(event=event, schema=schemas.INPUT, formats=custom_format) + + client_organization = boto3.client("organizations", region_name=event.get("region")) + account_data = client_organization.describe_account(AccountId=event.get("accountid")) + + return { + "account": json.dumps(account_data.get("Account"), default=str), + "message": "Success", + "statusCode": 200, + } + except SchemaValidationError as exception: + return return_error_message(str(exception)) + except Exception as exception: + return return_error_message(str(exception)) + + +def return_error_message(message: str) -> dict: + return {"account": None, "message": message, "statusCode": 400} diff --git a/examples/validation/src/custom_format_payload.json b/examples/validation/src/custom_format_payload.json new file mode 100644 index 00000000000..8f0607f94b0 --- /dev/null +++ b/examples/validation/src/custom_format_payload.json @@ -0,0 +1,4 @@ +{ + "accountid": "200984112386", + "region": "us-east-1" +} diff --git a/examples/validation/src/custom_format_schema.py b/examples/validation/src/custom_format_schema.py new file mode 100644 index 00000000000..e06a4b35e2d --- /dev/null +++ b/examples/validation/src/custom_format_schema.py @@ -0,0 +1,26 @@ +INPUT = { + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1660245931.json", + "title": "Root", + "type": "object", + "required": ["accountid", "region"], + "properties": { + "accountid": { + "$id": "#root/accountid", + "title": "The accountid", + "type": "string", + "format": "awsaccountid", + "default": "", + "examples": ["123456789012"], + }, + "region": { + "$id": "#root/region", + "title": "The region", + "type": "string", + "default": "", + "examples": ["us-east-1"], + "pattern": "^.*$", + }, + }, +} diff --git a/examples/validation/src/getting_started_validator_decorator_function.py b/examples/validation/src/getting_started_validator_decorator_function.py new file mode 100644 index 00000000000..bc371742860 --- /dev/null +++ b/examples/validation/src/getting_started_validator_decorator_function.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass, field +from uuid import uuid4 + +import getting_started_validator_decorator_schema as schemas + +from aws_lambda_powertools.utilities import parameters +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import validator + +# we can get list of allowed IPs from AWS Parameter Store using Parameters Utility +# See: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parameters/ +ALLOWED_IPS = parameters.get_parameter("/lambda-powertools/allowed_ips") + + +class UserPermissionsError(Exception): + ... + + +@dataclass +class User: + ip: str + permissions: list + user_id: str = field(default_factory=lambda: f"{uuid4()}") + name: str = "Project Lambda Powertools" + + +# using a decorator to validate input and output data +@validator(inbound_schema=schemas.INPUT, outbound_schema=schemas.OUTPUT) +def lambda_handler(event, context: LambdaContext) -> dict: + + try: + user_details: dict = {} + + # get permissions by user_id and project + if ( + event.get("user_id") == "0d44b083-8206-4a3a-aa95-5d392a99be4a" + and event.get("project") == "powertools" + and event.get("ip") in ALLOWED_IPS + ): + user_details = User(ip=event.get("ip"), permissions=["read", "write"]).__dict__ + + # the body must be an object because must match OUTPUT schema, otherwise it fails + return {"body": user_details or None, "statusCode": 200 if user_details else 204} + except Exception as e: + raise UserPermissionsError(str(e)) diff --git a/examples/validation/src/getting_started_validator_decorator_payload.json b/examples/validation/src/getting_started_validator_decorator_payload.json new file mode 100644 index 00000000000..0e8bb8b752b --- /dev/null +++ b/examples/validation/src/getting_started_validator_decorator_payload.json @@ -0,0 +1,6 @@ + +{ + "user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", + "project": "powertools", + "ip": "192.168.0.1" +} diff --git a/examples/validation/src/getting_started_validator_decorator_schema.py b/examples/validation/src/getting_started_validator_decorator_schema.py new file mode 100644 index 00000000000..1f74a2cc711 --- /dev/null +++ b/examples/validation/src/getting_started_validator_decorator_schema.py @@ -0,0 +1,60 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", "project": "powertools", "ip": "192.168.0.1"}], + "required": ["user_id", "project", "ip"], + "properties": { + "user_id": { + "$id": "#/properties/user_id", + "type": "string", + "title": "The user_id", + "examples": ["0d44b083-8206-4a3a-aa95-5d392a99be4a"], + "maxLength": 50, + }, + "project": { + "$id": "#/properties/project", + "type": "string", + "title": "The project", + "examples": ["powertools"], + "maxLength": 30, + }, + "ip": { + "$id": "#/properties/ip", + "type": "string", + "title": "The ip", + "format": "ipv4", + "examples": ["192.168.0.1"], + "maxLength": 30, + }, + }, +} + +OUTPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample outgoing schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"statusCode": 200, "body": {}}], + "required": ["statusCode", "body"], + "properties": { + "statusCode": { + "$id": "#/properties/statusCode", + "type": "integer", + "title": "The statusCode", + "examples": [200], + "maxLength": 3, + }, + "body": { + "$id": "#/properties/body", + "type": "object", + "title": "The body", + "examples": [ + '{"ip": "192.168.0.1", "permissions": ["read", "write"], "user_id": "7576b683-295e-4f69-b558-70e789de1b18", "name": "Project Lambda Powertools"}' # noqa E501 + ], + }, + }, +} diff --git a/examples/validation/src/getting_started_validator_standalone_function.py b/examples/validation/src/getting_started_validator_standalone_function.py new file mode 100644 index 00000000000..1680511766b --- /dev/null +++ b/examples/validation/src/getting_started_validator_standalone_function.py @@ -0,0 +1,30 @@ +import getting_started_validator_standalone_schema as schemas + +from aws_lambda_powertools.utilities import parameters +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + +# we can get list of allowed IPs from AWS Parameter Store using Parameters Utility +# See: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parameters/ +ALLOWED_IPS = parameters.get_parameter("/lambda-powertools/allowed_ips") + + +def lambda_handler(event, context: LambdaContext) -> dict: + try: + user_authenticated: str = "" + + # using standalone function to validate input data only + validate(event=event, schema=schemas.INPUT) + + if ( + event.get("user_id") == "0d44b083-8206-4a3a-aa95-5d392a99be4a" + and event.get("project") == "powertools" + and event.get("ip") in ALLOWED_IPS + ): + user_authenticated = "Allowed" + + # in this example the body can be of any type because we are not validating the OUTPUT + return {"body": user_authenticated, "statusCode": 200 if user_authenticated else 204} + except SchemaValidationError as exception: + # SchemaValidationError indicates where a data mismatch is + return {"body": str(exception), "statusCode": 400} diff --git a/examples/validation/src/getting_started_validator_standalone_payload.json b/examples/validation/src/getting_started_validator_standalone_payload.json new file mode 100644 index 00000000000..0e8bb8b752b --- /dev/null +++ b/examples/validation/src/getting_started_validator_standalone_payload.json @@ -0,0 +1,6 @@ + +{ + "user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", + "project": "powertools", + "ip": "192.168.0.1" +} diff --git a/examples/validation/src/getting_started_validator_standalone_schema.py b/examples/validation/src/getting_started_validator_standalone_schema.py new file mode 100644 index 00000000000..28711157196 --- /dev/null +++ b/examples/validation/src/getting_started_validator_standalone_schema.py @@ -0,0 +1,33 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "http://example.com/example.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [{"user_id": "0d44b083-8206-4a3a-aa95-5d392a99be4a", "powertools": "lessa", "ip": "192.168.0.1"}], + "required": ["user_id", "project", "ip"], + "properties": { + "user_id": { + "$id": "#/properties/user_id", + "type": "string", + "title": "The user_id", + "examples": ["0d44b083-8206-4a3a-aa95-5d392a99be4a"], + "maxLength": 50, + }, + "project": { + "$id": "#/properties/project", + "type": "string", + "title": "The project", + "examples": ["powertools"], + "maxLength": 30, + }, + "ip": { + "$id": "#/properties/ip", + "type": "string", + "title": "The ip", + "format": "ipv4", + "examples": ["192.168.0.1"], + "maxLength": 30, + }, + }, +} diff --git a/examples/validation/src/getting_started_validator_unwrapping_function.py b/examples/validation/src/getting_started_validator_unwrapping_function.py new file mode 100644 index 00000000000..96c66a6f2d3 --- /dev/null +++ b/examples/validation/src/getting_started_validator_unwrapping_function.py @@ -0,0 +1,36 @@ +import boto3 +import getting_started_validator_unwrapping_schema as schemas + +from aws_lambda_powertools.utilities.data_classes.event_bridge_event import EventBridgeEvent +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import validator + +s3_client = boto3.resource("s3") + + +# we use the 'envelope' parameter to extract the payload inside the 'detail' key before validating +@validator(inbound_schema=schemas.INPUT, envelope="detail") +def lambda_handler(event: dict, context: LambdaContext) -> dict: + my_event = EventBridgeEvent(event) + data = my_event.detail.get("data", {}) + s3_bucket, s3_key = data.get("s3_bucket"), data.get("s3_key") + + try: + s3_object = s3_client.Object(bucket_name=s3_bucket, key=s3_key) + payload = s3_object.get()["Body"] + content = payload.read().decode("utf-8") + + return {"message": process_data_object(content), "success": True} + except s3_client.meta.client.exceptions.NoSuchBucket as exception: + return return_error_message(str(exception)) + except s3_client.meta.client.exceptions.NoSuchKey as exception: + return return_error_message(str(exception)) + + +def return_error_message(message: str) -> dict: + return {"message": message, "success": False} + + +def process_data_object(content: str) -> str: + # insert logic here + return "Data OK" diff --git a/examples/validation/src/getting_started_validator_unwrapping_payload.json b/examples/validation/src/getting_started_validator_unwrapping_payload.json new file mode 100644 index 00000000000..7757085361b --- /dev/null +++ b/examples/validation/src/getting_started_validator_unwrapping_payload.json @@ -0,0 +1,17 @@ +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "CustomEvent", + "source": "mycompany.service", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-east-1", + "resources": [], + "detail": { + "data": { + "s3_bucket": "aws-lambda-powertools", + "s3_key": "folder/event.txt", + "file_size": 200, + "file_type": "text/plain" + } + } +} \ No newline at end of file diff --git a/examples/validation/src/getting_started_validator_unwrapping_schema.py b/examples/validation/src/getting_started_validator_unwrapping_schema.py new file mode 100644 index 00000000000..2db9b8a4ab9 --- /dev/null +++ b/examples/validation/src/getting_started_validator_unwrapping_schema.py @@ -0,0 +1,59 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1660222326.json", + "type": "object", + "title": "Sample schema", + "description": "The root schema comprises the entire JSON document.", + "examples": [ + { + "data": { + "s3_bucket": "aws-lambda-powertools", + "s3_key": "event.txt", + "file_size": 200, + "file_type": "text/plain", + } + } + ], + "required": ["data"], + "properties": { + "data": { + "$id": "#root/data", + "title": "Root", + "type": "object", + "required": ["s3_bucket", "s3_key", "file_size", "file_type"], + "properties": { + "s3_bucket": { + "$id": "#root/data/s3_bucket", + "title": "The S3 Bucker", + "type": "string", + "default": "", + "examples": ["aws-lambda-powertools"], + "pattern": "^.*$", + }, + "s3_key": { + "$id": "#root/data/s3_key", + "title": "The S3 Key", + "type": "string", + "default": "", + "examples": ["folder/event.txt"], + "pattern": "^.*$", + }, + "file_size": { + "$id": "#root/data/file_size", + "title": "The file size", + "type": "integer", + "examples": [200], + "default": 0, + }, + "file_type": { + "$id": "#root/data/file_type", + "title": "The file type", + "type": "string", + "default": "", + "examples": ["text/plain"], + "pattern": "^.*$", + }, + }, + } + }, +} diff --git a/examples/validation/src/unwrapping_popular_event_source_function.py b/examples/validation/src/unwrapping_popular_event_source_function.py new file mode 100644 index 00000000000..8afbb5c727f --- /dev/null +++ b/examples/validation/src/unwrapping_popular_event_source_function.py @@ -0,0 +1,24 @@ +import boto3 +import unwrapping_popular_event_source_schema as schemas +from botocore.exceptions import ClientError + +from aws_lambda_powertools.utilities.data_classes.event_bridge_event import EventBridgeEvent +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import envelopes, validator + + +# extracting detail from EventBridge custom event +# see: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/jmespath_functions/#built-in-envelopes +@validator(inbound_schema=schemas.INPUT, envelope=envelopes.EVENTBRIDGE) +def lambda_handler(event: dict, context: LambdaContext) -> dict: + my_event = EventBridgeEvent(event) + ec2_client = boto3.resource("ec2", region_name=my_event.region) + + try: + instance_id = my_event.detail.get("instance_id") + instance = ec2_client.Instance(instance_id) + instance.stop() + + return {"message": f"Successfully stopped {instance_id}", "success": True} + except ClientError as exception: + return {"message": str(exception), "success": False} diff --git a/examples/validation/src/unwrapping_popular_event_source_payload.json b/examples/validation/src/unwrapping_popular_event_source_payload.json new file mode 100644 index 00000000000..271e0be5b27 --- /dev/null +++ b/examples/validation/src/unwrapping_popular_event_source_payload.json @@ -0,0 +1,16 @@ + +{ + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" + ], + "detail": { + "instance_id": "i-042dd005362091826", + "region": "us-east-2" + } +} diff --git a/examples/validation/src/unwrapping_popular_event_source_schema.py b/examples/validation/src/unwrapping_popular_event_source_schema.py new file mode 100644 index 00000000000..0c5cc746250 --- /dev/null +++ b/examples/validation/src/unwrapping_popular_event_source_schema.py @@ -0,0 +1,26 @@ +INPUT = { + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1660233148.json", + "title": "Root", + "type": "object", + "required": ["instance_id", "region"], + "properties": { + "instance_id": { + "$id": "#root/instance_id", + "title": "Instance_id", + "type": "string", + "default": "", + "examples": ["i-042dd005362091826"], + "pattern": "^.*$", + }, + "region": { + "$id": "#root/region", + "title": "Region", + "type": "string", + "default": "", + "examples": ["us-east-1"], + "pattern": "^.*$", + }, + }, +} From 7567c84e0d9b21d7534b6b5f28e94ca96d6d06d8 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 19 Aug 2022 09:05:20 +0000 Subject: [PATCH 40/59] update changelog with latest changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eae7ea6dd3..f61497da279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 * **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) +* **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) ## Features @@ -42,6 +43,7 @@ * **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) * **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) * **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) +* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) From 6930b42dd8f3880c6f599865c91835f2f88b2c4a Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Fri, 19 Aug 2022 16:09:17 +0200 Subject: [PATCH 41/59] chore(tests): refactor E2E logger to ease maintenance, writing tests and parallelization (#1460) --- aws_lambda_powertools/shared/constants.py | 9 ++ poetry.lock | 44 +++-- pyproject.toml | 1 + tests/e2e/logger/conftest.py | 25 +++ tests/e2e/logger/handlers/basic_handler.py | 14 +- .../e2e/logger/handlers/no_context_handler.py | 14 -- tests/e2e/logger/infrastructure.py | 11 ++ tests/e2e/logger/test_logger.py | 150 +++-------------- tests/e2e/metrics/test_metrics.py | 12 +- tests/e2e/utils/data_fetcher/logs.py | 153 +++++++++++++++--- tests/e2e/utils/data_fetcher/metrics.py | 12 +- 11 files changed, 245 insertions(+), 200 deletions(-) create mode 100644 tests/e2e/logger/conftest.py delete mode 100644 tests/e2e/logger/handlers/no_context_handler.py create mode 100644 tests/e2e/logger/infrastructure.py diff --git a/aws_lambda_powertools/shared/constants.py b/aws_lambda_powertools/shared/constants.py index 45b46d236f9..48d94d88f1d 100644 --- a/aws_lambda_powertools/shared/constants.py +++ b/aws_lambda_powertools/shared/constants.py @@ -23,3 +23,12 @@ XRAY_SDK_CORE_MODULE: str = "aws_xray_sdk.core" IDEMPOTENCY_DISABLED_ENV: str = "POWERTOOLS_IDEMPOTENCY_DISABLED" + +LOGGER_LAMBDA_CONTEXT_KEYS = [ + "function_arn", + "function_memory_size", + "function_name", + "function_request_id", + "cold_start", + "xray_trace_id", +] diff --git a/poetry.lock b/poetry.lock index c178b0c694c..905c852476c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -678,7 +678,7 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-material" -version = "8.3.9" +version = "8.4.0" description = "Documentation that simply works" category = "dev" optional = false @@ -774,6 +774,17 @@ python-versions = ">=3.6" [package.dependencies] typing-extensions = ">=4.1.0" +[[package]] +name = "mypy-boto3-logs" +version = "1.24.36.post1" +description = "Type annotations for boto3.CloudWatchLogs 1.24.36 service generated with mypy-boto3-builder 7.10.0" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = ">=4.1.0" + [[package]] name = "mypy-boto3-s3" version = "1.24.36.post1" @@ -950,12 +961,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.12.0" +version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false python-versions = ">=3.6" +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pymdown-extensions" version = "9.5" @@ -1254,7 +1268,7 @@ python-versions = ">=3.6" [[package]] name = "types-requests" -version = "2.28.8" +version = "2.28.9" description = "Typing stubs for requests" category = "dev" optional = false @@ -1265,7 +1279,7 @@ types-urllib3 = "<1.27" [[package]] name = "types-urllib3" -version = "1.26.22" +version = "1.26.23" description = "Typing stubs for urllib3" category = "dev" optional = false @@ -1342,7 +1356,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "f9e26c18e24673e05314f2664f1442157e34b70ba4bdb9f912d149df96003eb9" +content-hash = "77b3593db443d2972a854cf7eaf6643e33315d5da218933f360b33a2e3bb945d" [metadata.files] atomicwrites = [ @@ -1633,8 +1647,8 @@ mkdocs-git-revision-date-plugin = [ {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, ] mkdocs-material = [ - {file = "mkdocs-material-8.3.9.tar.gz", hash = "sha256:dc82b667d2a83f0de581b46a6d0949732ab77e7638b87ea35b770b33bc02e75a"}, - {file = "mkdocs_material-8.3.9-py2.py3-none-any.whl", hash = "sha256:263f2721f3abe533b61f7c8bed435a0462620912742c919821ac2d698b4bfe67"}, + {file = "mkdocs-material-8.4.0.tar.gz", hash = "sha256:6c0a6e6cda8b43956e0c562374588160af8110584a1444f422b1cfd91930f9c7"}, + {file = "mkdocs_material-8.4.0-py2.py3-none-any.whl", hash = "sha256:ef6641e1910d4f217873ac376b4594f3157dca3949901b88b4991ba8e5477577"}, ] mkdocs-material-extensions = [ {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, @@ -1685,6 +1699,10 @@ mypy-boto3-lambda = [ {file = "mypy-boto3-lambda-1.24.0.tar.gz", hash = "sha256:ab425f941d0d50a2b8a20cc13cebe03c3097b122259bf00e7b295d284814bd6f"}, {file = "mypy_boto3_lambda-1.24.0-py3-none-any.whl", hash = "sha256:a286a464513adf50847bda8573f2dc7adc348234827d1ac0200e610ee9a09b80"}, ] +mypy-boto3-logs = [ + {file = "mypy-boto3-logs-1.24.36.post1.tar.gz", hash = "sha256:8b00c2d5328e72023b1d1acd65e7cea7854f07827d23ce21c78391ca74271290"}, + {file = "mypy_boto3_logs-1.24.36.post1-py3-none-any.whl", hash = "sha256:f96257ec06099bfda1ce5f35b410e7fb93fb601bc312e8d7a09b13adaefd23f0"}, +] mypy-boto3-s3 = [ {file = "mypy-boto3-s3-1.24.36.post1.tar.gz", hash = "sha256:3bd7e06f9ade5059eae2181d7a9f1a41e7fa807ad3e94c01c9901838e87e0abe"}, {file = "mypy_boto3_s3-1.24.36.post1-py3-none-any.whl", hash = "sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a"}, @@ -1785,8 +1803,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, - {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, ] pymdown-extensions = [ {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, @@ -2007,12 +2025,12 @@ typed-ast = [ {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] types-requests = [ - {file = "types-requests-2.28.8.tar.gz", hash = "sha256:7a9f7b152d594a1c18dd4932cdd2596b8efbeedfd73caa4e4abb3755805b4685"}, - {file = "types_requests-2.28.8-py3-none-any.whl", hash = "sha256:b0421f9f2d0dd0f8df2c75f974686517ca67473f05b466232d4c6384d765ad7a"}, + {file = "types-requests-2.28.9.tar.gz", hash = "sha256:feaf581bd580497a47fe845d506fa3b91b484cf706ff27774e87659837de9962"}, + {file = "types_requests-2.28.9-py3-none-any.whl", hash = "sha256:86cb66d3de2f53eac5c09adc42cf6547eefbd0c7e1210beca1ee751c35d96083"}, ] types-urllib3 = [ - {file = "types-urllib3-1.26.22.tar.gz", hash = "sha256:b05af90e73889e688094008a97ca95788db8bf3736e2776fd43fb6b171485d94"}, - {file = "types_urllib3-1.26.22-py3-none-any.whl", hash = "sha256:09a8783e1002472e8d1e1f3792d4c5cca1fffebb9b48ee1512aae6d16fe186bc"}, + {file = "types-urllib3-1.26.23.tar.gz", hash = "sha256:b78e819f0e350221d0689a5666162e467ba3910737bafda14b5c2c85e9bb1e56"}, + {file = "types_urllib3-1.26.23-py3-none-any.whl", hash = "sha256:333e675b188a1c1fd980b4b352f9e40572413a4c1ac689c23cd546e96310070a"}, ] typing-extensions = [ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, diff --git a/pyproject.toml b/pyproject.toml index 481652d9c30..ae6e1a5d56a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ mypy-boto3-lambda = "^1.24.0" mypy-boto3-xray = "^1.24.0" mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" } mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-logs = { version = "^1.24.0", python = ">=3.7" } types-requests = "^2.28.8" typing-extensions = { version = "^4.3.0", python = ">=3.7" } python-snappy = "^0.6.1" diff --git a/tests/e2e/logger/conftest.py b/tests/e2e/logger/conftest.py new file mode 100644 index 00000000000..201a5f7dca1 --- /dev/null +++ b/tests/e2e/logger/conftest.py @@ -0,0 +1,25 @@ +import pytest + +from tests.e2e.logger.infrastructure import LoggerStack +from tests.e2e.utils.infrastructure import deploy_once + + +@pytest.fixture(autouse=True, scope="module") +def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): + """Setup and teardown logic for E2E test infrastructure + + Parameters + ---------- + request : pytest.FixtureRequest + pytest request fixture to introspect absolute path to test being executed + tmp_path_factory : pytest.TempPathFactory + pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up + worker_id : str + pytest-xdist worker identification to detect whether parallelization is enabled + + Yields + ------ + Dict[str, str] + CloudFormation Outputs from deployed infrastructure + """ + yield from deploy_once(stack=LoggerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id) diff --git a/tests/e2e/logger/handlers/basic_handler.py b/tests/e2e/logger/handlers/basic_handler.py index 34d7fb4678a..0f0dd46b4aa 100644 --- a/tests/e2e/logger/handlers/basic_handler.py +++ b/tests/e2e/logger/handlers/basic_handler.py @@ -1,17 +1,11 @@ -import os - from aws_lambda_powertools import Logger logger = Logger() -MESSAGE = os.environ["MESSAGE"] -ADDITIONAL_KEY = os.environ["ADDITIONAL_KEY"] - -@logger.inject_lambda_context(log_event=True) +@logger.inject_lambda_context def lambda_handler(event, context): - logger.debug(MESSAGE) - logger.info(MESSAGE) - logger.append_keys(**{ADDITIONAL_KEY: "test"}) - logger.info(MESSAGE) + message, append_keys = event.get("message", ""), event.get("append_keys", {}) + logger.append_keys(**append_keys) + logger.info(message) return "success" diff --git a/tests/e2e/logger/handlers/no_context_handler.py b/tests/e2e/logger/handlers/no_context_handler.py deleted file mode 100644 index 1347ba98d81..00000000000 --- a/tests/e2e/logger/handlers/no_context_handler.py +++ /dev/null @@ -1,14 +0,0 @@ -import os - -from aws_lambda_powertools import Logger - -logger = Logger() - -MESSAGE = os.environ["MESSAGE"] -ADDITIONAL_KEY = os.environ["ADDITIONAL_KEY"] - - -def lambda_handler(event, context): - logger.info(MESSAGE) - logger.append_keys(**{ADDITIONAL_KEY: "test"}) - return "success" diff --git a/tests/e2e/logger/infrastructure.py b/tests/e2e/logger/infrastructure.py new file mode 100644 index 00000000000..76595908206 --- /dev/null +++ b/tests/e2e/logger/infrastructure.py @@ -0,0 +1,11 @@ +from pathlib import Path + +from tests.e2e.utils.infrastructure import BaseInfrastructureV2 + + +class LoggerStack(BaseInfrastructureV2): + def __init__(self, handlers_dir: Path, feature_name: str = "logger") -> None: + super().__init__(feature_name, handlers_dir) + + def create_resources(self): + self.create_lambda_functions() diff --git a/tests/e2e/logger/test_logger.py b/tests/e2e/logger/test_logger.py index 992cf779275..e5c27dd0a8f 100644 --- a/tests/e2e/logger/test_logger.py +++ b/tests/e2e/logger/test_logger.py @@ -1,143 +1,37 @@ -import boto3 +import json +from uuid import uuid4 + import pytest -from e2e import conftest +from aws_lambda_powertools.shared.constants import LOGGER_LAMBDA_CONTEXT_KEYS from tests.e2e.utils import data_fetcher -@pytest.fixture(scope="module") -def config() -> conftest.LambdaConfig: - return { - "parameters": {}, - "environment_variables": { - "MESSAGE": "logger message test", - "LOG_LEVEL": "INFO", - "ADDITIONAL_KEY": "extra_info", - }, - } - - -def test_basic_lambda_logs_visible(execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig): - # GIVEN - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") +@pytest.fixture +def basic_handler_fn(infrastructure: dict) -> str: + return infrastructure.get("BasicHandler", "") - # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) - # THEN - assert any( - log.message == config["environment_variables"]["MESSAGE"] - and log.level == config["environment_variables"]["LOG_LEVEL"] - for log in filtered_logs - ) +@pytest.fixture +def basic_handler_fn_arn(infrastructure: dict) -> str: + return infrastructure.get("BasicHandlerArn", "") -def test_basic_lambda_no_debug_logs_visible( - execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig -): +def test_basic_lambda_logs_visible(basic_handler_fn, basic_handler_fn_arn): # GIVEN - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") + message = "logs should be visible with default settings" + custom_key = "order_id" + additional_keys = {custom_key: f"{uuid4()}"} + payload = json.dumps({"message": message, "append_keys": additional_keys}) # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) + _, execution_time = data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=payload) + data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=payload) # THEN - assert not any( - log.message == config["environment_variables"]["MESSAGE"] and log.level == "DEBUG" for log in filtered_logs - ) - - -def test_basic_lambda_contextual_data_logged(execute_lambda: conftest.InfrastructureOutput): - # GIVEN - required_keys = ( - "xray_trace_id", - "function_request_id", - "function_arn", - "function_memory_size", - "function_name", - "cold_start", - ) - - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") - - # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) - - # THEN - assert all(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_keys) - - -def test_basic_lambda_additional_key_persistence_basic_lambda( - execute_lambda: conftest.InfrastructureOutput, config: conftest.LambdaConfig -): - # GIVEN - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") - - # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) - - # THEN - assert any( - log.extra_info - and log.message == config["environment_variables"]["MESSAGE"] - and log.level == config["environment_variables"]["LOG_LEVEL"] - for log in filtered_logs - ) - - -def test_basic_lambda_empty_event_logged(execute_lambda: conftest.InfrastructureOutput): + logs = data_fetcher.get_logs(function_name=basic_handler_fn, start_time=execution_time) - # GIVEN - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="basichandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") - - # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) - - # THEN - assert any(log.message == {} for log in filtered_logs) - - -def test_no_context_lambda_contextual_data_not_logged(execute_lambda: conftest.InfrastructureOutput): - - # GIVEN - required_missing_keys = ( - "function_request_id", - "function_arn", - "function_memory_size", - "function_name", - "cold_start", - ) - - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="nocontexthandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") - - # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) - - # THEN - assert not any(keys in logs.dict(exclude_unset=True) for logs in filtered_logs for keys in required_missing_keys) - - -def test_no_context_lambda_event_not_logged(execute_lambda: conftest.InfrastructureOutput): - - # GIVEN - lambda_name = execute_lambda.get_lambda_function_name(cf_output_name="nocontexthandlerarn") - timestamp = execute_lambda.get_lambda_execution_time_timestamp() - cw_client = boto3.client("logs") - - # WHEN - filtered_logs = data_fetcher.get_logs(lambda_function_name=lambda_name, start_time=timestamp, log_client=cw_client) - - # THEN - assert not any(log.message == {} for log in filtered_logs) + assert len(logs) == 2 + assert len(logs.get_cold_start_log()) == 1 + assert len(logs.get_log(key=custom_key)) == 2 + assert logs.have_keys(*LOGGER_LAMBDA_CONTEXT_KEYS) is True diff --git a/tests/e2e/metrics/test_metrics.py b/tests/e2e/metrics/test_metrics.py index 01d1ba2fbf1..516f93ac1f0 100644 --- a/tests/e2e/metrics/test_metrics.py +++ b/tests/e2e/metrics/test_metrics.py @@ -39,13 +39,12 @@ def test_basic_lambda_metric_is_visible(basic_handler_fn: str, basic_handler_fn_ event = json.dumps({"metrics": metrics, "service": service, "namespace": METRIC_NAMESPACE}) _, execution_time = data_fetcher.get_lambda_response(lambda_arn=basic_handler_fn_arn, payload=event) - my_metrics = data_fetcher.get_metrics( + metric_values = data_fetcher.get_metrics( namespace=METRIC_NAMESPACE, start_date=execution_time, metric_name=metric_name, dimensions=dimensions ) # THEN - metric_data = my_metrics.get("Values", []) - assert metric_data and metric_data[0] == 3.0 + assert metric_values == [3.0] def test_cold_start_metric(cold_start_fn_arn: str, cold_start_fn: str): @@ -58,12 +57,11 @@ def test_cold_start_metric(cold_start_fn_arn: str, cold_start_fn: str): event = json.dumps({"service": service, "namespace": METRIC_NAMESPACE}) _, execution_time = data_fetcher.get_lambda_response(lambda_arn=cold_start_fn_arn, payload=event) - _, _ = data_fetcher.get_lambda_response(lambda_arn=cold_start_fn_arn, payload=event) + data_fetcher.get_lambda_response(lambda_arn=cold_start_fn_arn, payload=event) - my_metrics = data_fetcher.get_metrics( + metric_values = data_fetcher.get_metrics( namespace=METRIC_NAMESPACE, start_date=execution_time, metric_name=metric_name, dimensions=dimensions ) # THEN - metric_data = my_metrics.get("Values", []) - assert metric_data and metric_data[0] == 1.0 + assert metric_values == [1.0] diff --git a/tests/e2e/utils/data_fetcher/logs.py b/tests/e2e/utils/data_fetcher/logs.py index e8211eeea30..a005009f5f5 100644 --- a/tests/e2e/utils/data_fetcher/logs.py +++ b/tests/e2e/utils/data_fetcher/logs.py @@ -1,13 +1,14 @@ import json -from functools import lru_cache +from datetime import datetime from typing import List, Optional, Union -from mypy_boto3_cloudwatch import CloudWatchClient -from pydantic import BaseModel +import boto3 +from mypy_boto3_logs import CloudWatchLogsClient +from pydantic import BaseModel, Extra from retry import retry -class Log(BaseModel): +class Log(BaseModel, extra=Extra.allow): level: str location: str message: Union[dict, str] @@ -19,21 +20,129 @@ class Log(BaseModel): function_arn: Optional[str] function_request_id: Optional[str] xray_trace_id: Optional[str] - extra_info: Optional[str] - - -@lru_cache(maxsize=10, typed=False) -@retry(ValueError, delay=1, jitter=1, tries=20) -def get_logs(lambda_function_name: str, log_client: CloudWatchClient, start_time: int, **kwargs: dict) -> List[Log]: - response = log_client.filter_log_events(logGroupName=f"/aws/lambda/{lambda_function_name}", startTime=start_time) - if not response["events"]: - raise ValueError("Empty response from Cloudwatch Logs. Repeating...") - filtered_logs = [] - for event in response["events"]: - try: - message = Log(**json.loads(event["message"])) - except json.decoder.JSONDecodeError: - continue - filtered_logs.append(message) - - return filtered_logs + + +class LogFetcher: + def __init__( + self, + function_name: str, + start_time: datetime, + log_client: Optional[CloudWatchLogsClient] = None, + filter_expression: Optional[str] = None, + ): + """Fetch and expose Powertools Logger logs from CloudWatch Logs + + Parameters + ---------- + function_name : str + Name of Lambda function to fetch logs for + start_time : datetime + Start date range to filter traces + log_client : Optional[CloudWatchLogsClient], optional + Amazon CloudWatch Logs Client, by default boto3.client('logs) + filter_expression : Optional[str], optional + CloudWatch Logs Filter Pattern expression, by default "message" + """ + self.function_name = function_name + self.start_time = int(start_time.timestamp()) + self.log_client = log_client or boto3.client("logs") + self.filter_expression = filter_expression or "message" # Logger message key + self.log_group = f"/aws/lambda/{self.function_name}" + self.logs: List[Log] = self._get_logs() + + def get_log(self, key: str, value: Optional[any] = None) -> List[Log]: + """Get logs based on key or key and value + + Parameters + ---------- + key : str + Log key name + value : Optional[any], optional + Log value, by default None + + Returns + ------- + List[Log] + List of Log instances + """ + logs = [] + for log in self.logs: + log_value = getattr(log, key, None) + if value is not None and log_value == value: + logs.append(log) + elif value is None and hasattr(log, key): + logs.append(log) + return logs + + def get_cold_start_log(self) -> List[Log]: + """Get logs where cold start was true + + Returns + ------- + List[Log] + List of Log instances + """ + return [log for log in self.logs if log.cold_start] + + def have_keys(self, *keys) -> bool: + """Whether an arbitrary number of key names exist in each log event + + Returns + ------- + bool + Whether keys are present + """ + return all(hasattr(log, key) for log in self.logs for key in keys) + + def _get_logs(self) -> List[Log]: + ret = self.log_client.filter_log_events( + logGroupName=self.log_group, + startTime=self.start_time, + filterPattern=self.filter_expression, + ) + + if not ret["events"]: + raise ValueError("Empty response from Cloudwatch Logs. Repeating...") + + filtered_logs = [] + for event in ret["events"]: + try: + message = Log(**json.loads(event["message"])) + except json.decoder.JSONDecodeError: + continue + filtered_logs.append(message) + + return filtered_logs + + def __len__(self) -> int: + return len(self.logs) + + +@retry(ValueError, delay=2, jitter=1.5, tries=10) +def get_logs( + function_name: str, + start_time: datetime, + filter_expression: Optional[str] = None, + log_client: Optional[CloudWatchLogsClient] = None, +) -> LogFetcher: + """_summary_ + + Parameters + ---------- + function_name : str + Name of Lambda function to fetch logs for + start_time : datetime + Start date range to filter traces + log_client : Optional[CloudWatchLogsClient], optional + Amazon CloudWatch Logs Client, by default boto3.client('logs) + filter_expression : Optional[str], optional + CloudWatch Logs Filter Pattern expression, by default "message" + + Returns + ------- + LogFetcher + LogFetcher instance with logs available as properties and methods + """ + return LogFetcher( + function_name=function_name, start_time=start_time, filter_expression=filter_expression, log_client=log_client + ) diff --git a/tests/e2e/utils/data_fetcher/metrics.py b/tests/e2e/utils/data_fetcher/metrics.py index 5a017f0a845..18023b18336 100644 --- a/tests/e2e/utils/data_fetcher/metrics.py +++ b/tests/e2e/utils/data_fetcher/metrics.py @@ -3,7 +3,7 @@ import boto3 from mypy_boto3_cloudwatch import CloudWatchClient -from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef, MetricDataResultTypeDef +from mypy_boto3_cloudwatch.type_defs import DimensionTypeDef from retry import retry from tests.e2e.utils.data_builder import build_metric_query_data @@ -19,7 +19,7 @@ def get_metrics( end_date: Optional[datetime] = None, period: int = 60, stat: str = "Sum", -) -> MetricDataResultTypeDef: +) -> List[float]: """Fetch CloudWatch Metrics It takes into account eventual consistency with up to 10 retries and 1.5s jitter. @@ -45,8 +45,8 @@ def get_metrics( Returns ------- - MetricDataResultTypeDef - Dict with metric values found + List[float] + List with metric values found Raises ------ @@ -65,7 +65,7 @@ def get_metrics( StartTime=start_date, EndTime=end_date or datetime.utcnow(), ) - result = response["MetricDataResults"][0] - if not result["Values"]: + result = response["MetricDataResults"][0]["Values"] + if not result: raise ValueError("Empty response from Cloudwatch. Repeating...") return result From 54c051d6deec797322bd42031e626cde04eb44fb Mon Sep 17 00:00:00 2001 From: Ruben Fonseca Date: Fri, 19 Aug 2022 19:42:07 +0200 Subject: [PATCH 42/59] chore(batch): deprecate sqs_batch_processor (#1463) --- aws_lambda_powertools/utilities/batch/sqs.py | 9 +++++++++ docs/overrides/main.html | 5 +++++ docs/utilities/batch.md | 5 +++++ mkdocs.yml | 1 + 4 files changed, 20 insertions(+) diff --git a/aws_lambda_powertools/utilities/batch/sqs.py b/aws_lambda_powertools/utilities/batch/sqs.py index 0848e327ea6..7b234c1372e 100644 --- a/aws_lambda_powertools/utilities/batch/sqs.py +++ b/aws_lambda_powertools/utilities/batch/sqs.py @@ -6,6 +6,7 @@ import logging import math import sys +import warnings from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Any, Callable, Dict, List, Optional, Tuple, cast @@ -77,6 +78,14 @@ def __init__( self.suppress_exception = suppress_exception self.max_message_batch = 10 + warnings.warn( + "The sqs_batch_processor decorator and PartialSQSProcessor class are now deprecated, " + "and will be removed in the next major version. " + "Please follow the upgrade guide at " + "https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/batch/#legacy " + "to use the native batch_processor decorator or BatchProcessor class." + ) + super().__init__() def _get_queue_url(self) -> Optional[str]: diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 0af326afb24..e7908bbfa32 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -1,5 +1,10 @@ {% extends "base.html" %} +{% block announce %} + 👋 Powertools for Python v2 is coming soon! + We encourage you to add your feedback and follow the progress on the RFC. +{% endblock %} + {% block outdated %} You're not viewing the latest version. diff --git a/docs/utilities/batch.md b/docs/utilities/batch.md index ce2e76e25d4..6241179ed4e 100644 --- a/docs/utilities/batch.md +++ b/docs/utilities/batch.md @@ -5,6 +5,11 @@ description: Utility The batch processing utility handles partial failures when processing batches from Amazon SQS, Amazon Kinesis Data Streams, and Amazon DynamoDB Streams. +???+ warning + The legacy `sqs_batch_processor` decorator and `PartialSQSProcessor` class are deprecated and are going to be removed soon. + + Please check the [migration guide](#migration-guide) for more information. + ## Key Features * Reports batch item failures to reduce number of retries for a record upon errors diff --git a/mkdocs.yml b/mkdocs.yml index 5b08b31a81d..171cf36eb13 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -47,6 +47,7 @@ theme: icon: material/toggle-switch name: Switch to light mode features: + - header.autohide - navigation.sections - navigation.expand - navigation.top From 5e4941091dfbc31432d8519e78157f5b2c20ca47 Mon Sep 17 00:00:00 2001 From: Release bot Date: Fri, 19 Aug 2022 17:42:37 +0000 Subject: [PATCH 43/59] update changelog with latest changes --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f61497da279..c4ba99f3ff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,17 +33,19 @@ ## Maintenance -* **ci:** reduce payload and only send prod notification -* **ci:** update changelog with latest changes +* **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/awslabs/aws-lambda-powertools-python/issues/1463)) * **ci:** remove conventional changelog commit to reduce noise -* **ci:** remove area/utilities conflicting label * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes +* **ci:** reduce payload and only send prod notification +* **ci:** remove area/utilities conflicting label +* **ci:** update changelog with latest changes * **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/awslabs/aws-lambda-powertools-python/issues/1458)) * **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) * **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) * **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) * **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) +* **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/awslabs/aws-lambda-powertools-python/issues/1460)) From 122f989de9f7921cb78657ad3a92067ea7028e1a Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Mon, 22 Aug 2022 16:47:09 +0200 Subject: [PATCH 44/59] chore(tests): build and deploy Lambda Layer stack once (#1466) --- Makefile | 2 +- poetry.lock | 78 +++---- pyproject.toml | 16 +- tests/e2e/conftest.py | 97 +++------ tests/e2e/logger/conftest.py | 17 +- tests/e2e/logger/infrastructure.py | 10 +- tests/e2e/metrics/conftest.py | 17 +- tests/e2e/metrics/infrastructure.py | 10 +- tests/e2e/tracer/conftest.py | 17 +- tests/e2e/tracer/infrastructure.py | 9 +- tests/e2e/utils/Dockerfile | 6 +- tests/e2e/utils/asset.py | 8 +- tests/e2e/utils/infrastructure.py | 307 ++++++++-------------------- tests/utils.py | 0 14 files changed, 218 insertions(+), 376 deletions(-) delete mode 100644 tests/utils.py diff --git a/Makefile b/Makefile index 94f9fc975b8..3977ee8e7a7 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ unit-test: poetry run pytest tests/unit e2e-test: - poetry run pytest -rP -n 3 --dist loadscope --durations=0 --durations-min=1 tests/e2e + poetry run pytest -rP -n auto --dist loadfile -o log_cli=true tests/e2e coverage-html: poetry run pytest -m "not perf" --ignore tests/e2e --cov=aws_lambda_powertools --cov-report=html diff --git a/poetry.lock b/poetry.lock index 905c852476c..9b94cb96cfe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -678,7 +678,7 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-material" -version = "8.4.0" +version = "8.4.1" description = "Documentation that simply works" category = "dev" optional = false @@ -721,11 +721,11 @@ reports = ["lxml"] [[package]] name = "mypy-boto3-appconfig" -version = "1.24.29" -description = "Type annotations for boto3.AppConfig 1.24.29 service generated with mypy-boto3-builder 7.7.3" +version = "1.24.36.post1" +description = "Type annotations for boto3.AppConfig 1.24.36 service generated with mypy-boto3-builder 7.10.0" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" @@ -743,33 +743,33 @@ typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-cloudwatch" -version = "1.24.35" -description = "Type annotations for boto3.CloudWatch 1.24.35 service generated with mypy-boto3-builder 7.9.2" +version = "1.24.55" +description = "Type annotations for boto3.CloudWatch 1.24.55 service generated with mypy-boto3-builder 7.11.6" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-dynamodb" -version = "1.24.27" -description = "Type annotations for boto3.DynamoDB 1.24.27 service generated with mypy-boto3-builder 7.6.0" +version = "1.24.55.post1" +description = "Type annotations for boto3.DynamoDB 1.24.55 service generated with mypy-boto3-builder 7.11.7" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-lambda" -version = "1.24.0" -description = "Type annotations for boto3.Lambda 1.24.0 service generated with mypy-boto3-builder 7.6.1" +version = "1.24.54" +description = "Type annotations for boto3.Lambda 1.24.54 service generated with mypy-boto3-builder 7.11.6" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" @@ -798,33 +798,33 @@ typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-secretsmanager" -version = "1.24.11.post3" -description = "Type annotations for boto3.SecretsManager 1.24.11 service generated with mypy-boto3-builder 7.7.1" +version = "1.24.54" +description = "Type annotations for boto3.SecretsManager 1.24.54 service generated with mypy-boto3-builder 7.11.6" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-ssm" -version = "1.24.0" -description = "Type annotations for boto3.SSM 1.24.0 service generated with mypy-boto3-builder 7.6.1" +version = "1.24.39.post2" +description = "Type annotations for boto3.SSM 1.24.39 service generated with mypy-boto3-builder 7.10.1" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" [[package]] name = "mypy-boto3-xray" -version = "1.24.0" -description = "Type annotations for boto3.XRay 1.24.0 service generated with mypy-boto3-builder 7.6.1" +version = "1.24.36.post1" +description = "Type annotations for boto3.XRay 1.24.36 service generated with mypy-boto3-builder 7.10.0" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = ">=4.1.0" @@ -1356,7 +1356,7 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "77b3593db443d2972a854cf7eaf6643e33315d5da218933f360b33a2e3bb945d" +content-hash = "0a9e21c2f15825934ad6c786121da020c4a964c5a0dd138e0e8ae09c0865a055" [metadata.files] atomicwrites = [ @@ -1647,8 +1647,8 @@ mkdocs-git-revision-date-plugin = [ {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, ] mkdocs-material = [ - {file = "mkdocs-material-8.4.0.tar.gz", hash = "sha256:6c0a6e6cda8b43956e0c562374588160af8110584a1444f422b1cfd91930f9c7"}, - {file = "mkdocs_material-8.4.0-py2.py3-none-any.whl", hash = "sha256:ef6641e1910d4f217873ac376b4594f3157dca3949901b88b4991ba8e5477577"}, + {file = "mkdocs-material-8.4.1.tar.gz", hash = "sha256:92c70f94b2e1f8a05d9e05eec1c7af9dffc516802d69222329db89503c97b4f3"}, + {file = "mkdocs_material-8.4.1-py2.py3-none-any.whl", hash = "sha256:319a6254819ce9d864ff79de48c43842fccfdebb43e4e6820eef75216f8cfb0a"}, ] mkdocs-material-extensions = [ {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, @@ -1680,24 +1680,24 @@ mypy = [ {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, ] mypy-boto3-appconfig = [ - {file = "mypy-boto3-appconfig-1.24.29.tar.gz", hash = "sha256:10583d309a9db99babfbe85d3b6467b49b3509a57e4f8771da239f6d5cb3731b"}, - {file = "mypy_boto3_appconfig-1.24.29-py3-none-any.whl", hash = "sha256:e9d9e2e25fdd82bffc6262dc184edf5d0d3d9fbb0ab35e597a1ea57ba13d4d80"}, + {file = "mypy-boto3-appconfig-1.24.36.post1.tar.gz", hash = "sha256:e1916b3754915cb411ef977083500e1f30f81f7b3aea6ff5eed1cec91944dea6"}, + {file = "mypy_boto3_appconfig-1.24.36.post1-py3-none-any.whl", hash = "sha256:a5dbe549dbebf4bc7a6cfcbfa9dff89ceb4983c042b785763ee656504bdb49f6"}, ] mypy-boto3-cloudformation = [ {file = "mypy-boto3-cloudformation-1.24.36.post1.tar.gz", hash = "sha256:ed7df9ae3a8390a145229122a1489d0a58bbf9986cb54f0d7a65ed54f12c8e63"}, {file = "mypy_boto3_cloudformation-1.24.36.post1-py3-none-any.whl", hash = "sha256:b39020c13a876bb18908aad22326478d0ac3faec0bdac0d2c11dc318c9dcf149"}, ] mypy-boto3-cloudwatch = [ - {file = "mypy-boto3-cloudwatch-1.24.35.tar.gz", hash = "sha256:92a818e2ea330f9afb5f8f9c15df47934736041e3ccfd696ffc0774bad14e0aa"}, - {file = "mypy_boto3_cloudwatch-1.24.35-py3-none-any.whl", hash = "sha256:28947763d70cdac24aca25779cd5b00cd995636f5815fac3d95009430ce02b72"}, + {file = "mypy-boto3-cloudwatch-1.24.55.tar.gz", hash = "sha256:f8950de7a93b3db890cd8524514a2245d9b5fd83ce2dd60a37047a2cd42d5dd6"}, + {file = "mypy_boto3_cloudwatch-1.24.55-py3-none-any.whl", hash = "sha256:23faf8fdfe928f9dcce453a60b03bda69177554eb88c2d7e5240ff91b5b14388"}, ] mypy-boto3-dynamodb = [ - {file = "mypy-boto3-dynamodb-1.24.27.tar.gz", hash = "sha256:c982d24f9b2525a70f408ad40eff69660d56928217597d88860b60436b25efbf"}, - {file = "mypy_boto3_dynamodb-1.24.27-py3-none-any.whl", hash = "sha256:63f7d9755fc5cf2e637edf8d33024050152a53013d1a102716ae0d534563ef07"}, + {file = "mypy-boto3-dynamodb-1.24.55.post1.tar.gz", hash = "sha256:c469223c15556d93d247d38c0c31ce3c08d8073ca4597158a27abc70b8d7fbee"}, + {file = "mypy_boto3_dynamodb-1.24.55.post1-py3-none-any.whl", hash = "sha256:c762975d023b356c573d58105c7bfc1b9e7ee62c1299f09784e9dede533179e1"}, ] mypy-boto3-lambda = [ - {file = "mypy-boto3-lambda-1.24.0.tar.gz", hash = "sha256:ab425f941d0d50a2b8a20cc13cebe03c3097b122259bf00e7b295d284814bd6f"}, - {file = "mypy_boto3_lambda-1.24.0-py3-none-any.whl", hash = "sha256:a286a464513adf50847bda8573f2dc7adc348234827d1ac0200e610ee9a09b80"}, + {file = "mypy-boto3-lambda-1.24.54.tar.gz", hash = "sha256:c76d28d84bdf94c8980acd85bc07f2747559ca11a990fd6785c9c2389e13aff1"}, + {file = "mypy_boto3_lambda-1.24.54-py3-none-any.whl", hash = "sha256:231b6aac22b107ebb7afa2ec6dc1311b769dbdd5bfae957cf60db3e8bc3133d7"}, ] mypy-boto3-logs = [ {file = "mypy-boto3-logs-1.24.36.post1.tar.gz", hash = "sha256:8b00c2d5328e72023b1d1acd65e7cea7854f07827d23ce21c78391ca74271290"}, @@ -1708,16 +1708,16 @@ mypy-boto3-s3 = [ {file = "mypy_boto3_s3-1.24.36.post1-py3-none-any.whl", hash = "sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a"}, ] mypy-boto3-secretsmanager = [ - {file = "mypy-boto3-secretsmanager-1.24.11.post3.tar.gz", hash = "sha256:f153b3f5ff2c65664a906fb2c97a6598a57da9f1da77679dbaf541051dcff36e"}, - {file = "mypy_boto3_secretsmanager-1.24.11.post3-py3-none-any.whl", hash = "sha256:d9655d568f7fd8fe05265613b85fba55ab6e4dcd078989af1ef9f0ffe4b45019"}, + {file = "mypy-boto3-secretsmanager-1.24.54.tar.gz", hash = "sha256:a846b79f86e218a794dbc858c08290bb6aebffa180c80cf0a463c32a04621ff1"}, + {file = "mypy_boto3_secretsmanager-1.24.54-py3-none-any.whl", hash = "sha256:b89c9a0ff65a8ab2c4e4d3f6e721a0477b7d0fec246ffc08e4378420eb50b4d0"}, ] mypy-boto3-ssm = [ - {file = "mypy-boto3-ssm-1.24.0.tar.gz", hash = "sha256:bab58398947c3627a4e7610cd0f57b525c12fd1d0a6bb862400b6af0a4e684fc"}, - {file = "mypy_boto3_ssm-1.24.0-py3-none-any.whl", hash = "sha256:1f17055abb8d70f25e6ece2ef4c0dc74d585744c25a3a833c2985d74165ac0c6"}, + {file = "mypy-boto3-ssm-1.24.39.post2.tar.gz", hash = "sha256:2859bdcef110d9cc53007a7adba9c765e804b886f98d742a496bb8f7dac07308"}, + {file = "mypy_boto3_ssm-1.24.39.post2-py3-none-any.whl", hash = "sha256:bfdb434c513fbb1f3bc4b5c158ed4e7a46cb578e5eb01e818d45f4f38296ef2c"}, ] mypy-boto3-xray = [ - {file = "mypy-boto3-xray-1.24.0.tar.gz", hash = "sha256:fbe211b7601684a2d4defa2f959286f1441027c15044c0c0013257e22307778a"}, - {file = "mypy_boto3_xray-1.24.0-py3-none-any.whl", hash = "sha256:6b9bc96e7924215fe833fe0d732d5e3ce98f7739b373432b9735a9905f867171"}, + {file = "mypy-boto3-xray-1.24.36.post1.tar.gz", hash = "sha256:104f1ecf7f1f6278c582201e71a7ab64843d3a3fdc8f23295cf68788cc77e9bb"}, + {file = "mypy_boto3_xray-1.24.36.post1-py3-none-any.whl", hash = "sha256:97b9f0686c717c8be99ac06cb52febaf71712b4e4cd0b61ed2eb5ed012a9b5fd"}, ] 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 ae6e1a5d56a..b12fdcc092a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,20 +52,20 @@ flake8-bugbear = "^22.7.1" mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^0.6.0" mypy = "^0.971" -mypy-boto3-secretsmanager = "^1.24.11" -mypy-boto3-ssm = "^1.24.0" -mypy-boto3-appconfig = "^1.24.29" -mypy-boto3-dynamodb = "^1.24.27" retry = "^0.9.2" pytest-xdist = "^2.5.0" aws-cdk-lib = "^2.23.0" pytest-benchmark = "^3.4.1" -mypy-boto3-cloudwatch = "^1.24.35" -mypy-boto3-lambda = "^1.24.0" -mypy-boto3-xray = "^1.24.0" -mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-appconfig = { version = "^1.24.29", python = ">=3.7" } mypy-boto3-cloudformation = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-cloudwatch = { version = "^1.24.35", python = ">=3.7" } +mypy-boto3-dynamodb = { version = "^1.24.27", python = ">=3.7" } +mypy-boto3-lambda = { version = "^1.24.0", python = ">=3.7" } mypy-boto3-logs = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-secretsmanager = { version = "^1.24.11", python = ">=3.7" } +mypy-boto3-ssm = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-s3 = { version = "^1.24.0", python = ">=3.7" } +mypy-boto3-xray = { version = "^1.24.0", python = ">=3.7" } types-requests = "^2.28.8" typing-extensions = { version = "^4.3.0", python = ">=3.7" } python-snappy = "^0.6.1" diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 3865be4d3e7..ac55d373e63 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -1,66 +1,35 @@ -import datetime -import sys -import uuid -from dataclasses import dataclass - -import boto3 - -from tests.e2e.utils import data_fetcher, infrastructure - -# We only need typing_extensions for python versions <3.8 -if sys.version_info >= (3, 8): - from typing import TypedDict -else: - from typing_extensions import TypedDict - -from typing import Dict, Generator, Optional - import pytest - -class LambdaConfig(TypedDict): - parameters: dict - environment_variables: Dict[str, str] - - -@dataclass -class InfrastructureOutput: - arns: Dict[str, str] - execution_time: datetime.datetime - - def get_lambda_arns(self) -> Dict[str, str]: - return self.arns - - def get_lambda_function_arn(self, cf_output_name: str) -> Optional[str]: - return self.arns.get(cf_output_name) - - def get_lambda_function_name(self, cf_output_name: str) -> Optional[str]: - lambda_arn = self.get_lambda_function_arn(cf_output_name=cf_output_name) - return lambda_arn.split(":")[-1] if lambda_arn else None - - def get_lambda_execution_time(self) -> datetime.datetime: - return self.execution_time - - def get_lambda_execution_time_timestamp(self) -> int: - return int(self.execution_time.timestamp() * 1000) - - -@pytest.fixture(scope="module") -def create_infrastructure(config, request) -> Generator[Dict[str, str], None, None]: - stack_name = f"test-lambda-{uuid.uuid4()}" - test_dir = request.fspath.dirname - handlers_dir = f"{test_dir}/handlers/" - - infra = infrastructure.Infrastructure(stack_name=stack_name, handlers_dir=handlers_dir, config=config) - yield infra.deploy(Stack=infrastructure.InfrastructureStack) - infra.delete() - - -@pytest.fixture(scope="module") -def execute_lambda(create_infrastructure) -> InfrastructureOutput: - execution_time = datetime.datetime.utcnow() - session = boto3.Session() - client = session.client("lambda") - for _, arn in create_infrastructure.items(): - data_fetcher.get_lambda_response(lambda_arn=arn, client=client) - return InfrastructureOutput(arns=create_infrastructure, execution_time=execution_time) +from tests.e2e.utils.infrastructure import LambdaLayerStack, deploy_once + + +@pytest.fixture(scope="session") +def lambda_layer_arn(lambda_layer_deployment): + yield lambda_layer_deployment.get("LayerArn") + + +@pytest.fixture(scope="session") +def lambda_layer_deployment(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): + """Setup and teardown logic for E2E test infrastructure + + Parameters + ---------- + request : pytest.FixtureRequest + pytest request fixture to introspect absolute path to test being executed + tmp_path_factory : pytest.TempPathFactory + pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up + worker_id : str + pytest-xdist worker identification to detect whether parallelization is enabled + + Yields + ------ + Dict[str, str] + CloudFormation Outputs from deployed infrastructure + """ + yield from deploy_once( + stack=LambdaLayerStack, + request=request, + tmp_path_factory=tmp_path_factory, + worker_id=worker_id, + layer_arn="", + ) diff --git a/tests/e2e/logger/conftest.py b/tests/e2e/logger/conftest.py index 201a5f7dca1..82a89314258 100644 --- a/tests/e2e/logger/conftest.py +++ b/tests/e2e/logger/conftest.py @@ -1,25 +1,28 @@ +from pathlib import Path + import pytest from tests.e2e.logger.infrastructure import LoggerStack -from tests.e2e.utils.infrastructure import deploy_once @pytest.fixture(autouse=True, scope="module") -def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): +def infrastructure(request: pytest.FixtureRequest, lambda_layer_arn: str): """Setup and teardown logic for E2E test infrastructure Parameters ---------- request : pytest.FixtureRequest pytest request fixture to introspect absolute path to test being executed - tmp_path_factory : pytest.TempPathFactory - pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up - worker_id : str - pytest-xdist worker identification to detect whether parallelization is enabled + lambda_layer_arn : str + Lambda Layer ARN Yields ------ Dict[str, str] CloudFormation Outputs from deployed infrastructure """ - yield from deploy_once(stack=LoggerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id) + stack = LoggerStack(handlers_dir=Path(f"{request.path.parent}/handlers"), layer_arn=lambda_layer_arn) + try: + yield stack.deploy() + finally: + stack.delete() diff --git a/tests/e2e/logger/infrastructure.py b/tests/e2e/logger/infrastructure.py index 76595908206..68aaa8eb38a 100644 --- a/tests/e2e/logger/infrastructure.py +++ b/tests/e2e/logger/infrastructure.py @@ -1,11 +1,13 @@ from pathlib import Path -from tests.e2e.utils.infrastructure import BaseInfrastructureV2 +from tests.e2e.utils.infrastructure import BaseInfrastructure -class LoggerStack(BaseInfrastructureV2): - def __init__(self, handlers_dir: Path, feature_name: str = "logger") -> None: - super().__init__(feature_name, handlers_dir) +class LoggerStack(BaseInfrastructure): + FEATURE_NAME = "logger" + + def __init__(self, handlers_dir: Path, feature_name: str = FEATURE_NAME, layer_arn: str = "") -> None: + super().__init__(feature_name, handlers_dir, layer_arn) def create_resources(self): self.create_lambda_functions() diff --git a/tests/e2e/metrics/conftest.py b/tests/e2e/metrics/conftest.py index 18f4564e714..663c8845be4 100644 --- a/tests/e2e/metrics/conftest.py +++ b/tests/e2e/metrics/conftest.py @@ -1,25 +1,28 @@ +from pathlib import Path + import pytest from tests.e2e.metrics.infrastructure import MetricsStack -from tests.e2e.utils.infrastructure import deploy_once @pytest.fixture(autouse=True, scope="module") -def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): +def infrastructure(request: pytest.FixtureRequest, lambda_layer_arn: str): """Setup and teardown logic for E2E test infrastructure Parameters ---------- request : pytest.FixtureRequest pytest request fixture to introspect absolute path to test being executed - tmp_path_factory : pytest.TempPathFactory - pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up - worker_id : str - pytest-xdist worker identification to detect whether parallelization is enabled + lambda_layer_arn : str + Lambda Layer ARN Yields ------ Dict[str, str] CloudFormation Outputs from deployed infrastructure """ - yield from deploy_once(stack=MetricsStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id) + stack = MetricsStack(handlers_dir=Path(f"{request.path.parent}/handlers"), layer_arn=lambda_layer_arn) + try: + yield stack.deploy() + finally: + stack.delete() diff --git a/tests/e2e/metrics/infrastructure.py b/tests/e2e/metrics/infrastructure.py index e01fb16b02e..9afa59bb5cd 100644 --- a/tests/e2e/metrics/infrastructure.py +++ b/tests/e2e/metrics/infrastructure.py @@ -1,11 +1,13 @@ from pathlib import Path -from tests.e2e.utils.infrastructure import BaseInfrastructureV2 +from tests.e2e.utils.infrastructure import BaseInfrastructure -class MetricsStack(BaseInfrastructureV2): - def __init__(self, handlers_dir: Path, feature_name: str = "metrics") -> None: - super().__init__(feature_name, handlers_dir) +class MetricsStack(BaseInfrastructure): + FEATURE_NAME = "metrics" + + def __init__(self, handlers_dir: Path, feature_name: str = FEATURE_NAME, layer_arn: str = "") -> None: + super().__init__(feature_name, handlers_dir, layer_arn) def create_resources(self): self.create_lambda_functions() diff --git a/tests/e2e/tracer/conftest.py b/tests/e2e/tracer/conftest.py index 599d7ab4ca8..3b724bf1247 100644 --- a/tests/e2e/tracer/conftest.py +++ b/tests/e2e/tracer/conftest.py @@ -1,25 +1,28 @@ +from pathlib import Path + import pytest from tests.e2e.tracer.infrastructure import TracerStack -from tests.e2e.utils.infrastructure import deploy_once @pytest.fixture(autouse=True, scope="module") -def infrastructure(request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str): +def infrastructure(request: pytest.FixtureRequest, lambda_layer_arn: str): """Setup and teardown logic for E2E test infrastructure Parameters ---------- request : pytest.FixtureRequest pytest request fixture to introspect absolute path to test being executed - tmp_path_factory : pytest.TempPathFactory - pytest temporary path factory to discover shared tmp when multiple CPU processes are spun up - worker_id : str - pytest-xdist worker identification to detect whether parallelization is enabled + lambda_layer_arn : str + Lambda Layer ARN Yields ------ Dict[str, str] CloudFormation Outputs from deployed infrastructure """ - yield from deploy_once(stack=TracerStack, request=request, tmp_path_factory=tmp_path_factory, worker_id=worker_id) + stack = TracerStack(handlers_dir=Path(f"{request.path.parent}/handlers"), layer_arn=lambda_layer_arn) + try: + yield stack.deploy() + finally: + stack.delete() diff --git a/tests/e2e/tracer/infrastructure.py b/tests/e2e/tracer/infrastructure.py index bd40fd2ca13..9b388558c0b 100644 --- a/tests/e2e/tracer/infrastructure.py +++ b/tests/e2e/tracer/infrastructure.py @@ -1,16 +1,17 @@ from pathlib import Path from tests.e2e.utils.data_builder import build_service_name -from tests.e2e.utils.infrastructure import BaseInfrastructureV2 +from tests.e2e.utils.infrastructure import BaseInfrastructure -class TracerStack(BaseInfrastructureV2): +class TracerStack(BaseInfrastructure): # Maintenance: Tracer doesn't support dynamic service injection (tracer.py L310) # we could move after handler response or adopt env vars usage in e2e tests SERVICE_NAME: str = build_service_name() + FEATURE_NAME = "tracer" - def __init__(self, handlers_dir: Path, feature_name: str = "tracer") -> None: - super().__init__(feature_name, handlers_dir) + def __init__(self, handlers_dir: Path, feature_name: str = FEATURE_NAME, layer_arn: str = "") -> None: + super().__init__(feature_name, handlers_dir, layer_arn) def create_resources(self) -> None: env_vars = {"POWERTOOLS_SERVICE_NAME": self.SERVICE_NAME} diff --git a/tests/e2e/utils/Dockerfile b/tests/e2e/utils/Dockerfile index eccfe2c6dfd..586847bb3fa 100644 --- a/tests/e2e/utils/Dockerfile +++ b/tests/e2e/utils/Dockerfile @@ -1,5 +1,5 @@ -# Image used by CDK's LayerVersion construct to create Lambda Layer with Powertools -# library code. +# Image used by CDK's LayerVersion construct to create Lambda Layer with Powertools +# library code. # The correct AWS SAM build image based on the runtime of the function will be # passed as build arg. The default allows to do `docker build .` when testing. ARG IMAGE=public.ecr.aws/sam/build-python3.7 @@ -9,8 +9,6 @@ ARG PIP_INDEX_URL ARG PIP_EXTRA_INDEX_URL ARG HTTPS_PROXY -# Upgrade pip (required by cryptography v3.4 and above, which is a dependency of poetry) RUN pip install --upgrade pip -RUN pip install pipenv poetry CMD [ "python" ] diff --git a/tests/e2e/utils/asset.py b/tests/e2e/utils/asset.py index 04d368a6ff4..db9e7299d1a 100644 --- a/tests/e2e/utils/asset.py +++ b/tests/e2e/utils/asset.py @@ -1,5 +1,6 @@ import io import json +import logging import zipfile from pathlib import Path from typing import Dict, List, Optional @@ -9,9 +10,7 @@ from mypy_boto3_s3 import S3Client from pydantic import BaseModel, Field -from aws_lambda_powertools import Logger - -logger = Logger(service="e2e-utils") +logger = logging.getLogger(__name__) class AssetManifest(BaseModel): @@ -113,6 +112,7 @@ def upload(self): We follow the same design cdk-assets: https://github.com/aws/aws-cdk-rfcs/blob/master/text/0092-asset-publishing.md. """ + logger.debug(f"Upload {len(self.assets)} assets") for asset in self.assets: if not asset.is_zip: logger.debug(f"Asset '{asset.object_key}' is not zip. Skipping upload.") @@ -138,7 +138,7 @@ def _find_assets_from_template(self) -> List[Asset]: def _compress_assets(self, asset: Asset) -> io.BytesIO: buf = io.BytesIO() asset_dir = f"{self.assets_location}/{asset.asset_path}" - asset_files = list(Path(asset_dir).iterdir()) + asset_files = list(Path(asset_dir).rglob("*")) with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as archive: for asset_file in asset_files: logger.debug(f"Adding file '{asset_file}' to the archive.") diff --git a/tests/e2e/utils/infrastructure.py b/tests/e2e/utils/infrastructure.py index ced6d70a1ad..7f232bb063f 100644 --- a/tests/e2e/utils/infrastructure.py +++ b/tests/e2e/utils/infrastructure.py @@ -1,12 +1,10 @@ -import io import json -import os +import logging import sys -import zipfile from abc import ABC, abstractmethod from enum import Enum from pathlib import Path -from typing import Dict, Generator, List, Optional, Tuple, Type +from typing import Dict, Generator, Optional, Tuple, Type from uuid import uuid4 import boto3 @@ -21,11 +19,7 @@ PYTHON_RUNTIME_VERSION = f"V{''.join(map(str, sys.version_info[:2]))}" - -class PythonVersion(Enum): - V37 = {"runtime": Runtime.PYTHON_3_7, "image": Runtime.PYTHON_3_7.bundling_image.image} - V38 = {"runtime": Runtime.PYTHON_3_8, "image": Runtime.PYTHON_3_8.bundling_image.image} - V39 = {"runtime": Runtime.PYTHON_3_9, "image": Runtime.PYTHON_3_9.bundling_image.image} +logger = logging.getLogger(__name__) class BaseInfrastructureStack(ABC): @@ -38,191 +32,22 @@ def __call__(self) -> Tuple[dict, str]: ... -class InfrastructureStack(BaseInfrastructureStack): - def __init__(self, handlers_dir: str, stack_name: str, config: dict) -> None: - self.stack_name = stack_name - self.handlers_dir = handlers_dir - self.config = config - - def _create_layer(self, stack: Stack): - output_dir = Path(str(AssetStaging.BUNDLING_OUTPUT_DIR), "python") - input_dir = Path(str(AssetStaging.BUNDLING_INPUT_DIR), "aws_lambda_powertools") - powertools_layer = LayerVersion( - stack, - "aws-lambda-powertools", - layer_version_name="aws-lambda-powertools", - compatible_runtimes=[PythonVersion[PYTHON_RUNTIME_VERSION].value["runtime"]], - code=Code.from_asset( - path=".", - bundling=BundlingOptions( - image=DockerImage.from_build( - str(Path(__file__).parent), - build_args={"IMAGE": PythonVersion[PYTHON_RUNTIME_VERSION].value["image"]}, - ), - command=[ - "bash", - "-c", - rf"poetry export --with-credentials --format requirements.txt --output /tmp/requirements.txt &&\ - pip install -r /tmp/requirements.txt -t {output_dir} &&\ - cp -R {input_dir} {output_dir}", - ], - ), - ), - ) - return powertools_layer - - def _find_handlers(self, directory: str) -> List: - for root, _, files in os.walk(directory): - return [os.path.join(root, filename) for filename in files if filename.endswith(".py")] - - def synthesize(self, handlers: List[str]) -> Tuple[dict, str, str]: - integration_test_app = App() - stack = Stack(integration_test_app, self.stack_name) - powertools_layer = self._create_layer(stack) - code = Code.from_asset(self.handlers_dir) - - for filename_path in handlers: - filename = Path(filename_path).stem - function_python = Function( - stack, - f"{filename}-lambda", - runtime=PythonVersion[PYTHON_RUNTIME_VERSION].value["runtime"], - code=code, - handler=f"{filename}.lambda_handler", - layers=[powertools_layer], - environment=self.config.get("environment_variables"), - tracing=Tracing.ACTIVE - if self.config.get("parameters", {}).get("tracing") == "ACTIVE" - else Tracing.DISABLED, - ) - - aws_logs.LogGroup( - stack, - f"{filename}-lg", - log_group_name=f"/aws/lambda/{function_python.function_name}", - retention=aws_logs.RetentionDays.ONE_DAY, - removal_policy=RemovalPolicy.DESTROY, - ) - CfnOutput(stack, f"{filename}_arn", value=function_python.function_arn) - cloud_assembly = integration_test_app.synth() - cf_template = cloud_assembly.get_stack_by_name(self.stack_name).template - cloud_assembly_directory = cloud_assembly.directory - cloud_assembly_assets_manifest_path = cloud_assembly.get_stack_by_name(self.stack_name).dependencies[0].file - - return (cf_template, cloud_assembly_directory, cloud_assembly_assets_manifest_path) - - def __call__(self) -> Tuple[dict, str]: - handlers = self._find_handlers(directory=self.handlers_dir) - return self.synthesize(handlers=handlers) - - -class Infrastructure: - def __init__(self, stack_name: str, handlers_dir: str, config: dict) -> None: - session = boto3.Session() - self.s3_client = session.client("s3") - self.lambda_client = session.client("lambda") - self.cfn = session.client("cloudformation") - self.s3_resource = session.resource("s3") - self.account_id = session.client("sts").get_caller_identity()["Account"] - self.region = session.region_name - self.stack_name = stack_name - self.handlers_dir = handlers_dir - self.config = config - - def deploy(self, Stack: Type[BaseInfrastructureStack]) -> Dict[str, str]: - - stack = Stack(handlers_dir=self.handlers_dir, stack_name=self.stack_name, config=self.config) - template, asset_root_dir, asset_manifest_file = stack() - self._upload_assets(asset_root_dir, asset_manifest_file) - - response = self._deploy_stack(self.stack_name, template) - - return self._transform_output(response["Stacks"][0]["Outputs"]) - - def delete(self): - self.cfn.delete_stack(StackName=self.stack_name) - - def _upload_assets(self, asset_root_dir: str, asset_manifest_file: str): - """ - This method is drop-in replacement for cdk-assets package s3 upload part. - https://www.npmjs.com/package/cdk-assets. - We use custom solution to avoid dependencies from nodejs ecosystem. - We follow the same design cdk-assets: - https://github.com/aws/aws-cdk-rfcs/blob/master/text/0092-asset-publishing.md. - """ - - assets = self._find_assets(asset_manifest_file, self.account_id, self.region) - - for s3_key, config in assets.items(): - print(config) - s3_bucket = self.s3_resource.Bucket(config["bucket_name"]) - - if config["asset_packaging"] != "zip": - print("Asset is not a zip file. Skipping upload") - continue - - if bool(list(s3_bucket.objects.filter(Prefix=s3_key))): - print("object exists, skipping") - continue - - buf = io.BytesIO() - asset_dir = f"{asset_root_dir}/{config['asset_path']}" - os.chdir(asset_dir) - asset_files = self._find_files(directory=".") - with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf: - for asset_file in asset_files: - zf.write(os.path.join(asset_file)) - buf.seek(0) - self.s3_client.upload_fileobj(Fileobj=buf, Bucket=config["bucket_name"], Key=s3_key) - - def _find_files(self, directory: str) -> List: - file_paths = [] - for root, _, files in os.walk(directory): - for filename in files: - file_paths.append(os.path.join(root, filename)) - return file_paths - - def _deploy_stack(self, stack_name: str, template: dict): - response = self.cfn.create_stack( - StackName=stack_name, - TemplateBody=yaml.dump(template), - TimeoutInMinutes=10, - OnFailure="ROLLBACK", - Capabilities=["CAPABILITY_IAM"], - ) - waiter = self.cfn.get_waiter("stack_create_complete") - waiter.wait(StackName=stack_name, WaiterConfig={"Delay": 10, "MaxAttempts": 50}) - response = self.cfn.describe_stacks(StackName=stack_name) - return response - - def _find_assets(self, asset_template: str, account_id: str, region: str): - assets = {} - with open(asset_template, mode="r") as template: - for _, config in json.loads(template.read())["files"].items(): - asset_path = config["source"]["path"] - asset_packaging = config["source"]["packaging"] - bucket_name = config["destinations"]["current_account-current_region"]["bucketName"] - object_key = config["destinations"]["current_account-current_region"]["objectKey"] - - assets[object_key] = { - "bucket_name": bucket_name.replace("${AWS::AccountId}", account_id).replace( - "${AWS::Region}", region - ), - "asset_path": asset_path, - "asset_packaging": asset_packaging, - } - - return assets - - def _transform_output(self, outputs: dict): - return {output["OutputKey"]: output["OutputValue"] for output in outputs if output["OutputKey"]} +class PythonVersion(Enum): + V37 = {"runtime": Runtime.PYTHON_3_7, "image": Runtime.PYTHON_3_7.bundling_image.image} + V38 = {"runtime": Runtime.PYTHON_3_8, "image": Runtime.PYTHON_3_8.bundling_image.image} + V39 = {"runtime": Runtime.PYTHON_3_9, "image": Runtime.PYTHON_3_9.bundling_image.image} -class BaseInfrastructureV2(ABC): - def __init__(self, feature_name: str, handlers_dir: Path) -> None: +class BaseInfrastructure(ABC): + def __init__(self, feature_name: str, handlers_dir: Path, layer_arn: str = "") -> None: + self.feature_name = feature_name self.stack_name = f"test-{feature_name}-{uuid4()}" self.handlers_dir = handlers_dir + self.layer_arn = layer_arn self.stack_outputs: Dict[str, str] = {} + + # NOTE: Investigate why cdk.Environment in Stack + # changes synthesized asset (no object_key in asset manifest) self.app = App() self.stack = Stack(self.app, self.stack_name) self.session = boto3.Session() @@ -242,7 +67,7 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None): Parameters ---------- function_props: Optional[Dict] - CDK Lambda FunctionProps as dictionary to override defaults + Dictionary representing CDK Lambda FunctionProps to override defaults Examples -------- @@ -263,41 +88,41 @@ def create_lambda_functions(self, function_props: Optional[Dict] = None): """ handlers = list(self.handlers_dir.rglob("*.py")) source = Code.from_asset(f"{self.handlers_dir}") - props_override = function_props or {} + logger.debug(f"Creating functions for handlers: {handlers}") + if not self.layer_arn: + raise ValueError( + """Lambda Layer ARN cannot be empty when creating Lambda functions. + Make sure to inject `lambda_layer_arn` fixture and pass at the constructor level""" + ) + layer = LayerVersion.from_layer_version_arn(self.stack, "layer-arn", layer_version_arn=self.layer_arn) + function_settings_override = function_props or {} for fn in handlers: fn_name = fn.stem + fn_name_pascal_case = fn_name.title().replace("_", "") # basic_handler -> BasicHandler + logger.debug(f"Creating function: {fn_name_pascal_case}") function_settings = { "id": f"{fn_name}-lambda", "code": source, "handler": f"{fn_name}.lambda_handler", "tracing": Tracing.ACTIVE, "runtime": Runtime.PYTHON_3_9, - "layers": [ - LayerVersion.from_layer_version_arn( - self.stack, - f"{fn_name}-lambda-powertools", - f"arn:aws:lambda:{self.region}:017000801446:layer:AWSLambdaPowertoolsPython:29", - ) - ], - **props_override, + "layers": [layer], + **function_settings_override, } - function_python = Function(self.stack, **function_settings) + function = Function(self.stack, **function_settings) aws_logs.LogGroup( self.stack, id=f"{fn_name}-lg", - log_group_name=f"/aws/lambda/{function_python.function_name}", + log_group_name=f"/aws/lambda/{function.function_name}", retention=aws_logs.RetentionDays.ONE_DAY, removal_policy=RemovalPolicy.DESTROY, ) - # CFN Outputs only support hyphen - fn_name_pascal_case = fn_name.title().replace("_", "") # basic_handler -> BasicHandler - self._add_resource_output( - name=fn_name_pascal_case, value=function_python.function_name, arn=function_python.function_arn - ) + # CFN Outputs only support hyphen hence pascal case + self.add_cfn_output(name=fn_name_pascal_case, value=function.function_name, arn=function.function_arn) def deploy(self) -> Dict[str, str]: """Creates CloudFormation Stack and return stack outputs as dict @@ -310,10 +135,12 @@ def deploy(self) -> Dict[str, str]: template, asset_manifest_file = self._synthesize() assets = Assets(asset_manifest=asset_manifest_file, account_id=self.account_id, region=self.region) assets.upload() - return self._deploy_stack(self.stack_name, template) + self.stack_outputs = self._deploy_stack(self.stack_name, template) + return self.stack_outputs def delete(self) -> None: """Delete CloudFormation Stack""" + logger.debug(f"Deleting stack: {self.stack_name}") self.cfn.delete_stack(StackName=self.stack_name) @abstractmethod @@ -330,7 +157,7 @@ def created_resources(self): s3 = s3.Bucket(self.stack, "MyBucket") # This will create MyBucket and MyBucketArn CloudFormation Output - self._add_resource_output(name="MyBucket", value=s3.bucket_name, arn_value=bucket.bucket_arn) + self.add_cfn_output(name="MyBucket", value=s3.bucket_name, arn_value=bucket.bucket_arn) ``` Creating Lambda functions available in the handlers directory @@ -343,7 +170,9 @@ def created_resources(self): ... def _synthesize(self) -> Tuple[Dict, Path]: + logger.debug("Creating CDK Stack resources") self.create_resources() + logger.debug("Synthesizing CDK Stack into raw CloudFormation template") cloud_assembly = self.app.synth() cf_template: Dict = cloud_assembly.get_stack_by_name(self.stack_name).template cloud_assembly_assets_manifest_path: str = ( @@ -352,6 +181,7 @@ def _synthesize(self) -> Tuple[Dict, Path]: return cf_template, Path(cloud_assembly_assets_manifest_path) def _deploy_stack(self, stack_name: str, template: Dict) -> Dict[str, str]: + logger.debug(f"Creating CloudFormation Stack: {stack_name}") self.cfn.create_stack( StackName=stack_name, TemplateBody=yaml.dump(template), @@ -364,16 +194,10 @@ def _deploy_stack(self, stack_name: str, template: Dict) -> Dict[str, str]: stack_details = self.cfn.describe_stacks(StackName=stack_name) stack_outputs = stack_details["Stacks"][0]["Outputs"] - self.stack_outputs = { - output["OutputKey"]: output["OutputValue"] for output in stack_outputs if output["OutputKey"] - } - - return self.stack_outputs - - def _add_resource_output(self, name: str, value: str, arn: str): - """Add both resource value and ARN as Outputs to facilitate tests. + return {output["OutputKey"]: output["OutputValue"] for output in stack_outputs if output["OutputKey"]} - This will create two outputs: {Name} and {Name}Arn + def add_cfn_output(self, name: str, value: str, arn: str = ""): + """Create {Name} and optionally {Name}Arn CloudFormation Outputs. Parameters ---------- @@ -385,20 +209,22 @@ def _add_resource_output(self, name: str, value: str, arn: str): CloudFormation Output Value for ARN """ CfnOutput(self.stack, f"{name}", value=value) - CfnOutput(self.stack, f"{name}Arn", value=arn) + if arn: + CfnOutput(self.stack, f"{name}Arn", value=arn) def deploy_once( - stack: Type[BaseInfrastructureV2], + stack: Type[BaseInfrastructure], request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory, worker_id: str, + layer_arn: str, ) -> Generator[Dict[str, str], None, None]: """Deploys provided stack once whether CPU parallelization is enabled or not Parameters ---------- - stack : Type[BaseInfrastructureV2] + stack : Type[BaseInfrastructure] stack class to instantiate and deploy, for example MetricStack. Not to be confused with class instance (MetricStack()). request : pytest.FixtureRequest @@ -413,8 +239,8 @@ def deploy_once( Generator[Dict[str, str], None, None] stack CloudFormation outputs """ - handlers_dir = f"{request.path.parent}/handlers" - stack = stack(handlers_dir=Path(handlers_dir)) + handlers_dir = f"{request.node.path.parent}/handlers" + stack = stack(handlers_dir=Path(handlers_dir), layer_arn=layer_arn) try: if worker_id == "master": @@ -423,8 +249,8 @@ def deploy_once( else: # tmp dir shared by all workers root_tmp_dir = tmp_path_factory.getbasetemp().parent - cache = root_tmp_dir / "cache.json" + with FileLock(f"{cache}.lock"): # If cache exists, return stack outputs back # otherwise it's the first run by the main worker @@ -437,3 +263,38 @@ def deploy_once( yield stack_outputs finally: stack.delete() + + +class LambdaLayerStack(BaseInfrastructure): + FEATURE_NAME = "lambda-layer" + + def __init__(self, handlers_dir: Path, feature_name: str = FEATURE_NAME, layer_arn: str = "") -> None: + super().__init__(feature_name, handlers_dir, layer_arn) + + def create_resources(self): + layer = self._create_layer() + CfnOutput(self.stack, "LayerArn", value=layer) + + def _create_layer(self) -> str: + logger.debug("Creating Lambda Layer with latest source code available") + output_dir = Path(str(AssetStaging.BUNDLING_OUTPUT_DIR), "python") + input_dir = Path(str(AssetStaging.BUNDLING_INPUT_DIR), "aws_lambda_powertools") + + build_commands = [f"pip install .[pydantic] -t {output_dir}", f"cp -R {input_dir} {output_dir}"] + layer = LayerVersion( + self.stack, + "aws-lambda-powertools-e2e-test", + layer_version_name="aws-lambda-powertools-e2e-test", + compatible_runtimes=[PythonVersion[PYTHON_RUNTIME_VERSION].value["runtime"]], + code=Code.from_asset( + path=".", + bundling=BundlingOptions( + image=DockerImage.from_build( + str(Path(__file__).parent), + build_args={"IMAGE": PythonVersion[PYTHON_RUNTIME_VERSION].value["image"]}, + ), + command=["bash", "-c", " && ".join(build_commands)], + ), + ), + ) + return layer.layer_version_arn diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index e69de29bb2d..00000000000 From 84b86958f02cc3a545763cbb3c1a850d8bd450c1 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 23 Aug 2022 13:36:12 +0100 Subject: [PATCH 45/59] chore(maintainer): add Leandro as maintainer (#1468) --- MAINTAINERS.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 16bc8ab1161..7165be8564e 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -37,13 +37,14 @@ This is document explains who the maintainers are (see below), what they do in t ## Current Maintainers -| Maintainer | GitHub ID | Affiliation | -| ---------------- | ----------------------------------------------- | ----------- | -| Heitor Lessa | [heitorlessa](https://github.com/heitorlessa) | Amazon | -| Alexander Melnyk | [am29d](https://github.com/am29d) | Amazon | -| Michal Ploski | [mploski](https://github.com/mploski) | Amazon | -| Simon Thulbourn | [sthulb](https://github.com/sthulb) | Amazon | -| Ruben Fonseca | [rubenfonseca](https://github.com/rubenfonseca) | Amazon | +| Maintainer | GitHub ID | Affiliation | +| ----------------- | ------------------------------------------------------- | ----------- | +| Heitor Lessa | [heitorlessa](https://github.com/heitorlessa) | Amazon | +| Alexander Melnyk | [am29d](https://github.com/am29d) | Amazon | +| Michal Ploski | [mploski](https://github.com/mploski) | Amazon | +| Simon Thulbourn | [sthulb](https://github.com/sthulb) | Amazon | +| Ruben Fonseca | [rubenfonseca](https://github.com/rubenfonseca) | Amazon | +| Leandro Damascena | [leandrodamascena](https://github.com/leandrodamascena) | Amazon | ## Emeritus From 4720ddbdff905b924460192e9851b2726c3af0b8 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 23 Aug 2022 16:52:08 +0200 Subject: [PATCH 46/59] docs(home): add discord invitation link (#1471) Co-authored-by: Heitor Lessa --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 81cd3f3ce7f..5e57e524f72 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,10 @@ A suite of Python utilities for AWS Lambda functions to ease adopting best pract * **[Idempotency](https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/idempotency/)** - Convert your Lambda functions into idempotent operations which are safe to retry * **[Feature Flags](https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/feature_flags/)** - A simple rule engine to evaluate when one or multiple features should be enabled depending on the input - ### Installation With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip install aws-lambda-powertools`` - ## Tutorial and Examples * [Tutorial](https://awslabs.github.io/aws-lambda-powertools-python/latest/tutorial) @@ -47,7 +45,9 @@ With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip insta * Powertools idea [DAZN Powertools](https://github.com/getndazn/dazn-lambda-powertools/) ## Connect -**Email**: aws-lambda-powertools-feedback@amazon.com + +* **AWS Lambda Powertools on Discord**: `#python` - **[Invite link(https://discord.gg/B8zZKbbyET)** +* **Email**: aws-lambda-powertools-feedback@amazon.com ## Security disclosures From 4dcfb7173d07178951f72be278e58d2cd38ba054 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Tue, 23 Aug 2022 17:06:00 +0200 Subject: [PATCH 47/59] docs(home): fix discord syntax and add Discord badge --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e57e524f72..2065a983342 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # AWS Lambda Powertools for Python -![Build](https://github.com/awslabs/aws-lambda-powertools/workflows/Powertools%20Python/badge.svg?branch=master) +[![Build](https://github.com/awslabs/aws-lambda-powertools-python/actions/workflows/python_build.yml/badge.svg)](https://github.com/awslabs/aws-lambda-powertools-python/actions/workflows/python_build.yml) [![codecov.io](https://codecov.io/github/awslabs/aws-lambda-powertools-python/branch/develop/graphs/badge.svg)](https://app.codecov.io/gh/awslabs/aws-lambda-powertools-python) ![PythonSupport](https://img.shields.io/static/v1?label=python&message=3.6%20|%203.7|%203.8|%203.9&color=blue?style=flat-square&logo=python) ![PyPI version](https://badge.fury.io/py/aws-lambda-powertools.svg) ![PyPi monthly downloads](https://img.shields.io/pypi/dm/aws-lambda-powertools) +[![Join our Discord](https://dcbadge.vercel.app/api/server/B8zZKbbyET)](https://discord.gg/B8zZKbbyET) A suite of Python utilities for AWS Lambda functions to ease adopting best practices such as tracing, structured logging, custom metrics, and more. (AWS Lambda Powertools [Java](https://github.com/awslabs/aws-lambda-powertools-java) and [Typescript](https://github.com/awslabs/aws-lambda-powertools-typescript) is also available). @@ -46,7 +47,7 @@ With [pip](https://pip.pypa.io/en/latest/index.html) installed, run: ``pip insta ## Connect -* **AWS Lambda Powertools on Discord**: `#python` - **[Invite link(https://discord.gg/B8zZKbbyET)** +* **AWS Lambda Powertools on Discord**: `#python` - **[Invite link](https://discord.gg/B8zZKbbyET)** * **Email**: aws-lambda-powertools-feedback@amazon.com ## Security disclosures From a45a7924a25ab9c99530fad71d2a9d025f0b1f13 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Tue, 23 Aug 2022 17:16:48 +0200 Subject: [PATCH 48/59] chore(tests): enable end-to-end test workflow (#1470) --- .github/workflows/run-e2e-tests.yml | 12 +- CONTRIBUTING.md | 71 +- MAINTAINERS.md | 86 +- .../event_handler/appsync.py | 3 +- poetry.lock | 926 +++--------------- pyproject.toml | 1 + tests/unit/test_tracing.py | 2 + tests/unit/test_utilities_batch.py | 2 + 8 files changed, 286 insertions(+), 817 deletions(-) diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index a020410823c..95d0d6b2c69 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -1,9 +1,17 @@ -name: run-e2e-tests +name: Run end-to-end tests + on: workflow_dispatch: + + push: + branches: [develop] + +# Maintenance: Add support for triggering on `run-e2e` label +# and enforce repo origin to prevent abuse + env: AWS_DEFAULT_REGION: us-east-1 - E2E_TESTS_PATH: tests/e2e/ + jobs: run: runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bef5fa2052f..b6d29d7b00a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,20 @@ -[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/) + +# Table of contents + +- [Contributing Guidelines](#contributing-guidelines) + - [Reporting Bugs/Feature Requests](#reporting-bugsfeature-requests) + - [Contributing via Pull Requests](#contributing-via-pull-requests) + - [Dev setup](#dev-setup) + - [Local documentation](#local-documentation) + - [Conventions](#conventions) + - [General terminology and practices](#general-terminology-and-practices) + - [Testing definition](#testing-definition) + - [Finding contributions to work on](#finding-contributions-to-work-on) + - [Code of Conduct](#code-of-conduct) + - [Security issue notifications](#security-issue-notifications) + - [Troubleshooting](#troubleshooting) + - [API reference documentation](#api-reference-documentation) + - [Licensing](#licensing) # Contributing Guidelines @@ -25,9 +41,11 @@ Contributions via pull requests are much appreciated. Before sending us a pull r ### Dev setup +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/) + Firstly, [fork the repository](https://github.com/awslabs/aws-lambda-powertools-python/fork). -To setup your development environment, we recommend using our pre-configured Cloud environment: https://gitpod.io/#https://github.com/YOUR_USERNAME/aws-lambda-powertools-python. Replace YOUR_USERNAME with your GitHub username or organization so the Cloud environment can target your fork accordingly. +To setup your development environment, we recommend using our pre-configured Cloud environment: . Replace YOUR_USERNAME with your GitHub username or organization so the Cloud environment can target your fork accordingly. Alternatively, you can use `make dev` within your local virtual environment. @@ -47,22 +65,38 @@ GitHub provides additional document on [forking a repository](https://help.githu You might find useful to run both the documentation website and the API reference locally while contributing: -* **API reference**: `make docs-api-local` -* **Docs website**: `make docs-local` - - If you prefer using Docker: `make docs-local-docker` +- **API reference**: `make docs-api-local` +- **Docs website**: `make docs-local` + - If you prefer using Docker: `make docs-local-docker` + +## Conventions + +### General terminology and practices -### Conventions +| Category | Convention | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Docstring** | We use a slight variation of Numpy convention with markdown to help generate more readable API references. | +| **Style guide** | We use black as well as flake8 extensions to enforce beyond good practices [PEP8](https://pep8.org/). We use type annotations and enforce static type checking at CI (mypy). | +| **Core utilities** | Core utilities use a Class, always accept `service` as a constructor parameter, can work in isolation, and are also available in other languages implementation. | +| **Utilities** | Utilities are not as strict as core and focus on solving a developer experience problem while following the project [Tenets](https://awslabs.github.io/aws-lambda-powertools-python/#tenets). | +| **Exceptions** | Specific exceptions live within utilities themselves and use `Error` suffix e.g. `MetricUnitError`. | +| **Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). We do not enforce conventional commits on contributors to lower the entry bar. Instead, we enforce a conventional PR title so our label automation and changelog are generated correctly. | +| **API documentation** | API reference docs are generated from docstrings which should have Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strive to be concise. | +| **Documentation** | We treat it like a product. We sub-divide content aimed at getting started (80% of customers) vs advanced usage (20%). We also ensure customers know how to unit test their code when using our features. | -Category | Convention -------------------------------------------------- | --------------------------------------------------------------------------------- -**Docstring** | We use a slight variation of Numpy convention with markdown to help generate more readable API references. -**Style guide** | We use black as well as flake8 extensions to enforce beyond good practices [PEP8](https://pep8.org/). We use type annotations and enforce static type checking at CI (mypy). -**Core utilities** | Core utilities use a Class, always accept `service` as a constructor parameter, can work in isolation, and are also available in other languages implementation. -**Utilities** | Utilities are not as strict as core and focus on solving a developer experience problem while following the project [Tenets](https://awslabs.github.io/aws-lambda-powertools-python/#tenets). -**Exceptions** | Specific exceptions live within utilities themselves and use `Error` suffix e.g. `MetricUnitError`. -**Git commits** | We follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). We do not enforce conventional commits on contributors to lower the entry bar. Instead, we enforce a conventional PR title so our label automation and changelog are generated correctly. -**API documentation** | API reference docs are generated from docstrings which should have Examples section to allow developers to have what they need within their own IDE. Documentation website covers the wider usage, tips, and strive to be concise. -**Documentation** | We treat it like a product. We sub-divide content aimed at getting started (80% of customers) vs advanced usage (20%). We also ensure customers know how to unit test their code when using our features. +### Testing definition + +We group tests in different categories + +| Test | When to write | Notes | Speed | +| ----------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| Unit tests | Verify the smallest possible unit works. | Networking access is prohibited. Prefer Functional tests given our complexity. | Lightning fast (nsec to ms) | +| Functional tests | Guarantee functionality works as expected. It's a subset of integration test covering multiple units. | No external dependency. Prefer Fake implementations (in-memory) over Mocks and Stubs. | Fast (ms to few seconds at worst) | +| Integration tests | Gain confidence that code works with one or more external dependencies. | No need for a Lambda function. Use our code base against an external dependency _e.g., fetch an existing SSM parameter_. | Moderate to slow (a few minutes) | +| End-to-end tests | Gain confidence that a Lambda function with our code operates as expected. | It simulates how customers configure, deploy, and run their Lambda function - Event Source configuration, IAM permissions, etc. | Slow (minutes) | +| Performance tests | Ensure critical operations won't increase latency and costs to customers. | CI arbitrary hardware can make it flaky. We'll resume writing perf test after our new Integ/End have significant coverage. | Fast to moderate (a few seconds to a few minutes) | + +**NOTE**: Functional tests are mandatory. We have plans to create a guide on how to create these different tests. Maintainers will help indicate whether additional tests are necessary and provide assistance as required. ## Finding contributions to work on @@ -75,6 +109,7 @@ For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of opensource-codeofconduct@amazon.com with any additional questions or comments. ## Security issue notifications + If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. ## Troubleshooting @@ -85,9 +120,9 @@ When you are working on the codebase and you use the local API reference documen This happens when: -* You did not install the local dev environment yet +- You did not install the local dev environment yet - You can install dev deps with `make dev` command -* The code in the repository is raising an exception while the `pdoc` is scanning the codebase +- The code in the repository is raising an exception while the `pdoc` is scanning the codebase - Unfortunately, this exception is not shown to you, but if you run, `poetry run pdoc --pdf aws_lambda_powertools`, the exception is shown and you can prevent the exception from being raised - Once resolved the documentation should load correctly again diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 7165be8564e..63f2b3eb8f8 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -15,6 +15,8 @@ - [Releasing a new version](#releasing-a-new-version) - [Drafting release notes](#drafting-release-notes) - [Run end to end tests](#run-end-to-end-tests) + - [Structure](#structure) + - [Workflow](#workflow) - [Releasing a documentation hotfix](#releasing-a-documentation-hotfix) - [Maintain Overall Health of the Repo](#maintain-overall-health-of-the-repo) - [Manage Roadmap](#manage-roadmap) @@ -212,11 +214,85 @@ This will kick off the [Publishing workflow](https://github.com/awslabs/aws-lamb ### Run end to end tests -In order to run end to end tests you need to install CDK CLI first and bootstrap your account with `cdk bootstrap` command. For additional details follow [documentation](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html). - -To run locally, export `AWS_PROFILE` environment variable and run `make e2e tests`. To run from GitHub Actions, use [run-e2e-tests workflow](https://github.com/awslabs/aws-lambda-powertools-python/actions/workflows/run-e2e-tests.yml) and pick the branch you want to run tests against. - -**NOTE**: E2E tests are run as part of each merge to `develop` branch. +E2E tests are run on every push to `develop` or manually via [run-e2e-tests workflow](https://github.com/awslabs/aws-lambda-powertools-python/actions/workflows/run-e2e-tests.yml). + +To run locally, you need [AWS CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_prerequisites) and an [account bootstrapped](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) (`cdk bootstrap`). With a default AWS CLI profile configured, or `AWS_PROFILE` environment variable set, run `make e2e tests`. + +#### Structure + +Our E2E framework relies on pytest fixtures to coordinate infrastructure and test parallelization (see [Workflow](#workflow)). You'll notice multiple `conftest.py`, `infrastructure.py`, and `handlers`. + +- **`infrastructure`**. Uses CDK to define what a Stack for a given feature should look like. It inherits from `BaseInfrastructure` to handle all boilerplate and deployment logic necessary. +- **`conftest.py`**. Imports and deploys a given feature Infrastructure. Hierarchy matters. Top-level `conftest` deploys stacks only once and blocks I/O across all CPUs. Feature-level `conftest` deploys stacks in parallel, and once complete run all tests in parallel. +- **`handlers`**. Lambda function handlers that will be automatically deployed and exported as PascalCase for later use. + +```shell +. +├── __init__.py +├── conftest.py # deploys Lambda Layer stack +├── logger +│ ├── __init__.py +│ ├── conftest.py # deploys LoggerStack +│ ├── handlers +│ │ └── basic_handler.py +│ ├── infrastructure.py # LoggerStack definition +│ └── test_logger.py +├── metrics +│ ├── __init__.py +│ ├── conftest.py # deploys MetricsStack +│ ├── handlers +│ │ ├── basic_handler.py +│ │ └── cold_start.py +│ ├── infrastructure.py # MetricsStack definition +│ └── test_metrics.py +├── tracer +│ ├── __init__.py +│ ├── conftest.py # deploys TracerStack +│ ├── handlers +│ │ ├── async_capture.py +│ │ └── basic_handler.py +│ ├── infrastructure.py # TracerStack definition +│ └── test_tracer.py +└── utils + ├── Dockerfile + ├── __init__.py + ├── data_builder # build_service_name(), build_add_dimensions_input, etc. + ├── data_fetcher # get_traces(), get_logs(), get_lambda_response(), etc. + ├── infrastructure.py # base infrastructure like deploy logic, Layer Stack, etc. +``` + +#### Workflow + +We parallelize our end-to-end tests to benefit from speed and isolate Lambda functions to ease assessing side effects (e.g., traces, logs, etc.). The following diagram demonstrates the process we take every time you use `make e2e`: + +```mermaid +graph TD + A[make e2e test] -->Spawn{"Split and group tests
by feature and CPU"} + + Spawn -->|Worker0| Worker0_Start["Load tests"] + Spawn -->|Worker1| Worker1_Start["Load tests"] + Spawn -->|WorkerN| WorkerN_Start["Load tests"] + + Worker0_Start -->|Wait| LambdaLayerStack["Lambda Layer Stack Deployment"] + Worker1_Start -->|Wait| LambdaLayerStack["Lambda Layer Stack Deployment"] + WorkerN_Start -->|Wait| LambdaLayerStack["Lambda Layer Stack Deployment"] + + LambdaLayerStack -->|Worker0| Worker0_Deploy["Launch feature stack"] + LambdaLayerStack -->|Worker1| Worker1_Deploy["Launch feature stack"] + LambdaLayerStack -->|WorkerN| WorkerN_Deploy["Launch feature stack"] + + Worker0_Deploy -->|Worker0| Worker0_Tests["Run tests"] + Worker1_Deploy -->|Worker1| Worker1_Tests["Run tests"] + WorkerN_Deploy -->|WorkerN| WorkerN_Tests["Run tests"] + + Worker0_Tests --> ResultCollection + Worker1_Tests --> ResultCollection + WorkerN_Tests --> ResultCollection + + ResultCollection{"Wait for workers
Collect test results"} + ResultCollection --> TestEnd["Report results"] + ResultCollection --> DeployEnd["Delete Stacks"] +``` ### Releasing a documentation hotfix diff --git a/aws_lambda_powertools/event_handler/appsync.py b/aws_lambda_powertools/event_handler/appsync.py index 896b303cd08..4ddc51cd102 100644 --- a/aws_lambda_powertools/event_handler/appsync.py +++ b/aws_lambda_powertools/event_handler/appsync.py @@ -1,5 +1,4 @@ import logging -from abc import ABC from typing import Any, Callable, Optional, Type, TypeVar from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent @@ -10,7 +9,7 @@ AppSyncResolverEventT = TypeVar("AppSyncResolverEventT", bound=AppSyncResolverEvent) -class BaseRouter(ABC): +class BaseRouter: current_event: AppSyncResolverEventT # type: ignore[valid-type] lambda_context: LambdaContext diff --git a/poetry.lock b/poetry.lock index 9b94cb96cfe..f77f61a9189 100644 --- a/poetry.lock +++ b/poetry.lock @@ -131,7 +131,7 @@ python-versions = "*" attrs = ">=17.3" [package.extras] -dev = ["bumpversion", "wheel", "watchdog", "flake8", "tox", "coverage", "sphinx", "pytest", "hypothesis", "pendulum"] +dev = ["pendulum", "hypothesis", "pytest", "sphinx", "coverage", "tox", "flake8", "watchdog", "wheel", "bumpversion"] [[package]] name = "cattrs" @@ -296,6 +296,18 @@ python-versions = "*" [package.extras] devel = ["colorama", "jsonschema", "json-spec", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] +[[package]] +name = "filelock" +version = "3.8.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] + [[package]] name = "flake8" version = "3.9.2" @@ -312,7 +324,7 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "flake8-bugbear" -version = "22.7.1" +version = "22.8.22" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false @@ -428,7 +440,7 @@ python-versions = "*" python-dateutil = ">=2.8.1" [package.extras] -dev = ["twine", "markdown", "flake8", "wheel"] +dev = ["wheel", "flake8", "markdown", "twine"] [[package]] name = "gitdb" @@ -637,8 +649,8 @@ packaging = "*" "ruamel.yaml" = "*" [package.extras] -dev = ["coverage", "flake8 (>=3.0)", "pypandoc (>=1.4)"] -test = ["coverage", "flake8 (>=3.0)"] +test = ["flake8 (>=3.0)", "coverage"] +dev = ["pypandoc (>=1.4)", "flake8 (>=3.0)", "coverage"] [[package]] name = "mkdocs" @@ -900,8 +912,8 @@ python-versions = ">=3.6" importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["pytest-benchmark", "pytest"] +dev = ["tox", "pre-commit"] [[package]] name = "publication" @@ -948,8 +960,8 @@ dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} typing-extensions = ">=3.7.4.3" [package.extras] -email = ["email-validator (>=1.0.3)"] dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] [[package]] name = "pyflakes" @@ -1058,7 +1070,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"] [[package]] name = "pytest-forked" @@ -1295,7 +1307,7 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.11" +version = "1.26.12" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -1303,7 +1315,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, [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"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -1356,785 +1368,119 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "0a9e21c2f15825934ad6c786121da020c4a964c5a0dd138e0e8ae09c0865a055" +content-hash = "223c6087ab0a58dc81fe9329849c3a0f8367685dcde88c2479d271808ccb5416" [metadata.files] -atomicwrites = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] -attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, -] -aws-cdk-lib = [ - {file = "aws-cdk-lib-2.23.0.tar.gz", hash = "sha256:3e07d1c6b320795d38567be183e56c2125b4c4492589775257aabec3d3e2a384"}, - {file = "aws_cdk_lib-2.23.0-py3-none-any.whl", hash = "sha256:1ec04a146d3364cd0fc4da08e3f8ca25e28df68abaa90641936db17a415ca4bc"}, -] -aws-xray-sdk = [ - {file = "aws-xray-sdk-2.10.0.tar.gz", hash = "sha256:9b14924fd0628cf92936055864655354003f0b1acc3e1c3ffde6403d0799dd7a"}, - {file = "aws_xray_sdk-2.10.0-py2.py3-none-any.whl", hash = "sha256:7551e81a796e1a5471ebe84844c40e8edf7c218db33506d046fec61f7495eda4"}, -] -bandit = [ - {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, - {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"}, -] -black = [ - {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, - {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, -] -boto3 = [ - {file = "boto3-1.23.10-py3-none-any.whl", hash = "sha256:40d08614f17a69075e175c02c5d5aab69a6153fd50e40fa7057b913ac7bf40e7"}, - {file = "boto3-1.23.10.tar.gz", hash = "sha256:2a4395e3241c20eef441d7443a5e6eaa0ee3f7114653fb9d9cef41587526f7bd"}, -] -botocore = [ - {file = "botocore-1.26.10-py3-none-any.whl", hash = "sha256:8a4a984bf901ccefe40037da11ba2abd1ddbcb3b490a492b7f218509c99fc12f"}, - {file = "botocore-1.26.10.tar.gz", hash = "sha256:5df2cf7ebe34377470172bd0bbc582cf98c5cbd02da0909a14e9e2885ab3ae9c"}, -] -cattrs = [ - {file = "cattrs-1.0.0-py2.py3-none-any.whl", hash = "sha256:616972ae3dfa6e623a40ad3cb845420e64942989152774ab055e5c2b2f89f997"}, - {file = "cattrs-1.0.0.tar.gz", hash = "sha256:b7ab5cf8ad127c42eefd01410c1c6e28569a45a255ea80ed968511873c433c7a"}, - {file = "cattrs-22.1.0-py3-none-any.whl", hash = "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364"}, - {file = "cattrs-22.1.0.tar.gz", hash = "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6"}, -] -certifi = [ - {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, - {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, -] -charset-normalizer = [ - {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-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -constructs = [ - {file = "constructs-10.1.1-py3-none-any.whl", hash = "sha256:c1f3deb196f54e070ded3c92c4339f73ef2b6022d35fb34908c0ebfa7ef8a640"}, - {file = "constructs-10.1.1.tar.gz", hash = "sha256:6ce0dd1352367237b5d7c51a25740482c852735d2a5e067c536acc1657f39ea5"}, -] -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"}, -] -dataclasses = [ - {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, - {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, -] -decorator = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] -dnspython = [ - {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, - {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, -] -email-validator = [ - {file = "email_validator-1.2.1-py2.py3-none-any.whl", hash = "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c"}, - {file = "email_validator-1.2.1.tar.gz", hash = "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8"}, -] -eradicate = [ - {file = "eradicate-2.1.0-py3-none-any.whl", hash = "sha256:8bfaca181db9227dc88bdbce4d051a9627604c2243e7d85324f6d6ce0fd08bb2"}, - {file = "eradicate-2.1.0.tar.gz", hash = "sha256:aac7384ab25b1bf21c4c012de9b4bf8398945a14c98c911545b2ea50ab558014"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"}, - {file = "exceptiongroup-1.0.0rc8.tar.gz", hash = "sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a"}, -] -execnet = [ - {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, - {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, -] -fastjsonschema = [ - {file = "fastjsonschema-2.16.1-py3-none-any.whl", hash = "sha256:2f7158c4de792555753d6c2277d6a2af2d406dfd97aeca21d17173561ede4fe6"}, - {file = "fastjsonschema-2.16.1.tar.gz", hash = "sha256:d6fa3ffbe719768d70e298b9fb847484e2bdfdb7241ed052b8d57a9294a8c334"}, -] -flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, -] -flake8-bugbear = [ - {file = "flake8-bugbear-22.7.1.tar.gz", hash = "sha256:e450976a07e4f9d6c043d4f72b17ec1baf717fe37f7997009c8ae58064f88305"}, - {file = "flake8_bugbear-22.7.1-py3-none-any.whl", hash = "sha256:db5d7a831ef4412a224b26c708967ff816818cabae415e76b8c58df156c4b8e5"}, -] -flake8-builtins = [ - {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"}, - {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"}, -] -flake8-comprehensions = [ - {file = "flake8-comprehensions-3.7.0.tar.gz", hash = "sha256:6b3218b2dde8ac5959c6476cde8f41a79e823c22feb656be2710cd2a3232cef9"}, - {file = "flake8_comprehensions-3.7.0-py3-none-any.whl", hash = "sha256:a5d7aea6315bbbd6fbcb2b4e80bff6a54d1600155e26236e555d0c6fe1d62522"}, -] -flake8-debugger = [ - {file = "flake8-debugger-4.0.0.tar.gz", hash = "sha256:e43dc777f7db1481db473210101ec2df2bd39a45b149d7218a618e954177eda6"}, - {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"}, -] -flake8-eradicate = [ - {file = "flake8-eradicate-1.3.0.tar.gz", hash = "sha256:e4c98f00d17dc8653e3388cac2624cd81e9735de2fd4a8dcf99029633ebd7a63"}, - {file = "flake8_eradicate-1.3.0-py3-none-any.whl", hash = "sha256:85a71e0c5f4e07f7c6c5fec520483561fd6bd295417d622855bdeade99242e3d"}, -] -flake8-fixme = [ - {file = "flake8-fixme-1.1.1.tar.gz", hash = "sha256:50cade07d27a4c30d4f12351478df87339e67640c83041b664724bda6d16f33a"}, - {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"}, -] -flake8-isort = [ - {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, - {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, -] -flake8-variables-names = [ - {file = "flake8_variables_names-0.0.4.tar.gz", hash = "sha256:d6fa0571a807c72940b5773827c5760421ea6f8206595ff0a8ecfa01e42bf2cf"}, -] -future = [ - {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, -] -ghp-import = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] -gitdb = [ - {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, - {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, -] -gitpython = [ - {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, - {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, -] -importlib-resources = [ - {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, - {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, -] -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.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] -jinja2 = [ - {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, - {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, -] -jmespath = [ - {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, - {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, -] -jsii = [ - {file = "jsii-1.57.0-py3-none-any.whl", hash = "sha256:4888091986a9ed8d50b042cc9c35a9564dd54c19e78adb890bf06d9ffac1b325"}, - {file = "jsii-1.57.0.tar.gz", hash = "sha256:ff7a3c51c1a653dd8a4342043b5f8e40b928bc617e3141e0d5d66175d22a754b"}, -] -mako = [ - {file = "Mako-1.1.6-py2.py3-none-any.whl", hash = "sha256:afaf8e515d075b22fad7d7b8b30e4a1c90624ff2f3733a06ec125f5a5f043a57"}, - {file = "Mako-1.1.6.tar.gz", hash = "sha256:4e9e345a41924a954251b95b4b28e14a301145b544901332e658907a7464b6b2"}, -] -mando = [ - {file = "mando-0.6.4-py2.py3-none-any.whl", hash = "sha256:4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c"}, - {file = "mando-0.6.4.tar.gz", hash = "sha256:79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"}, -] -markdown = [ - {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, - {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, -] -markupsafe = [ - {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-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-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_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-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-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"}, -] -mergedeep = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] -mike = [ - {file = "mike-0.6.0-py3-none-any.whl", hash = "sha256:cef9b9c803ff5c3fbb410f51f5ceb00902a9fe16d9fabd93b69c65cf481ab5a1"}, - {file = "mike-0.6.0.tar.gz", hash = "sha256:6d6239de2a60d733da2f34617e9b9a14c4b5437423b47e524f14dc96d6ce5f2f"}, -] -mkdocs = [ - {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, - {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, -] -mkdocs-git-revision-date-plugin = [ - {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, -] -mkdocs-material = [ - {file = "mkdocs-material-8.4.1.tar.gz", hash = "sha256:92c70f94b2e1f8a05d9e05eec1c7af9dffc516802d69222329db89503c97b4f3"}, - {file = "mkdocs_material-8.4.1-py2.py3-none-any.whl", hash = "sha256:319a6254819ce9d864ff79de48c43842fccfdebb43e4e6820eef75216f8cfb0a"}, -] -mkdocs-material-extensions = [ - {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, - {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, -] -mypy = [ - {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, - {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, - {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, - {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, - {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, - {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, - {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, - {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, - {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, - {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, - {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, - {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, - {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, - {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, - {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, - {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, - {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, - {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, - {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, - {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, - {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, -] -mypy-boto3-appconfig = [ - {file = "mypy-boto3-appconfig-1.24.36.post1.tar.gz", hash = "sha256:e1916b3754915cb411ef977083500e1f30f81f7b3aea6ff5eed1cec91944dea6"}, - {file = "mypy_boto3_appconfig-1.24.36.post1-py3-none-any.whl", hash = "sha256:a5dbe549dbebf4bc7a6cfcbfa9dff89ceb4983c042b785763ee656504bdb49f6"}, -] -mypy-boto3-cloudformation = [ - {file = "mypy-boto3-cloudformation-1.24.36.post1.tar.gz", hash = "sha256:ed7df9ae3a8390a145229122a1489d0a58bbf9986cb54f0d7a65ed54f12c8e63"}, - {file = "mypy_boto3_cloudformation-1.24.36.post1-py3-none-any.whl", hash = "sha256:b39020c13a876bb18908aad22326478d0ac3faec0bdac0d2c11dc318c9dcf149"}, -] -mypy-boto3-cloudwatch = [ - {file = "mypy-boto3-cloudwatch-1.24.55.tar.gz", hash = "sha256:f8950de7a93b3db890cd8524514a2245d9b5fd83ce2dd60a37047a2cd42d5dd6"}, - {file = "mypy_boto3_cloudwatch-1.24.55-py3-none-any.whl", hash = "sha256:23faf8fdfe928f9dcce453a60b03bda69177554eb88c2d7e5240ff91b5b14388"}, -] -mypy-boto3-dynamodb = [ - {file = "mypy-boto3-dynamodb-1.24.55.post1.tar.gz", hash = "sha256:c469223c15556d93d247d38c0c31ce3c08d8073ca4597158a27abc70b8d7fbee"}, - {file = "mypy_boto3_dynamodb-1.24.55.post1-py3-none-any.whl", hash = "sha256:c762975d023b356c573d58105c7bfc1b9e7ee62c1299f09784e9dede533179e1"}, -] -mypy-boto3-lambda = [ - {file = "mypy-boto3-lambda-1.24.54.tar.gz", hash = "sha256:c76d28d84bdf94c8980acd85bc07f2747559ca11a990fd6785c9c2389e13aff1"}, - {file = "mypy_boto3_lambda-1.24.54-py3-none-any.whl", hash = "sha256:231b6aac22b107ebb7afa2ec6dc1311b769dbdd5bfae957cf60db3e8bc3133d7"}, -] -mypy-boto3-logs = [ - {file = "mypy-boto3-logs-1.24.36.post1.tar.gz", hash = "sha256:8b00c2d5328e72023b1d1acd65e7cea7854f07827d23ce21c78391ca74271290"}, - {file = "mypy_boto3_logs-1.24.36.post1-py3-none-any.whl", hash = "sha256:f96257ec06099bfda1ce5f35b410e7fb93fb601bc312e8d7a09b13adaefd23f0"}, -] -mypy-boto3-s3 = [ - {file = "mypy-boto3-s3-1.24.36.post1.tar.gz", hash = "sha256:3bd7e06f9ade5059eae2181d7a9f1a41e7fa807ad3e94c01c9901838e87e0abe"}, - {file = "mypy_boto3_s3-1.24.36.post1-py3-none-any.whl", hash = "sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a"}, -] -mypy-boto3-secretsmanager = [ - {file = "mypy-boto3-secretsmanager-1.24.54.tar.gz", hash = "sha256:a846b79f86e218a794dbc858c08290bb6aebffa180c80cf0a463c32a04621ff1"}, - {file = "mypy_boto3_secretsmanager-1.24.54-py3-none-any.whl", hash = "sha256:b89c9a0ff65a8ab2c4e4d3f6e721a0477b7d0fec246ffc08e4378420eb50b4d0"}, -] -mypy-boto3-ssm = [ - {file = "mypy-boto3-ssm-1.24.39.post2.tar.gz", hash = "sha256:2859bdcef110d9cc53007a7adba9c765e804b886f98d742a496bb8f7dac07308"}, - {file = "mypy_boto3_ssm-1.24.39.post2-py3-none-any.whl", hash = "sha256:bfdb434c513fbb1f3bc4b5c158ed4e7a46cb578e5eb01e818d45f4f38296ef2c"}, -] -mypy-boto3-xray = [ - {file = "mypy-boto3-xray-1.24.36.post1.tar.gz", hash = "sha256:104f1ecf7f1f6278c582201e71a7ab64843d3a3fdc8f23295cf68788cc77e9bb"}, - {file = "mypy_boto3_xray-1.24.36.post1-py3-none-any.whl", hash = "sha256:97b9f0686c717c8be99ac06cb52febaf71712b4e4cd0b61ed2eb5ed012a9b5fd"}, -] -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"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] -pbr = [ - {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"}, - {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"}, -] -pdoc3 = [ - {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, -] -platformdirs = [ - {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, - {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -publication = [ - {file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"}, - {file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -py-cpuinfo = [ - {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, -] -pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, -] -pydantic = [ - {file = "pydantic-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e"}, - {file = "pydantic-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08"}, - {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c"}, - {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131"}, - {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"}, - {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567"}, - {file = "pydantic-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044"}, - {file = "pydantic-1.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555"}, - {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84"}, - {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f"}, - {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb"}, - {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b"}, - {file = "pydantic-1.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001"}, - {file = "pydantic-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56"}, - {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4"}, - {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f"}, - {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979"}, - {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d"}, - {file = "pydantic-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7"}, - {file = "pydantic-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3"}, - {file = "pydantic-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa"}, - {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3"}, - {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0"}, - {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8"}, - {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8"}, - {file = "pydantic-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326"}, - {file = "pydantic-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801"}, - {file = "pydantic-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44"}, - {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747"}, - {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453"}, - {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb"}, - {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15"}, - {file = "pydantic-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55"}, - {file = "pydantic-1.9.2-py3-none-any.whl", hash = "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e"}, - {file = "pydantic-1.9.2.tar.gz", hash = "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d"}, -] -pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, -] -pygments = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, -] -pymdown-extensions = [ - {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, - {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, -] -pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, -] -pytest = [ - {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, - {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.16.0.tar.gz", hash = "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"}, - {file = "pytest_asyncio-0.16.0-py3-none-any.whl", hash = "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b"}, -] -pytest-benchmark = [ - {file = "pytest-benchmark-3.4.1.tar.gz", hash = "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47"}, - {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"}, -] -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-forked = [ - {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, - {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, -] -pytest-mock = [ - {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, - {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, -] -pytest-xdist = [ - {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, - {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -python-snappy = [ - {file = "python-snappy-0.6.1.tar.gz", hash = "sha256:b6a107ab06206acc5359d4c5632bd9b22d448702a79b3169b0c62e0fb808bb2a"}, - {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b7f920eaf46ebf41bd26f9df51c160d40f9e00b7b48471c3438cb8d027f7fb9b"}, - {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ec533a8c1f8df797bded662ec3e494d225b37855bb63eb0d75464a07947477c"}, - {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6f8bf4708a11b47517baf962f9a02196478bbb10fdb9582add4aa1459fa82380"}, - {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d0c019ee7dcf2c60e240877107cddbd95a5b1081787579bf179938392d66480"}, - {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb18d9cd7b3f35a2f5af47bb8ed6a5bdbf4f3ddee37f3daade4ab7864c292f5b"}, - {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b265cde49774752aec9ca7f5d272e3f98718164afc85521622a8a5394158a2b5"}, - {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d017775851a778ec9cc32651c4464079d06d927303c2dde9ae9830ccf6fe94e1"}, - {file = "python_snappy-0.6.1-cp310-cp310-win32.whl", hash = "sha256:8277d1f6282463c40761f802b742f833f9f2449fcdbb20a96579aa05c8feb614"}, - {file = "python_snappy-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2aaaf618c68d8c9daebc23a20436bd01b09ee70d7fbf7072b7f38b06d2fab539"}, - {file = "python_snappy-0.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:277757d5dad4e239dc1417438a0871b65b1b155beb108888e7438c27ffc6a8cc"}, - {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e066a0586833d610c4bbddba0be5ba0e3e4f8e0bc5bb6d82103d8f8fc47bb59a"}, - {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0d489b50f49433494160c45048fe806de6b3aeab0586e497ebd22a0bab56e427"}, - {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463fd340a499d47b26ca42d2f36a639188738f6e2098c6dbf80aef0e60f461e1"}, - {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9837ac1650cc68d22a3cf5f15fb62c6964747d16cecc8b22431f113d6e39555d"}, - {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e973e637112391f05581f427659c05b30b6843bc522a65be35ac7b18ce3dedd"}, - {file = "python_snappy-0.6.1-cp36-cp36m-win32.whl", hash = "sha256:c20498bd712b6e31a4402e1d027a1cd64f6a4a0066a3fe3c7344475886d07fdf"}, - {file = "python_snappy-0.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59e975be4206cc54d0a112ef72fa3970a57c2b1bcc2c97ed41d6df0ebe518228"}, - {file = "python_snappy-0.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2a7e528ab6e09c0d67dcb61a1730a292683e5ff9bb088950638d3170cf2a0a54"}, - {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39692bedbe0b717001a99915ac0eb2d9d0bad546440d392a2042b96d813eede1"}, - {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a7620404da966f637b9ce8d4d3d543d363223f7a12452a575189c5355fc2d25"}, - {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7778c224efc38a40d274da4eb82a04cac27aae20012372a7db3c4bbd8926c4d4"}, - {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d029f7051ec1bbeaa3e03030b6d8ed47ceb69cae9016f493c802a08af54e026"}, - {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ad38bc98d0b0497a0b0dbc29409bcabfcecff4511ed7063403c86de16927bc"}, - {file = "python_snappy-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:5a453c45178d7864c1bdd6bfe0ee3ed2883f63b9ba2c9bb967c6b586bf763f96"}, - {file = "python_snappy-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9f0c0d88b84259f93c3aa46398680646f2c23e43394779758d9f739c34e15295"}, - {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb05c28298803a74add08ba496879242ef159c75bc86a5406fac0ffc7dd021b"}, - {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9eac51307c6a1a38d5f86ebabc26a889fddf20cbba7a116ccb54ba1446601d5b"}, - {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:88b6ea78b83d2796f330b0af1b70cdd3965dbdab02d8ac293260ec2c8fe340ee"}, - {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c07220408d3268e8268c9351c5c08041bc6f8c6172e59d398b71020df108541"}, - {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4038019b1bcaadde726a57430718394076c5a21545ebc5badad2c045a09546cf"}, - {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc96668d9c7cc656609764275c5f8da58ef56d89bdd6810f6923d36497468ff7"}, - {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf5bb9254e1c38aacf253d510d3d9be631bba21f3d068b17672b38b5cbf2fff5"}, - {file = "python_snappy-0.6.1-cp38-cp38-win32.whl", hash = "sha256:eaf905a580f2747c4a474040a5063cd5e0cc3d1d2d6edb65f28196186493ad4a"}, - {file = "python_snappy-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:546c1a7470ecbf6239101e9aff0f709b68ca0f0268b34d9023019a55baa1f7c6"}, - {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3a013895c64352b49d0d8e107a84f99631b16dbab156ded33ebf0becf56c8b2"}, - {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fb9a88a4dd6336488f3de67ce75816d0d796dce53c2c6e4d70e0b565633c7fd"}, - {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:735cd4528c55dbe4516d6d2b403331a99fc304f8feded8ae887cf97b67d589bb"}, - {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:90b0186516b7a101c14764b0c25931b741fb0102f21253eff67847b4742dfc72"}, - {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a993dc8aadd901915a510fe6af5f20ae4256f527040066c22a154db8946751f"}, - {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:530bfb9efebcc1aab8bb4ebcbd92b54477eed11f6cf499355e882970a6d3aa7d"}, - {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5843feb914796b1f0405ccf31ea0fb51034ceb65a7588edfd5a8250cb369e3b2"}, - {file = "python_snappy-0.6.1-cp39-cp39-win32.whl", hash = "sha256:66c80e9b366012dbee262bb1869e4fc5ba8786cda85928481528bc4a72ec2ee8"}, - {file = "python_snappy-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d3cafdf454354a621c8ab7408e45aa4e9d5c0b943b61ff4815f71ca6bdf0130"}, - {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586724a0276d7a6083a17259d0b51622e492289a9998848a1b01b6441ca12b2f"}, - {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2be4f4550acd484912441f5f1209ba611ac399aac9355fee73611b9a0d4f949c"}, - {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdb6942180660bda7f7d01f4c0def3cfc72b1c6d99aad964801775a3e379aba"}, - {file = "python_snappy-0.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:03bb511380fca2a13325b6f16fe8234c8e12da9660f0258cd45d9a02ffc916af"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -pyyaml-env-tag = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] -radon = [ - {file = "radon-5.1.0-py2.py3-none-any.whl", hash = "sha256:fa74e018197f1fcb54578af0f675d8b8e2342bd8e0b72bef8197bc4c9e645f36"}, - {file = "radon-5.1.0.tar.gz", hash = "sha256:cb1d8752e5f862fb9e20d82b5f758cbc4fb1237c92c9a66450ea0ea7bf29aeee"}, -] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -retry = [ - {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"}, - {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"}, -] -"ruamel.yaml" = [ - {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, - {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, -] -"ruamel.yaml.clib" = [ - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, - {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, - {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, - {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, - {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, - {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, - {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, - {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, -] -s3transfer = [ - {file = "s3transfer-0.5.2-py3-none-any.whl", hash = "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971"}, - {file = "s3transfer-0.5.2.tar.gz", hash = "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -smmap = [ - {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, - {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, -] -stevedore = [ - {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, - {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, -] -tomli = [ - {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, - {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, -] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -types-requests = [ - {file = "types-requests-2.28.9.tar.gz", hash = "sha256:feaf581bd580497a47fe845d506fa3b91b484cf706ff27774e87659837de9962"}, - {file = "types_requests-2.28.9-py3-none-any.whl", hash = "sha256:86cb66d3de2f53eac5c09adc42cf6547eefbd0c7e1210beca1ee751c35d96083"}, -] -types-urllib3 = [ - {file = "types-urllib3-1.26.23.tar.gz", hash = "sha256:b78e819f0e350221d0689a5666162e467ba3910737bafda14b5c2c85e9bb1e56"}, - {file = "types_urllib3-1.26.23-py3-none-any.whl", hash = "sha256:333e675b188a1c1fd980b4b352f9e40572413a4c1ac689c23cd546e96310070a"}, -] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] +atomicwrites = [] +attrs = [] +aws-cdk-lib = [] +aws-xray-sdk = [] +bandit = [] +black = [] +boto3 = [] +botocore = [] +cattrs = [] +certifi = [] +charset-normalizer = [] +click = [] +colorama = [] +constructs = [] +coverage = [] +dataclasses = [] +decorator = [] +dnspython = [] +email-validator = [] +eradicate = [] +exceptiongroup = [] +execnet = [] +fastjsonschema = [] +filelock = [] +flake8 = [] +flake8-bugbear = [] +flake8-builtins = [] +flake8-comprehensions = [] +flake8-debugger = [] +flake8-eradicate = [] +flake8-fixme = [] +flake8-isort = [] +flake8-variables-names = [] +future = [] +ghp-import = [] +gitdb = [] +gitpython = [] +idna = [] +importlib-metadata = [] +importlib-resources = [] +iniconfig = [] +isort = [] +jinja2 = [] +jmespath = [] +jsii = [] +mako = [] +mando = [] +markdown = [] +markupsafe = [] +mccabe = [] +mergedeep = [] +mike = [] +mkdocs = [] +mkdocs-git-revision-date-plugin = [] +mkdocs-material = [] +mkdocs-material-extensions = [] +mypy = [] +mypy-boto3-appconfig = [] +mypy-boto3-cloudformation = [] +mypy-boto3-cloudwatch = [] +mypy-boto3-dynamodb = [] +mypy-boto3-lambda = [] +mypy-boto3-logs = [] +mypy-boto3-s3 = [] +mypy-boto3-secretsmanager = [] +mypy-boto3-ssm = [] +mypy-boto3-xray = [] +mypy-extensions = [] +packaging = [] +pathspec = [] +pbr = [] +pdoc3 = [] +platformdirs = [] +pluggy = [] +publication = [] +py = [] +py-cpuinfo = [] +pycodestyle = [] +pydantic = [] +pyflakes = [] +pygments = [] +pymdown-extensions = [] +pyparsing = [] +pytest = [] +pytest-asyncio = [] +pytest-benchmark = [] +pytest-cov = [] +pytest-forked = [] +pytest-mock = [] +pytest-xdist = [] +python-dateutil = [] +python-snappy = [] +pyyaml = [] +pyyaml-env-tag = [] +radon = [] +requests = [] +retry = [] +"ruamel.yaml" = [] +"ruamel.yaml.clib" = [] +s3transfer = [] +six = [] +smmap = [] +stevedore = [] +tomli = [] +typed-ast = [] +types-requests = [] +types-urllib3 = [] +typing-extensions = [] urllib3 = [] -watchdog = [ - {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, - {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, - {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"}, - {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"}, - {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"}, - {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"}, - {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"}, - {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"}, - {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"}, - {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"}, - {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"}, - {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"}, - {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"}, - {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"}, - {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"}, - {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"}, - {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"}, - {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"}, -] -wrapt = [ - {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, - {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, - {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, - {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, - {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, - {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, - {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, - {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, - {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, - {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, - {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, - {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, - {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, - {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, - {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, - {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, - {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, - {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, - {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, - {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, - {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, - {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, - {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, - {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, - {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, - {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, - {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, - {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, - {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, - {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, - {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, - {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, -] -xenon = [ - {file = "xenon-0.9.0-py2.py3-none-any.whl", hash = "sha256:994c80c7f1c6d40596b600b93734d85a5739208f31895ef99f1e4d362caf9e35"}, - {file = "xenon-0.9.0.tar.gz", hash = "sha256:d2b9cb6c6260f771a432c1e588e51fddb17858f88f73ef641e7532f7a5f58fb8"}, -] -zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, -] +watchdog = [] +wrapt = [] +xenon = [] +zipp = [] diff --git a/pyproject.toml b/pyproject.toml index b12fdcc092a..d70f5fb5fbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ types-requests = "^2.28.8" typing-extensions = { version = "^4.3.0", python = ">=3.7" } python-snappy = "^0.6.1" mkdocs-material = { version = "^8.3.9", python = ">=3.7" } +filelock = { version = "^3.8.0", python = ">=3.7" } [tool.poetry.extras] pydantic = ["pydantic", "email-validator"] diff --git a/tests/unit/test_tracing.py b/tests/unit/test_tracing.py index 2482b0177d3..d9c5b91214a 100644 --- a/tests/unit/test_tracing.py +++ b/tests/unit/test_tracing.py @@ -8,6 +8,8 @@ from aws_lambda_powertools import Tracer +# Maintenance: This should move to Functional tests and use Fake over mocks. + @pytest.fixture def dummy_response(): diff --git a/tests/unit/test_utilities_batch.py b/tests/unit/test_utilities_batch.py index 57de0223404..8cc4f0b0225 100644 --- a/tests/unit/test_utilities_batch.py +++ b/tests/unit/test_utilities_batch.py @@ -4,6 +4,8 @@ from aws_lambda_powertools.utilities.batch import PartialSQSProcessor from aws_lambda_powertools.utilities.batch.exceptions import SQSBatchProcessingError +# Maintenance: This will be deleted as part of legacy Batch deprecation + @pytest.fixture(scope="function") def sqs_event(): From ee69215f718e7e8436ace9b588d3aad0c70fb88e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 06:58:32 +0200 Subject: [PATCH 49/59] chore(deps-dev): bump flake8-bugbear from 22.8.22 to 22.8.23 (#1473) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 944 +++++++++++++++++++++++++++++++++++++++++++------ pyproject.toml | 2 +- 2 files changed, 830 insertions(+), 116 deletions(-) diff --git a/poetry.lock b/poetry.lock index f77f61a9189..c6696bdff76 100644 --- a/poetry.lock +++ b/poetry.lock @@ -324,7 +324,7 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "flake8-bugbear" -version = "22.8.22" +version = "22.8.23" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." category = "dev" optional = false @@ -1368,119 +1368,833 @@ pydantic = ["pydantic", "email-validator"] [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "223c6087ab0a58dc81fe9329849c3a0f8367685dcde88c2479d271808ccb5416" +content-hash = "a5ff8f9945c42eeee596b973e484efae6bcfde17a0e37cc708cac05b635cec1f" [metadata.files] -atomicwrites = [] -attrs = [] -aws-cdk-lib = [] -aws-xray-sdk = [] -bandit = [] -black = [] -boto3 = [] -botocore = [] -cattrs = [] -certifi = [] -charset-normalizer = [] -click = [] -colorama = [] -constructs = [] -coverage = [] -dataclasses = [] -decorator = [] -dnspython = [] -email-validator = [] -eradicate = [] -exceptiongroup = [] -execnet = [] -fastjsonschema = [] -filelock = [] -flake8 = [] -flake8-bugbear = [] -flake8-builtins = [] -flake8-comprehensions = [] -flake8-debugger = [] -flake8-eradicate = [] -flake8-fixme = [] -flake8-isort = [] -flake8-variables-names = [] -future = [] -ghp-import = [] -gitdb = [] -gitpython = [] -idna = [] -importlib-metadata = [] -importlib-resources = [] -iniconfig = [] -isort = [] -jinja2 = [] -jmespath = [] -jsii = [] -mako = [] -mando = [] -markdown = [] -markupsafe = [] -mccabe = [] -mergedeep = [] -mike = [] -mkdocs = [] -mkdocs-git-revision-date-plugin = [] -mkdocs-material = [] -mkdocs-material-extensions = [] -mypy = [] -mypy-boto3-appconfig = [] -mypy-boto3-cloudformation = [] -mypy-boto3-cloudwatch = [] -mypy-boto3-dynamodb = [] -mypy-boto3-lambda = [] -mypy-boto3-logs = [] -mypy-boto3-s3 = [] -mypy-boto3-secretsmanager = [] -mypy-boto3-ssm = [] -mypy-boto3-xray = [] -mypy-extensions = [] -packaging = [] -pathspec = [] -pbr = [] -pdoc3 = [] -platformdirs = [] -pluggy = [] -publication = [] -py = [] -py-cpuinfo = [] -pycodestyle = [] -pydantic = [] -pyflakes = [] -pygments = [] -pymdown-extensions = [] -pyparsing = [] -pytest = [] -pytest-asyncio = [] -pytest-benchmark = [] -pytest-cov = [] -pytest-forked = [] -pytest-mock = [] -pytest-xdist = [] -python-dateutil = [] -python-snappy = [] -pyyaml = [] -pyyaml-env-tag = [] -radon = [] -requests = [] -retry = [] -"ruamel.yaml" = [] -"ruamel.yaml.clib" = [] -s3transfer = [] -six = [] -smmap = [] -stevedore = [] -tomli = [] -typed-ast = [] -types-requests = [] -types-urllib3 = [] -typing-extensions = [] -urllib3 = [] -watchdog = [] -wrapt = [] -xenon = [] -zipp = [] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +aws-cdk-lib = [ + {file = "aws-cdk-lib-2.23.0.tar.gz", hash = "sha256:3e07d1c6b320795d38567be183e56c2125b4c4492589775257aabec3d3e2a384"}, + {file = "aws_cdk_lib-2.23.0-py3-none-any.whl", hash = "sha256:1ec04a146d3364cd0fc4da08e3f8ca25e28df68abaa90641936db17a415ca4bc"}, +] +aws-xray-sdk = [ + {file = "aws-xray-sdk-2.10.0.tar.gz", hash = "sha256:9b14924fd0628cf92936055864655354003f0b1acc3e1c3ffde6403d0799dd7a"}, + {file = "aws_xray_sdk-2.10.0-py2.py3-none-any.whl", hash = "sha256:7551e81a796e1a5471ebe84844c40e8edf7c218db33506d046fec61f7495eda4"}, +] +bandit = [ + {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"}, + {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"}, +] +black = [ + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, +] +boto3 = [ + {file = "boto3-1.23.10-py3-none-any.whl", hash = "sha256:40d08614f17a69075e175c02c5d5aab69a6153fd50e40fa7057b913ac7bf40e7"}, + {file = "boto3-1.23.10.tar.gz", hash = "sha256:2a4395e3241c20eef441d7443a5e6eaa0ee3f7114653fb9d9cef41587526f7bd"}, +] +botocore = [ + {file = "botocore-1.26.10-py3-none-any.whl", hash = "sha256:8a4a984bf901ccefe40037da11ba2abd1ddbcb3b490a492b7f218509c99fc12f"}, + {file = "botocore-1.26.10.tar.gz", hash = "sha256:5df2cf7ebe34377470172bd0bbc582cf98c5cbd02da0909a14e9e2885ab3ae9c"}, +] +cattrs = [ + {file = "cattrs-1.0.0-py2.py3-none-any.whl", hash = "sha256:616972ae3dfa6e623a40ad3cb845420e64942989152774ab055e5c2b2f89f997"}, + {file = "cattrs-1.0.0.tar.gz", hash = "sha256:b7ab5cf8ad127c42eefd01410c1c6e28569a45a255ea80ed968511873c433c7a"}, + {file = "cattrs-22.1.0-py3-none-any.whl", hash = "sha256:d55c477b4672f93606e992049f15d526dc7867e6c756cd6256d4af92e2b1e364"}, + {file = "cattrs-22.1.0.tar.gz", hash = "sha256:94b67b64cf92c994f8784c40c082177dc916e0489a73a9a36b24eb18a9db40c6"}, +] +certifi = [ + {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, + {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, +] +charset-normalizer = [ + {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-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +constructs = [ + {file = "constructs-10.1.1-py3-none-any.whl", hash = "sha256:c1f3deb196f54e070ded3c92c4339f73ef2b6022d35fb34908c0ebfa7ef8a640"}, + {file = "constructs-10.1.1.tar.gz", hash = "sha256:6ce0dd1352367237b5d7c51a25740482c852735d2a5e067c536acc1657f39ea5"}, +] +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"}, +] +dataclasses = [ + {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, + {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, +] +decorator = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +dnspython = [ + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, +] +email-validator = [ + {file = "email_validator-1.2.1-py2.py3-none-any.whl", hash = "sha256:c8589e691cf73eb99eed8d10ce0e9cbb05a0886ba920c8bcb7c82873f4c5789c"}, + {file = "email_validator-1.2.1.tar.gz", hash = "sha256:6757aea012d40516357c0ac2b1a4c31219ab2f899d26831334c5d069e8b6c3d8"}, +] +eradicate = [ + {file = "eradicate-2.1.0-py3-none-any.whl", hash = "sha256:8bfaca181db9227dc88bdbce4d051a9627604c2243e7d85324f6d6ce0fd08bb2"}, + {file = "eradicate-2.1.0.tar.gz", hash = "sha256:aac7384ab25b1bf21c4c012de9b4bf8398945a14c98c911545b2ea50ab558014"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.0.0rc8-py3-none-any.whl", hash = "sha256:ab0a968e1ef769e55d9a596f4a89f7be9ffedbc9fdefdb77cc68cf5c33ce1035"}, + {file = "exceptiongroup-1.0.0rc8.tar.gz", hash = "sha256:6990c24f06b8d33c8065cfe43e5e8a4bfa384e0358be036af9cc60b6321bd11a"}, +] +execnet = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] +fastjsonschema = [ + {file = "fastjsonschema-2.16.1-py3-none-any.whl", hash = "sha256:2f7158c4de792555753d6c2277d6a2af2d406dfd97aeca21d17173561ede4fe6"}, + {file = "fastjsonschema-2.16.1.tar.gz", hash = "sha256:d6fa3ffbe719768d70e298b9fb847484e2bdfdb7241ed052b8d57a9294a8c334"}, +] +filelock = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] +flake8 = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] +flake8-bugbear = [ + {file = "flake8-bugbear-22.8.23.tar.gz", hash = "sha256:de0717d11124a082118dd08387b34fd86b2721642ec2d8e92be66cfa5ea7c445"}, + {file = "flake8_bugbear-22.8.23-py3-none-any.whl", hash = "sha256:1b0ebe0873d1cd55bf9f1588bfcb930db339018ef44a3981a26532daa9fd14a8"}, +] +flake8-builtins = [ + {file = "flake8-builtins-1.5.3.tar.gz", hash = "sha256:09998853b2405e98e61d2ff3027c47033adbdc17f9fe44ca58443d876eb00f3b"}, + {file = "flake8_builtins-1.5.3-py2.py3-none-any.whl", hash = "sha256:7706babee43879320376861897e5d1468e396a40b8918ed7bccf70e5f90b8687"}, +] +flake8-comprehensions = [ + {file = "flake8-comprehensions-3.7.0.tar.gz", hash = "sha256:6b3218b2dde8ac5959c6476cde8f41a79e823c22feb656be2710cd2a3232cef9"}, + {file = "flake8_comprehensions-3.7.0-py3-none-any.whl", hash = "sha256:a5d7aea6315bbbd6fbcb2b4e80bff6a54d1600155e26236e555d0c6fe1d62522"}, +] +flake8-debugger = [ + {file = "flake8-debugger-4.0.0.tar.gz", hash = "sha256:e43dc777f7db1481db473210101ec2df2bd39a45b149d7218a618e954177eda6"}, + {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"}, +] +flake8-eradicate = [ + {file = "flake8-eradicate-1.3.0.tar.gz", hash = "sha256:e4c98f00d17dc8653e3388cac2624cd81e9735de2fd4a8dcf99029633ebd7a63"}, + {file = "flake8_eradicate-1.3.0-py3-none-any.whl", hash = "sha256:85a71e0c5f4e07f7c6c5fec520483561fd6bd295417d622855bdeade99242e3d"}, +] +flake8-fixme = [ + {file = "flake8-fixme-1.1.1.tar.gz", hash = "sha256:50cade07d27a4c30d4f12351478df87339e67640c83041b664724bda6d16f33a"}, + {file = "flake8_fixme-1.1.1-py2.py3-none-any.whl", hash = "sha256:226a6f2ef916730899f29ac140bed5d4a17e5aba79f00a0e3ae1eff1997cb1ac"}, +] +flake8-isort = [ + {file = "flake8-isort-4.2.0.tar.gz", hash = "sha256:26571500cd54976bbc0cf1006ffbcd1a68dd102f816b7a1051b219616ba9fee0"}, + {file = "flake8_isort-4.2.0-py3-none-any.whl", hash = "sha256:5b87630fb3719bf4c1833fd11e0d9534f43efdeba524863e15d8f14a7ef6adbf"}, +] +flake8-variables-names = [ + {file = "flake8_variables_names-0.0.4.tar.gz", hash = "sha256:d6fa0571a807c72940b5773827c5760421ea6f8206595ff0a8ecfa01e42bf2cf"}, +] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] +ghp-import = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] +gitdb = [ + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, +] +gitpython = [ + {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"}, + {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, + {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, +] +importlib-resources = [ + {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, + {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, +] +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.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +jinja2 = [ + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, +] +jmespath = [ + {file = "jmespath-0.10.0-py2.py3-none-any.whl", hash = "sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"}, + {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, +] +jsii = [ + {file = "jsii-1.57.0-py3-none-any.whl", hash = "sha256:4888091986a9ed8d50b042cc9c35a9564dd54c19e78adb890bf06d9ffac1b325"}, + {file = "jsii-1.57.0.tar.gz", hash = "sha256:ff7a3c51c1a653dd8a4342043b5f8e40b928bc617e3141e0d5d66175d22a754b"}, +] +mako = [ + {file = "Mako-1.1.6-py2.py3-none-any.whl", hash = "sha256:afaf8e515d075b22fad7d7b8b30e4a1c90624ff2f3733a06ec125f5a5f043a57"}, + {file = "Mako-1.1.6.tar.gz", hash = "sha256:4e9e345a41924a954251b95b4b28e14a301145b544901332e658907a7464b6b2"}, +] +mando = [ + {file = "mando-0.6.4-py2.py3-none-any.whl", hash = "sha256:4ce09faec7e5192ffc3c57830e26acba0fd6cd11e1ee81af0d4df0657463bd1c"}, + {file = "mando-0.6.4.tar.gz", hash = "sha256:79feb19dc0f097daa64a1243db578e7674909b75f88ac2220f1c065c10a0d960"}, +] +markdown = [ + {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, + {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, +] +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"}, +] +mergedeep = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] +mike = [ + {file = "mike-0.6.0-py3-none-any.whl", hash = "sha256:cef9b9c803ff5c3fbb410f51f5ceb00902a9fe16d9fabd93b69c65cf481ab5a1"}, + {file = "mike-0.6.0.tar.gz", hash = "sha256:6d6239de2a60d733da2f34617e9b9a14c4b5437423b47e524f14dc96d6ce5f2f"}, +] +mkdocs = [ + {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, + {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, +] +mkdocs-git-revision-date-plugin = [ + {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, +] +mkdocs-material = [ + {file = "mkdocs-material-8.4.1.tar.gz", hash = "sha256:92c70f94b2e1f8a05d9e05eec1c7af9dffc516802d69222329db89503c97b4f3"}, + {file = "mkdocs_material-8.4.1-py2.py3-none-any.whl", hash = "sha256:319a6254819ce9d864ff79de48c43842fccfdebb43e4e6820eef75216f8cfb0a"}, +] +mkdocs-material-extensions = [ + {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, + {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, +] +mypy = [ + {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"}, + {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"}, + {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"}, + {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"}, + {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"}, + {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"}, + {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"}, + {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"}, + {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"}, + {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"}, + {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"}, + {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"}, + {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"}, + {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"}, + {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"}, + {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"}, + {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"}, + {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"}, + {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"}, + {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"}, + {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"}, + {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"}, + {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"}, +] +mypy-boto3-appconfig = [ + {file = "mypy-boto3-appconfig-1.24.36.post1.tar.gz", hash = "sha256:e1916b3754915cb411ef977083500e1f30f81f7b3aea6ff5eed1cec91944dea6"}, + {file = "mypy_boto3_appconfig-1.24.36.post1-py3-none-any.whl", hash = "sha256:a5dbe549dbebf4bc7a6cfcbfa9dff89ceb4983c042b785763ee656504bdb49f6"}, +] +mypy-boto3-cloudformation = [ + {file = "mypy-boto3-cloudformation-1.24.36.post1.tar.gz", hash = "sha256:ed7df9ae3a8390a145229122a1489d0a58bbf9986cb54f0d7a65ed54f12c8e63"}, + {file = "mypy_boto3_cloudformation-1.24.36.post1-py3-none-any.whl", hash = "sha256:b39020c13a876bb18908aad22326478d0ac3faec0bdac0d2c11dc318c9dcf149"}, +] +mypy-boto3-cloudwatch = [ + {file = "mypy-boto3-cloudwatch-1.24.55.tar.gz", hash = "sha256:f8950de7a93b3db890cd8524514a2245d9b5fd83ce2dd60a37047a2cd42d5dd6"}, + {file = "mypy_boto3_cloudwatch-1.24.55-py3-none-any.whl", hash = "sha256:23faf8fdfe928f9dcce453a60b03bda69177554eb88c2d7e5240ff91b5b14388"}, +] +mypy-boto3-dynamodb = [ + {file = "mypy-boto3-dynamodb-1.24.55.post1.tar.gz", hash = "sha256:c469223c15556d93d247d38c0c31ce3c08d8073ca4597158a27abc70b8d7fbee"}, + {file = "mypy_boto3_dynamodb-1.24.55.post1-py3-none-any.whl", hash = "sha256:c762975d023b356c573d58105c7bfc1b9e7ee62c1299f09784e9dede533179e1"}, +] +mypy-boto3-lambda = [ + {file = "mypy-boto3-lambda-1.24.54.tar.gz", hash = "sha256:c76d28d84bdf94c8980acd85bc07f2747559ca11a990fd6785c9c2389e13aff1"}, + {file = "mypy_boto3_lambda-1.24.54-py3-none-any.whl", hash = "sha256:231b6aac22b107ebb7afa2ec6dc1311b769dbdd5bfae957cf60db3e8bc3133d7"}, +] +mypy-boto3-logs = [ + {file = "mypy-boto3-logs-1.24.36.post1.tar.gz", hash = "sha256:8b00c2d5328e72023b1d1acd65e7cea7854f07827d23ce21c78391ca74271290"}, + {file = "mypy_boto3_logs-1.24.36.post1-py3-none-any.whl", hash = "sha256:f96257ec06099bfda1ce5f35b410e7fb93fb601bc312e8d7a09b13adaefd23f0"}, +] +mypy-boto3-s3 = [ + {file = "mypy-boto3-s3-1.24.36.post1.tar.gz", hash = "sha256:3bd7e06f9ade5059eae2181d7a9f1a41e7fa807ad3e94c01c9901838e87e0abe"}, + {file = "mypy_boto3_s3-1.24.36.post1-py3-none-any.whl", hash = "sha256:30ae59b33c55f8b7b693170f9519ea5b91a2fbf31a73de79cdef57a27d784e5a"}, +] +mypy-boto3-secretsmanager = [ + {file = "mypy-boto3-secretsmanager-1.24.54.tar.gz", hash = "sha256:a846b79f86e218a794dbc858c08290bb6aebffa180c80cf0a463c32a04621ff1"}, + {file = "mypy_boto3_secretsmanager-1.24.54-py3-none-any.whl", hash = "sha256:b89c9a0ff65a8ab2c4e4d3f6e721a0477b7d0fec246ffc08e4378420eb50b4d0"}, +] +mypy-boto3-ssm = [ + {file = "mypy-boto3-ssm-1.24.39.post2.tar.gz", hash = "sha256:2859bdcef110d9cc53007a7adba9c765e804b886f98d742a496bb8f7dac07308"}, + {file = "mypy_boto3_ssm-1.24.39.post2-py3-none-any.whl", hash = "sha256:bfdb434c513fbb1f3bc4b5c158ed4e7a46cb578e5eb01e818d45f4f38296ef2c"}, +] +mypy-boto3-xray = [ + {file = "mypy-boto3-xray-1.24.36.post1.tar.gz", hash = "sha256:104f1ecf7f1f6278c582201e71a7ab64843d3a3fdc8f23295cf68788cc77e9bb"}, + {file = "mypy_boto3_xray-1.24.36.post1-py3-none-any.whl", hash = "sha256:97b9f0686c717c8be99ac06cb52febaf71712b4e4cd0b61ed2eb5ed012a9b5fd"}, +] +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"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pbr = [ + {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"}, + {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"}, +] +pdoc3 = [ + {file = "pdoc3-0.10.0-py3-none-any.whl", hash = "sha256:ba45d1ada1bd987427d2bf5cdec30b2631a3ff5fb01f6d0e77648a572ce6028b"}, + {file = "pdoc3-0.10.0.tar.gz", hash = "sha256:5f22e7bcb969006738e1aa4219c75a32f34c2d62d46dc9d2fb2d3e0b0287e4b7"}, +] +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +publication = [ + {file = "publication-0.0.3-py2.py3-none-any.whl", hash = "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6"}, + {file = "publication-0.0.3.tar.gz", hash = "sha256:68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +py-cpuinfo = [ + {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, +] +pycodestyle = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] +pydantic = [ + {file = "pydantic-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e"}, + {file = "pydantic-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08"}, + {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c"}, + {file = "pydantic-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131"}, + {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76"}, + {file = "pydantic-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567"}, + {file = "pydantic-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044"}, + {file = "pydantic-1.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555"}, + {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84"}, + {file = "pydantic-1.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f"}, + {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb"}, + {file = "pydantic-1.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b"}, + {file = "pydantic-1.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001"}, + {file = "pydantic-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56"}, + {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4"}, + {file = "pydantic-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f"}, + {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979"}, + {file = "pydantic-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d"}, + {file = "pydantic-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7"}, + {file = "pydantic-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3"}, + {file = "pydantic-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa"}, + {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3"}, + {file = "pydantic-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0"}, + {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8"}, + {file = "pydantic-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8"}, + {file = "pydantic-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326"}, + {file = "pydantic-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801"}, + {file = "pydantic-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44"}, + {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747"}, + {file = "pydantic-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453"}, + {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb"}, + {file = "pydantic-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15"}, + {file = "pydantic-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55"}, + {file = "pydantic-1.9.2-py3-none-any.whl", hash = "sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e"}, + {file = "pydantic-1.9.2.tar.gz", hash = "sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d"}, +] +pyflakes = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] +pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pymdown-extensions = [ + {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, + {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pytest = [ + {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, + {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.16.0.tar.gz", hash = "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"}, + {file = "pytest_asyncio-0.16.0-py3-none-any.whl", hash = "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b"}, +] +pytest-benchmark = [ + {file = "pytest-benchmark-3.4.1.tar.gz", hash = "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47"}, + {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"}, +] +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-forked = [ + {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, + {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, +] +pytest-mock = [ + {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, + {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, +] +pytest-xdist = [ + {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, + {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python-snappy = [ + {file = "python-snappy-0.6.1.tar.gz", hash = "sha256:b6a107ab06206acc5359d4c5632bd9b22d448702a79b3169b0c62e0fb808bb2a"}, + {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b7f920eaf46ebf41bd26f9df51c160d40f9e00b7b48471c3438cb8d027f7fb9b"}, + {file = "python_snappy-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4ec533a8c1f8df797bded662ec3e494d225b37855bb63eb0d75464a07947477c"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6f8bf4708a11b47517baf962f9a02196478bbb10fdb9582add4aa1459fa82380"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d0c019ee7dcf2c60e240877107cddbd95a5b1081787579bf179938392d66480"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb18d9cd7b3f35a2f5af47bb8ed6a5bdbf4f3ddee37f3daade4ab7864c292f5b"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b265cde49774752aec9ca7f5d272e3f98718164afc85521622a8a5394158a2b5"}, + {file = "python_snappy-0.6.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d017775851a778ec9cc32651c4464079d06d927303c2dde9ae9830ccf6fe94e1"}, + {file = "python_snappy-0.6.1-cp310-cp310-win32.whl", hash = "sha256:8277d1f6282463c40761f802b742f833f9f2449fcdbb20a96579aa05c8feb614"}, + {file = "python_snappy-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2aaaf618c68d8c9daebc23a20436bd01b09ee70d7fbf7072b7f38b06d2fab539"}, + {file = "python_snappy-0.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:277757d5dad4e239dc1417438a0871b65b1b155beb108888e7438c27ffc6a8cc"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e066a0586833d610c4bbddba0be5ba0e3e4f8e0bc5bb6d82103d8f8fc47bb59a"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0d489b50f49433494160c45048fe806de6b3aeab0586e497ebd22a0bab56e427"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:463fd340a499d47b26ca42d2f36a639188738f6e2098c6dbf80aef0e60f461e1"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9837ac1650cc68d22a3cf5f15fb62c6964747d16cecc8b22431f113d6e39555d"}, + {file = "python_snappy-0.6.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e973e637112391f05581f427659c05b30b6843bc522a65be35ac7b18ce3dedd"}, + {file = "python_snappy-0.6.1-cp36-cp36m-win32.whl", hash = "sha256:c20498bd712b6e31a4402e1d027a1cd64f6a4a0066a3fe3c7344475886d07fdf"}, + {file = "python_snappy-0.6.1-cp36-cp36m-win_amd64.whl", hash = "sha256:59e975be4206cc54d0a112ef72fa3970a57c2b1bcc2c97ed41d6df0ebe518228"}, + {file = "python_snappy-0.6.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2a7e528ab6e09c0d67dcb61a1730a292683e5ff9bb088950638d3170cf2a0a54"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:39692bedbe0b717001a99915ac0eb2d9d0bad546440d392a2042b96d813eede1"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6a7620404da966f637b9ce8d4d3d543d363223f7a12452a575189c5355fc2d25"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7778c224efc38a40d274da4eb82a04cac27aae20012372a7db3c4bbd8926c4d4"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d029f7051ec1bbeaa3e03030b6d8ed47ceb69cae9016f493c802a08af54e026"}, + {file = "python_snappy-0.6.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ad38bc98d0b0497a0b0dbc29409bcabfcecff4511ed7063403c86de16927bc"}, + {file = "python_snappy-0.6.1-cp37-cp37m-win32.whl", hash = "sha256:5a453c45178d7864c1bdd6bfe0ee3ed2883f63b9ba2c9bb967c6b586bf763f96"}, + {file = "python_snappy-0.6.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9f0c0d88b84259f93c3aa46398680646f2c23e43394779758d9f739c34e15295"}, + {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb05c28298803a74add08ba496879242ef159c75bc86a5406fac0ffc7dd021b"}, + {file = "python_snappy-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9eac51307c6a1a38d5f86ebabc26a889fddf20cbba7a116ccb54ba1446601d5b"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:88b6ea78b83d2796f330b0af1b70cdd3965dbdab02d8ac293260ec2c8fe340ee"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c07220408d3268e8268c9351c5c08041bc6f8c6172e59d398b71020df108541"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4038019b1bcaadde726a57430718394076c5a21545ebc5badad2c045a09546cf"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc96668d9c7cc656609764275c5f8da58ef56d89bdd6810f6923d36497468ff7"}, + {file = "python_snappy-0.6.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf5bb9254e1c38aacf253d510d3d9be631bba21f3d068b17672b38b5cbf2fff5"}, + {file = "python_snappy-0.6.1-cp38-cp38-win32.whl", hash = "sha256:eaf905a580f2747c4a474040a5063cd5e0cc3d1d2d6edb65f28196186493ad4a"}, + {file = "python_snappy-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:546c1a7470ecbf6239101e9aff0f709b68ca0f0268b34d9023019a55baa1f7c6"}, + {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e3a013895c64352b49d0d8e107a84f99631b16dbab156ded33ebf0becf56c8b2"}, + {file = "python_snappy-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fb9a88a4dd6336488f3de67ce75816d0d796dce53c2c6e4d70e0b565633c7fd"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:735cd4528c55dbe4516d6d2b403331a99fc304f8feded8ae887cf97b67d589bb"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:90b0186516b7a101c14764b0c25931b741fb0102f21253eff67847b4742dfc72"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a993dc8aadd901915a510fe6af5f20ae4256f527040066c22a154db8946751f"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:530bfb9efebcc1aab8bb4ebcbd92b54477eed11f6cf499355e882970a6d3aa7d"}, + {file = "python_snappy-0.6.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5843feb914796b1f0405ccf31ea0fb51034ceb65a7588edfd5a8250cb369e3b2"}, + {file = "python_snappy-0.6.1-cp39-cp39-win32.whl", hash = "sha256:66c80e9b366012dbee262bb1869e4fc5ba8786cda85928481528bc4a72ec2ee8"}, + {file = "python_snappy-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d3cafdf454354a621c8ab7408e45aa4e9d5c0b943b61ff4815f71ca6bdf0130"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:586724a0276d7a6083a17259d0b51622e492289a9998848a1b01b6441ca12b2f"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2be4f4550acd484912441f5f1209ba611ac399aac9355fee73611b9a0d4f949c"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdb6942180660bda7f7d01f4c0def3cfc72b1c6d99aad964801775a3e379aba"}, + {file = "python_snappy-0.6.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:03bb511380fca2a13325b6f16fe8234c8e12da9660f0258cd45d9a02ffc916af"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +pyyaml-env-tag = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] +radon = [ + {file = "radon-5.1.0-py2.py3-none-any.whl", hash = "sha256:fa74e018197f1fcb54578af0f675d8b8e2342bd8e0b72bef8197bc4c9e645f36"}, + {file = "radon-5.1.0.tar.gz", hash = "sha256:cb1d8752e5f862fb9e20d82b5f758cbc4fb1237c92c9a66450ea0ea7bf29aeee"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +retry = [ + {file = "retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606"}, + {file = "retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4"}, +] +"ruamel.yaml" = [ + {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, + {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, +] +"ruamel.yaml.clib" = [ + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:066f886bc90cc2ce44df8b5f7acfc6a7e2b2e672713f027136464492b0c34d7c"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"}, + {file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"}, + {file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d3c620a54748a3d4cf0bcfe623e388407c8e85a4b06b8188e126302bcab93ea8"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"}, + {file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:210c8fcfeff90514b7133010bf14e3bad652c8efde6b20e00c43854bf94fa5a6"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"}, + {file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:61bc5e5ca632d95925907c569daa559ea194a4d16084ba86084be98ab1cec1c6"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"}, + {file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1b4139a6ffbca8ef60fdaf9b33dec05143ba746a6f0ae0f9d11d38239211d335"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"}, + {file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"}, + {file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"}, +] +s3transfer = [ + {file = "s3transfer-0.5.2-py3-none-any.whl", hash = "sha256:7a6f4c4d1fdb9a2b640244008e142cbc2cd3ae34b386584ef044dd0f27101971"}, + {file = "s3transfer-0.5.2.tar.gz", hash = "sha256:95c58c194ce657a5f4fb0b9e60a84968c808888aed628cd98ab8771fe1db98ed"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +smmap = [ + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, +] +stevedore = [ + {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, + {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, +] +tomli = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +types-requests = [ + {file = "types-requests-2.28.9.tar.gz", hash = "sha256:feaf581bd580497a47fe845d506fa3b91b484cf706ff27774e87659837de9962"}, + {file = "types_requests-2.28.9-py3-none-any.whl", hash = "sha256:86cb66d3de2f53eac5c09adc42cf6547eefbd0c7e1210beca1ee751c35d96083"}, +] +types-urllib3 = [ + {file = "types-urllib3-1.26.23.tar.gz", hash = "sha256:b78e819f0e350221d0689a5666162e467ba3910737bafda14b5c2c85e9bb1e56"}, + {file = "types_urllib3-1.26.23-py3-none-any.whl", hash = "sha256:333e675b188a1c1fd980b4b352f9e40572413a4c1ac689c23cd546e96310070a"}, +] +typing-extensions = [ + {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, + {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] +watchdog = [ + {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, + {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, + {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"}, + {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"}, + {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"}, + {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"}, + {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"}, + {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"}, + {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"}, + {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"}, + {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"}, + {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"}, + {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"}, + {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"}, + {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"}, + {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"}, + {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"}, + {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"}, + {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"}, +] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] +xenon = [ + {file = "xenon-0.9.0-py2.py3-none-any.whl", hash = "sha256:994c80c7f1c6d40596b600b93734d85a5739208f31895ef99f1e4d362caf9e35"}, + {file = "xenon-0.9.0.tar.gz", hash = "sha256:d2b9cb6c6260f771a432c1e588e51fddb17858f88f73ef641e7532f7a5f58fb8"}, +] +zipp = [ + {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, + {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, +] diff --git a/pyproject.toml b/pyproject.toml index d70f5fb5fbd..aa6bfec1c1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,7 @@ bandit = "^1.7.1" radon = "^5.1.0" xenon = "^0.9.0" flake8-eradicate = "^1.2.1" -flake8-bugbear = "^22.7.1" +flake8-bugbear = "^22.8.23" mkdocs-git-revision-date-plugin = "^0.3.2" mike = "^0.6.0" mypy = "^0.971" From 8528a3ea818e2b6e1bf552345269f1d664106f6b Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Wed, 24 Aug 2022 10:29:13 +0200 Subject: [PATCH 50/59] chore(ci): temp disable e2e matrix --- .github/workflows/run-e2e-tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 95d0d6b2c69..6609a9957d6 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -20,7 +20,8 @@ jobs: contents: read strategy: matrix: - version: ["3.7", "3.8", "3.9"] +# version: ["3.7", "3.8", "3.9"] + version: ["3.7"] steps: - name: "Checkout" uses: actions/checkout@v3 From b3b22cdd0ca1e0bf7b29f8178859173b6fb9ddc6 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 24 Aug 2022 10:37:18 +0200 Subject: [PATCH 51/59] chore(ci): include py version in stack and cache lock --- tests/e2e/utils/infrastructure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/utils/infrastructure.py b/tests/e2e/utils/infrastructure.py index 7f232bb063f..cddd6844504 100644 --- a/tests/e2e/utils/infrastructure.py +++ b/tests/e2e/utils/infrastructure.py @@ -41,7 +41,7 @@ class PythonVersion(Enum): class BaseInfrastructure(ABC): def __init__(self, feature_name: str, handlers_dir: Path, layer_arn: str = "") -> None: self.feature_name = feature_name - self.stack_name = f"test-{feature_name}-{uuid4()}" + self.stack_name = f"test{PYTHON_RUNTIME_VERSION}-{feature_name}-{uuid4()}" self.handlers_dir = handlers_dir self.layer_arn = layer_arn self.stack_outputs: Dict[str, str] = {} @@ -249,7 +249,7 @@ def deploy_once( else: # tmp dir shared by all workers root_tmp_dir = tmp_path_factory.getbasetemp().parent - cache = root_tmp_dir / "cache.json" + cache = root_tmp_dir / f"{PYTHON_RUNTIME_VERSION}_cache.json" with FileLock(f"{cache}.lock"): # If cache exists, return stack outputs back From c8f3e4225f281a6ee0b688a9f675def633fa6eb9 Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 24 Aug 2022 10:39:53 +0200 Subject: [PATCH 52/59] chore(ci): revert e2e py version matrix --- .github/workflows/run-e2e-tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 6609a9957d6..95d0d6b2c69 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -20,8 +20,7 @@ jobs: contents: read strategy: matrix: -# version: ["3.7", "3.8", "3.9"] - version: ["3.7"] + version: ["3.7", "3.8", "3.9"] steps: - name: "Checkout" uses: actions/checkout@v3 From 2ade49ade325e9ccb4b12a4e922e18c7d26c745f Mon Sep 17 00:00:00 2001 From: heitorlessa Date: Wed, 24 Aug 2022 10:47:10 +0200 Subject: [PATCH 53/59] chore(ci): disable e2e py version matrix due to concurrent locking --- .github/workflows/run-e2e-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 95d0d6b2c69..6386e9f05e3 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -20,7 +20,9 @@ jobs: contents: read strategy: matrix: - version: ["3.7", "3.8", "3.9"] + # Maintenance: disabled until we discover concurrency lock issue with multiple versions and tmp + # version: ["3.7", "3.8", "3.9"] + version: ["3.7"] steps: - name: "Checkout" uses: actions/checkout@v3 From 48b504f2dfd0c9126730943e9f6637bd72c5d509 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Wed, 24 Aug 2022 15:45:18 +0200 Subject: [PATCH 54/59] fix(ci): calculate parallel jobs based on infrastructure needs (#1475) --- .github/workflows/run-e2e-tests.yml | 4 ++-- Makefile | 2 +- parallel_run_e2e.py | 16 ++++++++++++++++ tox.ini | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 3 deletions(-) create mode 100755 parallel_run_e2e.py create mode 100644 tox.ini diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 6386e9f05e3..5b1372d54aa 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -21,8 +21,8 @@ jobs: strategy: matrix: # Maintenance: disabled until we discover concurrency lock issue with multiple versions and tmp - # version: ["3.7", "3.8", "3.9"] - version: ["3.7"] + version: ["3.7", "3.8", "3.9"] + # version: ["3.7"] steps: - name: "Checkout" uses: actions/checkout@v3 diff --git a/Makefile b/Makefile index 3977ee8e7a7..7a212738c53 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ unit-test: poetry run pytest tests/unit e2e-test: - poetry run pytest -rP -n auto --dist loadfile -o log_cli=true tests/e2e + python parallel_run_e2e.py coverage-html: poetry run pytest -m "not perf" --ignore tests/e2e --cov=aws_lambda_powertools --cov-report=html diff --git a/parallel_run_e2e.py b/parallel_run_e2e.py new file mode 100755 index 00000000000..b9603701e5e --- /dev/null +++ b/parallel_run_e2e.py @@ -0,0 +1,16 @@ +""" Calculate how many parallel workers are needed to complete E2E infrastructure jobs across available CPU Cores """ +import subprocess +from pathlib import Path + + +def main(): + features = Path("tests/e2e").rglob("infrastructure.py") + workers = len(list(features)) - 1 + + command = f"poetry run pytest -n {workers} --dist loadfile -o log_cli=true tests/e2e" + print(f"Running E2E tests with: {command}") + subprocess.run(command.split(), shell=False) + + +if __name__ == "__main__": + main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000000..286b1c10ab0 --- /dev/null +++ b/tox.ini @@ -0,0 +1,15 @@ +[tox] +envlist = py37,py38,py39 + +[testenv] +deps = + filelock + pytest-xdist + pydantic + email-validator + +commands = python parallel_run_e2e.py + +; If you ever encounter another parallel lock across interpreters +; pip install tox tox-poetry +; tox -p --parallel-live \ No newline at end of file From 2cf223d010446e0c12e4bbc016f4ce18b924cb48 Mon Sep 17 00:00:00 2001 From: Heitor Lessa Date: Thu, 25 Aug 2022 10:09:12 +0200 Subject: [PATCH 55/59] chore(ci): prevent concurrent git update in critical workflows (#1478) --- .github/workflows/build_changelog.yml | 10 ++++++++ .../{publish.yml => on_release_notes.yml} | 25 +++++++++++-------- .../workflows/reusable_publish_changelog.yml | 9 +++++-- 3 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/build_changelog.yml rename .github/workflows/{publish.yml => on_release_notes.yml} (91%) diff --git a/.github/workflows/build_changelog.yml b/.github/workflows/build_changelog.yml new file mode 100644 index 00000000000..f0501083048 --- /dev/null +++ b/.github/workflows/build_changelog.yml @@ -0,0 +1,10 @@ +# Standalone workflow to update changelog if necessary +name: Build changelog + +on: + workflow_dispatch: + +jobs: + changelog: + needs: release + uses: ./.github/workflows/reusable_publish_changelog.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/on_release_notes.yml similarity index 91% rename from .github/workflows/publish.yml rename to .github/workflows/on_release_notes.yml index 16e51a5f542..563d1fefc79 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/on_release_notes.yml @@ -20,6 +20,9 @@ name: Publish to PyPi # See MAINTAINERS.md "Releasing a new version" for release mechanisms +env: + BRANCH: develop + on: release: types: [published] @@ -101,17 +104,14 @@ jobs: aws ssm put-parameter --name "powertools-python-release-version" --value $RELEASE_VERSION --overwrite aws codepipeline start-pipeline-execution --name ${{ secrets.AWS_SAR_PIPELINE_NAME }} - # NOTE: `event` type brings a detached head failing git setup - # and reusable workflows only work as a standalone job - # meaning we need to research for a solution that works for non-detached and detached mode - # changelog: - # needs: release - # permissions: - # contents: write - # uses: ./.github/workflows/reusable_publish_changelog.yml + changelog: + needs: release + permissions: + contents: write + uses: ./.github/workflows/reusable_publish_changelog.yml docs: - needs: release + needs: [release, changelog] permissions: contents: write pages: write @@ -122,10 +122,13 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Setup git client + - name: Git client setup and refresh tip run: | git config user.name "Release bot" - git config user.email aws-devax-open-source@amazon.com + git config user.email "aws-devax-open-source@amazon.com" + git config pull.rebase true + git config remote.origin.url >&- || git remote add origin https://github.com/$origin # Git Detached mode (release notes) doesn't have origin + git pull origin $BRANCH - name: Install poetry run: pipx install poetry - name: Set up Python diff --git a/.github/workflows/reusable_publish_changelog.yml b/.github/workflows/reusable_publish_changelog.yml index 9bd0959d1c8..2cb786ed86a 100644 --- a/.github/workflows/reusable_publish_changelog.yml +++ b/.github/workflows/reusable_publish_changelog.yml @@ -6,6 +6,9 @@ on: permissions: contents: write +env: + BRANCH: develop + jobs: publish_changelog: # Force Github action to run only a single job at a time (based on the group name) @@ -23,11 +26,13 @@ jobs: git config user.name "Release bot" git config user.email "aws-devax-open-source@amazon.com" git config pull.rebase true - git pull --rebase + git config remote.origin.url >&- || git remote add origin https://github.com/$origin # Git Detached mode (release notes) doesn't have origin + git pull origin $BRANCH - name: "Generate latest changelog" run: make changelog - name: Update Changelog in trunk run: | git add CHANGELOG.md git commit -m "update changelog with latest changes" - git push origin HEAD:refs/heads/develop + git pull origin $BRANCH # prevents concurrent branch update failing push + git push origin HEAD:refs/heads/$BRANCH From 75d2ff9c3104ffae43eb9024e4c16b26cf7bbdf1 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 25 Aug 2022 09:10:04 +0100 Subject: [PATCH 56/59] docs(middleware-factory): snippets split, improved, and lint (#1451) Co-authored-by: heitorlessa --- docs/media/middleware_factory_tracer_1.png | Bin 0 -> 51968 bytes docs/media/middleware_factory_tracer_2.png | Bin 0 -> 80366 bytes docs/utilities/middleware_factory.md | 157 ++++++++++++------ ...mbining_powertools_utilities_template.yaml | 136 +++++++++++++++ .../advanced_middleware_tracer_function.py | 44 +++++ .../advanced_middleware_tracer_payload.json | 5 + .../combining_powertools_utilities_event.json | 79 +++++++++ ...combining_powertools_utilities_function.py | 121 ++++++++++++++ .../combining_powertools_utilities_schema.py | 25 +++ ...started_middleware_after_logic_function.py | 39 +++++ ...tarted_middleware_after_logic_payload.json | 6 + ...tarted_middleware_before_logic_function.py | 47 ++++++ ...arted_middleware_before_logic_payload.json | 14 ++ ...ting_started_middleware_tracer_function.py | 38 +++++ ...ing_started_middleware_tracer_payload.json | 5 + ...started_middleware_with_params_function.py | 58 +++++++ ...tarted_middleware_with_params_payload.json | 23 +++ 17 files changed, 746 insertions(+), 51 deletions(-) create mode 100644 docs/media/middleware_factory_tracer_1.png create mode 100644 docs/media/middleware_factory_tracer_2.png create mode 100644 examples/middleware_factory/sam/combining_powertools_utilities_template.yaml create mode 100644 examples/middleware_factory/src/advanced_middleware_tracer_function.py create mode 100644 examples/middleware_factory/src/advanced_middleware_tracer_payload.json create mode 100644 examples/middleware_factory/src/combining_powertools_utilities_event.json create mode 100644 examples/middleware_factory/src/combining_powertools_utilities_function.py create mode 100644 examples/middleware_factory/src/combining_powertools_utilities_schema.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_after_logic_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json create mode 100644 examples/middleware_factory/src/getting_started_middleware_before_logic_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json create mode 100644 examples/middleware_factory/src/getting_started_middleware_tracer_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_tracer_payload.json create mode 100644 examples/middleware_factory/src/getting_started_middleware_with_params_function.py create mode 100644 examples/middleware_factory/src/getting_started_middleware_with_params_payload.json diff --git a/docs/media/middleware_factory_tracer_1.png b/docs/media/middleware_factory_tracer_1.png new file mode 100644 index 0000000000000000000000000000000000000000..70c1a0c1da15583ccf0722ef273aac187ac8b341 GIT binary patch literal 51968 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 U{{QhX?SH)M|3Ch``sdhx037G94FCWD literal 0 HcmV?d00001 diff --git a/docs/media/middleware_factory_tracer_2.png b/docs/media/middleware_factory_tracer_2.png new file mode 100644 index 0000000000000000000000000000000000000000..54f8917956533ddcaf1eac95484532132e4750a8 GIT binary patch literal 80366 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 + Middleware factory provides a decorator factory to create your own middleware to run logic before, and after each Lambda invocation synchronously. ## Key features * Run logic before, after, and handle exceptions -* Trace each middleware when requested +* Built-in tracing opt-in capability + +## Getting started + +???+ tip + All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}. + +You might need a custom middleware to abstract non-functional code. These are often custom authorization or any reusable logic you might need to run before/after a Lambda function invocation. -## Middleware with no params +### Middleware with no params You can create your own middleware using `lambda_handler_decorator`. The decorator factory expects 3 arguments in your function signature: @@ -18,74 +27,120 @@ You can create your own middleware using `lambda_handler_decorator`. The decorat * **event** - Lambda function invocation event * **context** - Lambda function context object -```python hl_lines="3-4 10" title="Creating your own middleware for before/after logic" -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +### Middleware with before logic + +=== "getting_started_middleware_before_logic_function.py" + ```python hl_lines="5 23 24 29 30 32 37 38" + --8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_function.py" + ``` + +=== "getting_started_middleware_before_logic_payload.json" + + ```json hl_lines="9-13" + --8<-- "examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json" + ``` -@lambda_handler_decorator -def middleware_before_after(handler, event, context): - # logic_before_handler_execution() - response = handler(event, context) - # logic_after_handler_execution() - return response +### Middleware with after logic -@middleware_before_after -def lambda_handler(event, context): - ... -``` +=== "getting_started_middleware_after_logic_function.py" + ```python hl_lines="7 14 15 21-23 37" + --8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_function.py" + ``` -## Middleware with params +=== "getting_started_middleware_after_logic_payload.json" + + ```json + --8<-- "examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json" + ``` + +### Middleware with params You can also have your own keyword arguments after the mandatory arguments. -```python hl_lines="2 12" title="Accepting arbitrary keyword arguments" -@lambda_handler_decorator -def obfuscate_sensitive_data(handler, event, context, fields: List = None): - # Obfuscate email before calling Lambda handler - if fields: - for field in fields: - if field in event: - event[field] = obfuscate(event[field]) +=== "getting_started_middleware_with_params_function.py" + ```python hl_lines="6 27 28 29 33 49" + --8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_function.py" + ``` + +=== "getting_started_middleware_with_params_payload.json" - return handler(event, context) + ```json hl_lines="18 19 20" + --8<-- "examples/middleware_factory/src/getting_started_middleware_with_params_payload.json" + ``` + +## Advanced + +For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights. -@obfuscate_sensitive_data(fields=["email"]) -def lambda_handler(event, context): - ... -``` +=== "advanced_middleware_tracer_function.py" + ```python hl_lines="7 9 12 16 17 19 25 42" + --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_function.py" + ``` -## Tracing middleware execution +=== "advanced_middleware_tracer_payload.json" + + ```json + --8<-- "examples/middleware_factory/src/advanced_middleware_tracer_payload.json" + ``` + +![Middleware advanced Tracer](../media/middleware_factory_tracer_2.png) + +### Tracing middleware **execution** If you are making use of [Tracer](../core/tracer.md), you can trace the execution of your middleware to ease operations. This makes use of an existing Tracer instance that you may have initialized anywhere in your code. -```python hl_lines="3" title="Tracing custom middlewares with Tracer" -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +???+ warning + You must [enable Active Tracing](../core/tracer/#permissions) in your Lambda function when using this feature, otherwise Lambda cannot send traces to XRay. -@lambda_handler_decorator(trace_execution=True) -def my_middleware(handler, event, context): - return handler(event, context) +=== "getting_started_middleware_tracer_function.py" + ```python hl_lines="8 14 15 36" + --8<-- "examples/middleware_factory/src/getting_started_middleware_tracer_function.py" + ``` -@my_middleware -def lambda_handler(event, context): - ... -``` +=== "getting_started_middleware_tracer_payload.json" -When executed, your middleware name will [appear in AWS X-Ray Trace details as](../core/tracer.md) `## middleware_name`. + ```json hl_lines="18 19 20" + --8<-- "examples/middleware_factory/src/getting_started_middleware_tracer_payload.json" + ``` -For advanced use cases, you can instantiate [Tracer](../core/tracer.md) inside your middleware, and add annotations as well as metadata for additional operational insights. +When executed, your middleware name will [appear in AWS X-Ray Trace details as](../core/tracer.md) `## middleware_name`, in this example the middleware name is `## middleware_with_tracing`. + +![Middleware simple Tracer](../media/middleware_factory_tracer_1.png) + +### Combining Powertools utilities + +You can create your own middleware and combine many features of Lambda Powertools such as [trace](../core/logger.md), [logs](../core/logger.md), [feature flags](feature_flags.md), [validation](validation.md), [jmespath_functions](jmespath_functions.md) and others to abstract non-functional code. + +In the example below, we create a Middleware with the following features: + +* Logs and traces +* Validate if the payload contains a specific header +* Extract specific keys from event +* Automatically add security headers on every execution +* Validate if a specific feature flag is enabled +* Save execution history to a DynamoDB table + +=== "combining_powertools_utilities_function.py" + ```python hl_lines="11 28 29 119 52 61 73" + --8<-- "examples/middleware_factory/src/combining_powertools_utilities_function.py" + ``` + +=== "combining_powertools_utilities_schema.py" + ```python hl_lines="12 14" + --8<-- "examples/middleware_factory/src/combining_powertools_utilities_schema.py" + ``` + +=== "combining_powertools_utilities_event.json" + ```python hl_lines="10" + --8<-- "examples/middleware_factory/src/combining_powertools_utilities_event.json" + ``` -```python hl_lines="6-8" title="Add custom tracing insights before/after in your middlware" -from aws_lambda_powertools.middleware_factory import lambda_handler_decorator -from aws_lambda_powertools import Tracer - -@lambda_handler_decorator(trace_execution=True) -def middleware_name(handler, event, context): - # tracer = Tracer() # Takes a copy of an existing tracer instance - # tracer.add_annotation... - # tracer.add_metadata... - return handler(event, context) -``` +=== "SAM TEMPLATE" + ```python hl_lines="66 83 89 96 103 108-113 119 130" + --8<-- "examples/middleware_factory/sam/combining_powertools_utilities_template.yaml" + ``` ## Tips diff --git a/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml b/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml new file mode 100644 index 00000000000..4ee87e379cd --- /dev/null +++ b/examples/middleware_factory/sam/combining_powertools_utilities_template.yaml @@ -0,0 +1,136 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Middleware-powertools-utilities example + +Globals: + Function: + Timeout: 5 + Runtime: python3.9 + Tracing: Active + Architectures: + - x86_64 + Environment: + Variables: + LOG_LEVEL: DEBUG + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 + POWERTOOLS_LOGGER_LOG_EVENT: true + POWERTOOLS_SERVICE_NAME: middleware + +Resources: + MiddlewareFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Properties: + CodeUri: middleware/ + Handler: app.lambda_handler + Description: Middleware function + Policies: + - AWSLambdaBasicExecutionRole # Managed Policy + - Version: '2012-10-17' # Policy Document + Statement: + - Effect: Allow + Action: + - dynamodb:PutItem + Resource: !GetAtt HistoryTable.Arn + - Effect: Allow + Action: # https://docs.aws.amazon.com/appconfig/latest/userguide/getting-started-with-appconfig-permissions.html + - ssm:GetDocument + - ssm:ListDocuments + - appconfig:GetLatestConfiguration + - appconfig:StartConfigurationSession + - appconfig:ListApplications + - appconfig:GetApplication + - appconfig:ListEnvironments + - appconfig:GetEnvironment + - appconfig:ListConfigurationProfiles + - appconfig:GetConfigurationProfile + - appconfig:ListDeploymentStrategies + - appconfig:GetDeploymentStrategy + - appconfig:GetConfiguration + - appconfig:ListDeployments + - appconfig:GetDeployment + Resource: "*" + Events: + GetComments: + Type: Api + Properties: + Path: /comments + Method: GET + GetCommentsById: + Type: Api + Properties: + Path: /comments/{comment_id} + Method: GET + + # DynamoDB table to store historical data + HistoryTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: "HistoryTable" + AttributeDefinitions: + - AttributeName: customer_id + AttributeType: S + - AttributeName: request_id + AttributeType: S + KeySchema: + - AttributeName: customer_id + KeyType: HASH + - AttributeName: request_id + KeyType: "RANGE" + BillingMode: PAY_PER_REQUEST + + # Feature flags using AppConfig + FeatureCommentApp: + Type: AWS::AppConfig::Application + Properties: + Description: "Comments Application for feature toggles" + Name: comments + + FeatureCommentDevEnv: + Type: AWS::AppConfig::Environment + Properties: + ApplicationId: !Ref FeatureCommentApp + Description: "Development Environment for the App Config Comments" + Name: dev + + FeatureCommentConfigProfile: + Type: AWS::AppConfig::ConfigurationProfile + Properties: + ApplicationId: !Ref FeatureCommentApp + Name: features + LocationUri: "hosted" + + HostedConfigVersion: + Type: AWS::AppConfig::HostedConfigurationVersion + Properties: + ApplicationId: !Ref FeatureCommentApp + ConfigurationProfileId: !Ref FeatureCommentConfigProfile + Description: 'A sample hosted configuration version' + Content: | + { + "save_history": { + "default": true + } + } + ContentType: 'application/json' + + # this is just an example + # change this values according your deployment strategy + BasicDeploymentStrategy: + Type: AWS::AppConfig::DeploymentStrategy + Properties: + Name: "Deployment" + Description: "Deployment strategy for comments app." + DeploymentDurationInMinutes: 1 + FinalBakeTimeInMinutes: 1 + GrowthFactor: 100 + GrowthType: LINEAR + ReplicateTo: NONE + + ConfigDeployment: + Type: AWS::AppConfig::Deployment + Properties: + ApplicationId: !Ref FeatureCommentApp + ConfigurationProfileId: !Ref FeatureCommentConfigProfile + ConfigurationVersion: !Ref HostedConfigVersion + DeploymentStrategyId: !Ref BasicDeploymentStrategy + EnvironmentId: !Ref FeatureCommentDevEnv diff --git a/examples/middleware_factory/src/advanced_middleware_tracer_function.py b/examples/middleware_factory/src/advanced_middleware_tracer_function.py new file mode 100644 index 00000000000..05aa65d33c4 --- /dev/null +++ b/examples/middleware_factory/src/advanced_middleware_tracer_function.py @@ -0,0 +1,44 @@ +import time +from typing import Callable + +import requests +from requests import Response + +from aws_lambda_powertools import Tracer +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +app = APIGatewayRestResolver() + + +@lambda_handler_decorator(trace_execution=True) +def middleware_with_advanced_tracing(handler, event, context) -> Callable: + + tracer.put_metadata(key="resource", value=event.get("resource")) + + start_time = time.time() + response = handler(event, context) + execution_time = time.time() - start_time + + tracer.put_annotation(key="TotalExecutionTime", value=str(execution_time)) + + # adding custom headers in response object after lambda executing + response["headers"]["execution_time"] = execution_time + response["headers"]["aws_request_id"] = context.aws_request_id + + return response + + +@app.get("/products") +def create_product() -> dict: + product: Response = requests.get("https://dummyjson.com/products/1") + product.raise_for_status() + + return {"product": product.json()} + + +@middleware_with_advanced_tracing +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/advanced_middleware_tracer_payload.json b/examples/middleware_factory/src/advanced_middleware_tracer_payload.json new file mode 100644 index 00000000000..d8a89bcfc67 --- /dev/null +++ b/examples/middleware_factory/src/advanced_middleware_tracer_payload.json @@ -0,0 +1,5 @@ +{ + "resource": "/products", + "path": "/products", + "httpMethod": "GET" + } diff --git a/examples/middleware_factory/src/combining_powertools_utilities_event.json b/examples/middleware_factory/src/combining_powertools_utilities_event.json new file mode 100644 index 00000000000..74257f56411 --- /dev/null +++ b/examples/middleware_factory/src/combining_powertools_utilities_event.json @@ -0,0 +1,79 @@ +{ + "body":"None", + "headers":{ + "Accept":"*/*", + "Accept-Encoding":"gzip, deflate, br", + "Connection":"keep-alive", + "Host":"127.0.0.1:3001", + "Postman-Token":"a9d49365-ebe1-4bb0-8627-d5e37cdce86d", + "User-Agent":"PostmanRuntime/7.29.0", + "X-Customer-Id":"1", + "X-Forwarded-Port":"3001", + "X-Forwarded-Proto":"http" + }, + "httpMethod":"GET", + "isBase64Encoded":false, + "multiValueHeaders":{ + "Accept":[ + "*/*" + ], + "Accept-Encoding":[ + "gzip, deflate, br" + ], + "Connection":[ + "keep-alive" + ], + "Host":[ + "127.0.0.1:3001" + ], + "Postman-Token":[ + "a9d49365-ebe1-4bb0-8627-d5e37cdce86d" + ], + "User-Agent":[ + "PostmanRuntime/7.29.0" + ], + "X-Customer-Id":[ + "1" + ], + "X-Forwarded-Port":[ + "3001" + ], + "X-Forwarded-Proto":[ + "http" + ] + }, + "multiValueQueryStringParameters":"None", + "path":"/comments", + "pathParameters":"None", + "queryStringParameters":"None", + "requestContext":{ + "accountId":"123456789012", + "apiId":"1234567890", + "domainName":"127.0.0.1:3001", + "extendedRequestId":"None", + "httpMethod":"GET", + "identity":{ + "accountId":"None", + "apiKey":"None", + "caller":"None", + "cognitoAuthenticationProvider":"None", + "cognitoAuthenticationType":"None", + "cognitoIdentityPoolId":"None", + "sourceIp":"127.0.0.1", + "user":"None", + "userAgent":"Custom User Agent String", + "userArn":"None" + }, + "path":"/comments", + "protocol":"HTTP/1.1", + "requestId":"56d1a102-6d9d-4f13-b4f7-26751c10a131", + "requestTime":"20/Aug/2022:18:18:58 +0000", + "requestTimeEpoch":1661019538, + "resourceId":"123456", + "resourcePath":"/comments", + "stage":"Prod" + }, + "resource":"/comments", + "stageVariables":"None", + "version":"1.0" + } diff --git a/examples/middleware_factory/src/combining_powertools_utilities_function.py b/examples/middleware_factory/src/combining_powertools_utilities_function.py new file mode 100644 index 00000000000..3b546ea240e --- /dev/null +++ b/examples/middleware_factory/src/combining_powertools_utilities_function.py @@ -0,0 +1,121 @@ +import json +from typing import Callable + +import boto3 +import combining_powertools_utilities_schema as schemas +import requests + +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.event_handler.exceptions import InternalServerError +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.shared.types import JSONType +from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags +from aws_lambda_powertools.utilities.jmespath_utils import extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext +from aws_lambda_powertools.utilities.validation import SchemaValidationError, validate + +app = APIGatewayRestResolver() +tracer = Tracer() +logger = Logger() + +table_historic = boto3.resource("dynamodb").Table("HistoricTable") + +app_config = AppConfigStore(environment="dev", application="comments", name="features") +feature_flags = FeatureFlags(store=app_config) + + +@lambda_handler_decorator(trace_execution=True) +def middleware_custom(handler: Callable, event: dict, context: LambdaContext): + + # validating the INPUT with the given schema + # X-Customer-Id header must be informed in all requests + try: + validate(event=event, schema=schemas.INPUT) + except SchemaValidationError as e: + return { + "statusCode": 400, + "body": json.dumps(str(e)), + } + + # extracting headers and requestContext from event + headers = extract_data_from_envelope(data=event, envelope="headers") + request_context = extract_data_from_envelope(data=event, envelope="requestContext") + + logger.debug(f"X-Customer-Id => {headers.get('X-Customer-Id')}") + tracer.put_annotation(key="CustomerId", value=headers.get("X-Customer-Id")) + + response = handler(event, context) + + # automatically adding security headers to all responses + # see: https://securityheaders.com/ + logger.info("Injecting security headers") + response["headers"]["Referrer-Policy"] = "no-referrer" + response["headers"]["Strict-Transport-Security"] = "max-age=15552000; includeSubDomains; preload" + response["headers"]["X-DNS-Prefetch-Control"] = "off" + response["headers"]["X-Content-Type-Options"] = "nosniff" + response["headers"]["X-Permitted-Cross-Domain-Policies"] = "none" + response["headers"]["X-Download-Options"] = "noopen" + + logger.info("Saving api call in history table") + save_api_execution_history(str(event.get("path")), headers, request_context) + + # return lambda execution + return response + + +@tracer.capture_method +def save_api_execution_history(path: str, headers: dict, request_context: dict) -> None: + + try: + # using the feature flags utility to check if the new feature "save api call to history" is enabled by default + # see: https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/feature_flags/#static-flags + save_history: JSONType = feature_flags.evaluate(name="save_history", default=False) + if save_history: + # saving history in dynamodb table + tracer.put_metadata(key="execution detail", value=request_context) + table_historic.put_item( + Item={ + "customer_id": headers.get("X-Customer-Id"), + "request_id": request_context.get("requestId"), + "path": path, + "request_time": request_context.get("requestTime"), + "source_ip": request_context.get("identity", {}).get("sourceIp"), + "http_method": request_context.get("httpMethod"), + } + ) + + return None + except Exception: + # you can add more logic here to handle exceptions or even save this to a DLQ + # but not to make this example too long, we just return None since the Lambda has been successfully executed + return None + + +@app.get("/comments") +@tracer.capture_method +def get_comments(): + try: + comments: requests.Response = requests.get("https://jsonplaceholder.typicode.com/comments") + comments.raise_for_status() + + return {"comments": comments.json()[:10]} + except Exception as exc: + raise InternalServerError(str(exc)) + + +@app.get("/comments/") +@tracer.capture_method +def get_comments_by_id(comment_id: str): + try: + comments: requests.Response = requests.get(f"https://jsonplaceholder.typicode.com/comments/{comment_id}") + comments.raise_for_status() + + return {"comments": comments.json()} + except Exception as exc: + raise InternalServerError(str(exc)) + + +@middleware_custom +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/combining_powertools_utilities_schema.py b/examples/middleware_factory/src/combining_powertools_utilities_schema.py new file mode 100644 index 00000000000..7a1978a71a3 --- /dev/null +++ b/examples/middleware_factory/src/combining_powertools_utilities_schema.py @@ -0,0 +1,25 @@ +INPUT = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://example.com/object1661012141.json", + "title": "Root", + "type": "object", + "required": ["headers"], + "properties": { + "headers": { + "$id": "#root/headers", + "title": "Headers", + "type": "object", + "required": ["X-Customer-Id"], + "properties": { + "X-Customer-Id": { + "$id": "#root/headers/X-Customer-Id", + "title": "X-customer-id", + "type": "string", + "default": "", + "examples": ["1"], + "pattern": "^.*$", + } + }, + } + }, +} diff --git a/examples/middleware_factory/src/getting_started_middleware_after_logic_function.py b/examples/middleware_factory/src/getting_started_middleware_after_logic_function.py new file mode 100644 index 00000000000..e77328ca8f7 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_after_logic_function.py @@ -0,0 +1,39 @@ +import time +from typing import Callable + +import requests +from requests import Response + +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = APIGatewayRestResolver() + + +@lambda_handler_decorator +def middleware_after(handler, event, context) -> Callable: + + start_time = time.time() + response = handler(event, context) + execution_time = time.time() - start_time + + # adding custom headers in response object after lambda executing + response["headers"]["execution_time"] = execution_time + response["headers"]["aws_request_id"] = context.aws_request_id + + return response + + +@app.post("/todos") +def create_todo() -> dict: + todo_data: dict = app.current_event.json_body # deserialize json str to dict + todo: Response = requests.post("https://jsonplaceholder.typicode.com/todos", data=todo_data) + todo.raise_for_status() + + return {"todo": todo.json()} + + +@middleware_after +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json b/examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json new file mode 100644 index 00000000000..e0f775d72df --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_after_logic_payload.json @@ -0,0 +1,6 @@ +{ + "resource": "/todos", + "path": "/todos", + "httpMethod": "POST", + "body": "{\"title\": \"foo\", \"userId\": 1, \"completed\": false}" +} diff --git a/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py b/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py new file mode 100644 index 00000000000..7d5ee035e7b --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_before_logic_function.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass, field +from typing import Callable +from uuid import uuid4 + +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.jmespath_utils import envelopes, extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext + + +@dataclass +class Payment: + user_id: str + order_id: str + amount: float + status_id: str + payment_id: str = field(default_factory=lambda: f"{uuid4()}") + + +class PaymentError(Exception): + ... + + +@lambda_handler_decorator +def middleware_before(handler, event, context) -> Callable: + # extract payload from a EventBridge event + detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + + # check if status_id exists in payload, otherwise add default state before processing payment + if "status_id" not in detail: + event["detail"]["status_id"] = "pending" + + response = handler(event, context) + + return response + + +@middleware_before +def lambda_handler(event, context: LambdaContext) -> dict: + try: + payment_payload: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + return { + "order": Payment(**payment_payload).__dict__, + "message": "payment created", + "success": True, + } + except Exception as e: + raise PaymentError("Unable to create payment") from e diff --git a/examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json b/examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json new file mode 100644 index 00000000000..21fa5d9b6c7 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_before_logic_payload.json @@ -0,0 +1,14 @@ +{ + "version": "0", + "id": "9c95e8e4-96a4-ef3f-b739-b6aa5b193afb", + "detail-type": "PaymentCreated", + "source": "app.payment", + "account": "0123456789012", + "time": "2022-08-08T20:41:53Z", + "region": "eu-east-1", + "detail": { + "amount": "150.00", + "order_id": "8f1f1710-1b30-48a5-a6bd-153fd23b866b", + "user_id": "f80e3c51-5b8c-49d5-af7d-c7804966235f" + } + } diff --git a/examples/middleware_factory/src/getting_started_middleware_tracer_function.py b/examples/middleware_factory/src/getting_started_middleware_tracer_function.py new file mode 100644 index 00000000000..0c461592254 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_tracer_function.py @@ -0,0 +1,38 @@ +import time +from typing import Callable + +import requests +from requests import Response + +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.typing import LambdaContext + +app = APIGatewayRestResolver() + + +@lambda_handler_decorator(trace_execution=True) +def middleware_with_tracing(handler, event, context) -> Callable: + + start_time = time.time() + response = handler(event, context) + execution_time = time.time() - start_time + + # adding custom headers in response object after lambda executing + response["headers"]["execution_time"] = execution_time + response["headers"]["aws_request_id"] = context.aws_request_id + + return response + + +@app.get("/products") +def create_product() -> dict: + product: Response = requests.get("https://dummyjson.com/products/1") + product.raise_for_status() + + return {"product": product.json()} + + +@middleware_with_tracing +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) diff --git a/examples/middleware_factory/src/getting_started_middleware_tracer_payload.json b/examples/middleware_factory/src/getting_started_middleware_tracer_payload.json new file mode 100644 index 00000000000..d8a89bcfc67 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_tracer_payload.json @@ -0,0 +1,5 @@ +{ + "resource": "/products", + "path": "/products", + "httpMethod": "GET" + } diff --git a/examples/middleware_factory/src/getting_started_middleware_with_params_function.py b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py new file mode 100644 index 00000000000..ce800e9162f --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_with_params_function.py @@ -0,0 +1,58 @@ +import base64 +from dataclasses import dataclass, field +from typing import Any, Callable, List +from uuid import uuid4 + +from aws_lambda_powertools.middleware_factory import lambda_handler_decorator +from aws_lambda_powertools.utilities.jmespath_utils import envelopes, extract_data_from_envelope +from aws_lambda_powertools.utilities.typing import LambdaContext + + +@dataclass +class Booking: + days: int + date_from: str + date_to: str + hotel_id: int + country: str + city: str + guest: dict + booking_id: str = field(default_factory=lambda: f"{uuid4()}") + + +class BookingError(Exception): + ... + + +@lambda_handler_decorator +def obfuscate_sensitive_data(handler, event, context, fields: List) -> Callable: + # extracting payload from a EventBridge event + detail: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + guest_data: Any = detail.get("guest") + + # Obfuscate fields (email, vat, passport) before calling Lambda handler + for guest_field in fields: + if guest_data.get(guest_field): + event["detail"]["guest"][guest_field] = obfuscate_data(str(guest_data.get(guest_field))) + + response = handler(event, context) + + return response + + +def obfuscate_data(value: str) -> bytes: + # base64 is not effective for obfuscation, this is an example + return base64.b64encode(value.encode("ascii")) + + +@obfuscate_sensitive_data(fields=["email", "passport", "vat"]) +def lambda_handler(event, context: LambdaContext) -> dict: + try: + booking_payload: dict = extract_data_from_envelope(data=event, envelope=envelopes.EVENTBRIDGE) + return { + "book": Booking(**booking_payload).__dict__, + "message": "booking created", + "success": True, + } + except Exception as e: + raise BookingError("Unable to create booking") from e diff --git a/examples/middleware_factory/src/getting_started_middleware_with_params_payload.json b/examples/middleware_factory/src/getting_started_middleware_with_params_payload.json new file mode 100644 index 00000000000..de6dbc626d3 --- /dev/null +++ b/examples/middleware_factory/src/getting_started_middleware_with_params_payload.json @@ -0,0 +1,23 @@ +{ + "version": "0", + "id": "9c95e8e4-96a4-ef3f-b739-b6aa5b193afb", + "detail-type": "BookingCreated", + "source": "app.booking", + "account": "0123456789012", + "time": "2022-08-08T20:41:53Z", + "region": "eu-east-1", + "detail": { + "days": 5, + "date_from": "2020-08-08", + "date_to": "2020-08-13", + "hotel_id": "1", + "country": "Portugal", + "city": "Lisbon", + "guest": { + "name": "Lambda", + "email": "lambda@powertool.tools", + "passport": "AA123456", + "vat": "123456789" + } + } +} From 200ac6751aeb77c52fc4087c8f0a69a14c6fb53f Mon Sep 17 00:00:00 2001 From: Release bot Date: Thu, 25 Aug 2022 08:10:31 +0000 Subject: [PATCH 57/59] update changelog with latest changes --- CHANGELOG.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ba99f3ff5..a4cabbb477b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ## Bug Fixes +* **ci:** calculate parallel jobs based on infrastructure needs ([#1475](https://github.com/awslabs/aws-lambda-powertools-python/issues/1475)) * **ci:** del flake8 direct dep over py3.6 conflicts and docs failure * **ci:** move from pip-tools to poetry on layers reusable workflow * **ci:** move from pip-tools to poetry on layers to fix conflicts @@ -21,9 +22,12 @@ ## Documentation * **apigateway:** removes duplicate admonition ([#1426](https://github.com/awslabs/aws-lambda-powertools-python/issues/1426)) +* **home:** fix discord syntax and add Discord badge +* **home:** add discord invitation link ([#1471](https://github.com/awslabs/aws-lambda-powertools-python/issues/1471)) * **jmespath_util:** snippets split, improved, and lint ([#1419](https://github.com/awslabs/aws-lambda-powertools-python/issues/1419)) * **layer:** upgrade to 1.27.0 * **layer:** upgrade to 1.27.0 +* **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/awslabs/aws-lambda-powertools-python/issues/1451)) * **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) * **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) @@ -34,18 +38,27 @@ ## Maintenance * **batch:** deprecate sqs_batch_processor ([#1463](https://github.com/awslabs/aws-lambda-powertools-python/issues/1463)) -* **ci:** remove conventional changelog commit to reduce noise +* **ci:** prevent concurrent git update in critical workflows ([#1478](https://github.com/awslabs/aws-lambda-powertools-python/issues/1478)) +* **ci:** disable e2e py version matrix due to concurrent locking +* **ci:** revert e2e py version matrix +* **ci:** temp disable e2e matrix * **ci:** update changelog with latest changes * **ci:** update changelog with latest changes * **ci:** reduce payload and only send prod notification * **ci:** remove area/utilities conflicting label +* **ci:** include py version in stack and cache lock +* **ci:** remove conventional changelog commit to reduce noise * **ci:** update changelog with latest changes * **deps:** bump release-drafter/release-drafter from 5.20.0 to 5.20.1 ([#1458](https://github.com/awslabs/aws-lambda-powertools-python/issues/1458)) * **deps:** bump pydantic from 1.9.1 to 1.9.2 ([#1448](https://github.com/awslabs/aws-lambda-powertools-python/issues/1448)) +* **deps-dev:** bump flake8-bugbear from 22.8.22 to 22.8.23 ([#1473](https://github.com/awslabs/aws-lambda-powertools-python/issues/1473)) * **deps-dev:** bump types-requests from 2.28.7 to 2.28.8 ([#1423](https://github.com/awslabs/aws-lambda-powertools-python/issues/1423)) +* **maintainer:** add Leandro as maintainer ([#1468](https://github.com/awslabs/aws-lambda-powertools-python/issues/1468)) +* **tests:** build and deploy Lambda Layer stack once ([#1466](https://github.com/awslabs/aws-lambda-powertools-python/issues/1466)) * **tests:** refactor E2E test mechanics to ease maintenance, writing tests and parallelization ([#1444](https://github.com/awslabs/aws-lambda-powertools-python/issues/1444)) -* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) +* **tests:** enable end-to-end test workflow ([#1470](https://github.com/awslabs/aws-lambda-powertools-python/issues/1470)) * **tests:** refactor E2E logger to ease maintenance, writing tests and parallelization ([#1460](https://github.com/awslabs/aws-lambda-powertools-python/issues/1460)) +* **tests:** refactor E2E tracer to ease maintenance, writing tests and parallelization ([#1457](https://github.com/awslabs/aws-lambda-powertools-python/issues/1457)) From f443c8f2d2753fa58499c35c75c39f3ad210135f Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Thu, 25 Aug 2022 09:12:47 +0100 Subject: [PATCH 58/59] docs(typing): snippets split, improved, and lint (#1465) Co-authored-by: Ruben Fonseca Co-authored-by: Heitor Lessa --- docs/media/utilities_typing.png | Bin 81849 -> 0 bytes docs/media/utilities_typing_1.png | Bin 0 -> 71864 bytes docs/media/utilities_typing_2.png | Bin 0 -> 106715 bytes docs/media/utilities_typing_3.png | Bin 0 -> 122071 bytes docs/utilities/typing.md | 39 ++++++++++++++---- .../src/getting_started_typing_function.py | 6 +++ .../src/working_with_context_function.py | 32 ++++++++++++++ 7 files changed, 69 insertions(+), 8 deletions(-) delete mode 100644 docs/media/utilities_typing.png create mode 100644 docs/media/utilities_typing_1.png create mode 100644 docs/media/utilities_typing_2.png create mode 100644 docs/media/utilities_typing_3.png create mode 100644 examples/typing/src/getting_started_typing_function.py create mode 100644 examples/typing/src/working_with_context_function.py diff --git a/docs/media/utilities_typing.png b/docs/media/utilities_typing.png deleted file mode 100644 index 0f293abb6ec0ed43a9b387dcc97be266ad907020..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81849 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%0AJzV|` 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 diff --git a/docs/media/utilities_typing_1.png b/docs/media/utilities_typing_1.png new file mode 100644 index 0000000000000000000000000000000000000000..8476b9f093e1dabc5d84825b3f0dff4ce4a2218b GIT binary patch literal 71864 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

B9-o53=uBpql7;eszHI9M$LhMc0V50IA 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

3a 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 LKdgY-Uo-y~9kW(Z literal 0 HcmV?d00001 diff --git a/docs/media/utilities_typing_2.png b/docs/media/utilities_typing_2.png new file mode 100644 index 0000000000000000000000000000000000000000..2a045df0a0707568f88ec5eccf23f78378d34fda GIT binary patch literal 106715 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 D@nsN0 literal 0 HcmV?d00001 diff --git a/docs/media/utilities_typing_3.png b/docs/media/utilities_typing_3.png new file mode 100644 index 0000000000000000000000000000000000000000..88888f506c362eef23471f2408dd053d6c76b1bd GIT binary patch literal 122071 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

i7eBb{$*rtt|KuUKFUm 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 Bb{qfz literal 0 HcmV?d00001 diff --git a/docs/utilities/typing.md b/docs/utilities/typing.md index a23d014afa6..e94f6b49201 100644 --- a/docs/utilities/typing.md +++ b/docs/utilities/typing.md @@ -7,17 +7,40 @@ description: Utility This typing utility provides static typing classes that can be used to ease the development by providing the IDE type hints. -![Utilities Typing](../media/utilities_typing.png) +## Key features + +* Add static typing classes +* Ease the development by leveraging your IDE's type hints +* Avoid common typing mistakes in Python + +![Utilities Typing](../media/utilities_typing_1.png) + +## Getting started + +???+ tip + All examples shared in this documentation are available within the [project repository](https://github.com/awslabs/aws-lambda-powertools-python/tree/develop/examples){target="_blank"}. + +We provide static typing for any context methods or properties implemented by [Lambda context object](https://docs.aws.amazon.com/lambda/latest/dg/python-context.html){target="_blank"}. ## LambdaContext The `LambdaContext` typing is typically used in the handler method for the Lambda function. -```python hl_lines="4" title="Annotating Lambda context type" -from typing import Any, Dict -from aws_lambda_powertools.utilities.typing import LambdaContext +=== "getting_started_validator_decorator_function.py" + + ```python hl_lines="1 4" + --8<-- "examples/typing/src/getting_started_typing_function.py" + ``` + +## Working with context methods and properties + +Using `LambdaContext` typing makes it possible to access information and hints of all properties and methods implemented by Lambda context object. + +=== "working_with_context_function.py" + + ```python hl_lines="6 16 25 26" + --8<-- "examples/typing/src/working_with_context_function.py" + ``` -def handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]: - # Insert business logic - return event -``` +![Utilities Typing All](../media/utilities_typing_2.png) +![Utilities Typing Specific](../media/utilities_typing_3.png) diff --git a/examples/typing/src/getting_started_typing_function.py b/examples/typing/src/getting_started_typing_function.py new file mode 100644 index 00000000000..493f0c18b2e --- /dev/null +++ b/examples/typing/src/getting_started_typing_function.py @@ -0,0 +1,6 @@ +from aws_lambda_powertools.utilities.typing import LambdaContext + + +def handler(event: dict, context: LambdaContext) -> dict: + # Insert business logic + return event diff --git a/examples/typing/src/working_with_context_function.py b/examples/typing/src/working_with_context_function.py new file mode 100644 index 00000000000..bfe610efa38 --- /dev/null +++ b/examples/typing/src/working_with_context_function.py @@ -0,0 +1,32 @@ +from time import sleep + +import requests + +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.typing import LambdaContext + +logger = Logger() + + +def lambda_handler(event, context: LambdaContext) -> dict: + + limit_execution: int = 1000 # milliseconds + + # scrape website and exit before lambda timeout + while context.get_remaining_time_in_millis() > limit_execution: + + comments: requests.Response = requests.get("https://jsonplaceholder.typicode.com/comments") + # add logic here and save the results of the request to an S3 bucket, for example. + + logger.info( + { + "operation": "scrape_website", + "request_id": context.aws_request_id, + "remaining_time": context.get_remaining_time_in_millis(), + "comments": comments.json()[:2], + } + ) + + sleep(1) + + return {"message": "Success"} From 7c737e85b15afb989a62676988b3f208ae74f4a0 Mon Sep 17 00:00:00 2001 From: Release bot Date: Thu, 25 Aug 2022 08:13:18 +0000 Subject: [PATCH 59/59] update changelog with latest changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4cabbb477b..20c156aec8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * **layer:** upgrade to 1.27.0 * **middleware-factory:** snippets split, improved, and lint ([#1451](https://github.com/awslabs/aws-lambda-powertools-python/issues/1451)) * **parser:** minor grammar fix ([#1427](https://github.com/awslabs/aws-lambda-powertools-python/issues/1427)) +* **typing:** snippets split, improved, and lint ([#1465](https://github.com/awslabs/aws-lambda-powertools-python/issues/1465)) * **validation:** snippets split, improved, and lint ([#1449](https://github.com/awslabs/aws-lambda-powertools-python/issues/1449)) ## Features