From 37697b109107c02849752af282ead4c96f9cc9ea Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi Date: Wed, 30 Jul 2025 11:50:33 +0100 Subject: [PATCH 1/7] Migrate the poetry for dependency management --- .github/workflows/test-on-push-and-pr.yml | 13 +- .gitignore | 3 + .pre-commit-config.yaml | 16 +- Makefile | 54 +- poetry.lock | 785 ++++++++++++++++++ pyproject.toml | 64 ++ requirements/base.txt | 2 - requirements/dev.txt | 12 - scripts/dev.py | 88 ++ setup.py | 120 +-- .../integration/docker/Dockerfile.echo.alpine | 14 +- .../docker/Dockerfile.echo.amazonlinux2 | 24 +- .../docker/Dockerfile.echo.amazonlinux2023 | 28 +- .../integration/docker/Dockerfile.echo.debian | 19 +- .../integration/docker/Dockerfile.echo.ubuntu | 9 +- tests/test_bootstrap.py | 95 +-- 16 files changed, 1135 insertions(+), 211 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements/base.txt delete mode 100644 requirements/dev.txt create mode 100644 scripts/dev.py diff --git a/.github/workflows/test-on-push-and-pr.yml b/.github/workflows/test-on-push-and-pr.yml index 5b80d23..d9cb340 100644 --- a/.github/workflows/test-on-push-and-pr.yml +++ b/.github/workflows/test-on-push-and-pr.yml @@ -12,6 +12,17 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + run: | + curl -sSL https://install.python-poetry.org | python3 - + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Run 'pr' target run: make pr @@ -45,4 +56,4 @@ jobs: steps: - uses: actions/checkout@v4 - name: Run ubuntu integration tests - run: DISTRO=ubuntu make test-integ \ No newline at end of file + run: DISTRO=ubuntu make test-integ diff --git a/.gitignore b/.gitignore index 9d46e4c..08747aa 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,6 @@ tmp*.py deps/artifacts/ deps/aws-lambda-cpp-*/ deps/curl-*/ + +# local caches +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb66b03..ab968cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,13 @@ repos: - - repo: https://github.com/python/black - rev: 19.3b0 + - repo: local hooks: - - id: black - language_version: python3.9 - exclude_types: ['markdown', 'ini', 'toml', 'rst'] + - id: ruff-check + name: ruff + entry: poetry run ruff check --fix + language: system + types: [python] + - id: ruff-format + name: ruff format + entry: poetry run ruff format + language: system + types: [python] diff --git a/Makefile b/Makefile index 521b61c..70f415b 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,23 @@ target: .PHONY: init init: - pip3 install -r requirements/base.txt -r requirements/dev.txt + python3 scripts/dev.py init .PHONY: test -test: check-format - pytest --cov awslambdaric --cov-report term-missing --cov-fail-under 90 tests +test: + python3 scripts/dev.py test + +.PHONY: lint +lint: + python3 scripts/dev.py lint + +.PHONY: clean +clean: + python3 scripts/dev.py clean + +.PHONY: build +build: clean + python3 scripts/dev.py build .PHONY: setup-codebuild-agent setup-codebuild-agent: @@ -25,48 +37,40 @@ test-integ: setup-codebuild-agent .PHONY: check-security check-security: - bandit -r awslambdaric + poetry run bandit -r awslambdaric .PHONY: format format: - black setup.py awslambdaric/ tests/ + poetry run ruff format awslambdaric/ tests/ .PHONY: check-format check-format: - black --check setup.py awslambdaric/ tests/ + poetry run ruff format --check awslambdaric/ tests/ -# Command to run everytime you make changes to verify everything works .PHONY: dev dev: init test -# Verifications to run before sending a pull request .PHONY: pr pr: init check-format check-security dev +.PHONY: codebuild codebuild: setup-codebuild-agent CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild -.PHONY: clean -clean: - rm -rf dist - rm -rf awslambdaric.egg-info - -.PHONY: build -build: clean - BUILD=true python3 setup.py sdist - define HELP_MESSAGE Usage: $ make [TARGETS] TARGETS check-security Run bandit to find security issues. - format Run black to automatically update your code to match our formatting. - build Builds the package. - clean Cleans the working directory by removing built artifacts. - dev Run all development tests after a change. - init Initialize and install the requirements and dev-requirements for this project. + format Run black to automatically update your code to match formatting. + build Build the package using scripts/dev.py. + clean Cleans the working directory using scripts/dev.py. + dev Run all development tests using scripts/dev.py. + init Install dependencies via scripts/dev.py. pr Perform all checks before submitting a Pull Request. - test Run the Unit tests. - -endef + test Run unit tests using scripts/dev.py. + lint Run all linters via scripts/dev.py. + test-smoke Run smoke tests inside Docker. + test-integ Run all integration tests. +endef \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..491f844 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,785 @@ +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. + +[[package]] +name = "bandit" +version = "1.8.6" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "bandit-1.8.6-py3-none-any.whl", hash = "sha256:3348e934d736fcdb68b6aa4030487097e23a501adf3e7827b63658df464dddd0"}, + {file = "bandit-1.8.6.tar.gz", hash = "sha256:dbfe9c25fc6961c2078593de55fd19f2559f9e45b99f1272341f5b95dea4e56b"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0) ; python_version < \"3.11\""] +yaml = ["PyYAML"] + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.10.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "coverage-7.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1c86eb388bbd609d15560e7cc0eb936c102b6f43f31cf3e58b4fd9afe28e1372"}, + {file = "coverage-7.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b4ba0f488c1bdb6bd9ba81da50715a372119785458831c73428a8566253b86b"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083442ecf97d434f0cb3b3e3676584443182653da08b42e965326ba12d6b5f2a"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c1a40c486041006b135759f59189385da7c66d239bad897c994e18fd1d0c128f"}, + {file = "coverage-7.10.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3beb76e20b28046989300c4ea81bf690df84ee98ade4dc0bbbf774a28eb98440"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc265a7945e8d08da28999ad02b544963f813a00f3ed0a7a0ce4165fd77629f8"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:47c91f32ba4ac46f1e224a7ebf3f98b4b24335bad16137737fe71a5961a0665c"}, + {file = "coverage-7.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1a108dd78ed185020f66f131c60078f3fae3f61646c28c8bb4edd3fa121fc7fc"}, + {file = "coverage-7.10.1-cp310-cp310-win32.whl", hash = "sha256:7092cc82382e634075cc0255b0b69cb7cada7c1f249070ace6a95cb0f13548ef"}, + {file = "coverage-7.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:ac0c5bba938879c2fc0bc6c1b47311b5ad1212a9dcb8b40fe2c8110239b7faed"}, + {file = "coverage-7.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45e2f9d5b0b5c1977cb4feb5f594be60eb121106f8900348e29331f553a726f"}, + {file = "coverage-7.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a7a4d74cb0f5e3334f9aa26af7016ddb94fb4bfa11b4a573d8e98ecba8c34f1"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d4b0aab55ad60ead26159ff12b538c85fbab731a5e3411c642b46c3525863437"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dcc93488c9ebd229be6ee1f0d9aad90da97b33ad7e2912f5495804d78a3cd6b7"}, + {file = "coverage-7.10.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa309df995d020f3438407081b51ff527171cca6772b33cf8f85344b8b4b8770"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cfb8b9d8855c8608f9747602a48ab525b1d320ecf0113994f6df23160af68262"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:320d86da829b012982b414c7cdda65f5d358d63f764e0e4e54b33097646f39a3"}, + {file = "coverage-7.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc60ddd483c556590da1d9482a4518292eec36dd0e1e8496966759a1f282bcd0"}, + {file = "coverage-7.10.1-cp311-cp311-win32.whl", hash = "sha256:4fcfe294f95b44e4754da5b58be750396f2b1caca8f9a0e78588e3ef85f8b8be"}, + {file = "coverage-7.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:efa23166da3fe2915f8ab452dde40319ac84dc357f635737174a08dbd912980c"}, + {file = "coverage-7.10.1-cp311-cp311-win_arm64.whl", hash = "sha256:d12b15a8c3759e2bb580ffa423ae54be4f184cf23beffcbd641f4fe6e1584293"}, + {file = "coverage-7.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6b7dc7f0a75a7eaa4584e5843c873c561b12602439d2351ee28c7478186c4da4"}, + {file = "coverage-7.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:607f82389f0ecafc565813aa201a5cade04f897603750028dd660fb01797265e"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f7da31a1ba31f1c1d4d5044b7c5813878adae1f3af8f4052d679cc493c7328f4"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51fe93f3fe4f5d8483d51072fddc65e717a175490804e1942c975a68e04bf97a"}, + {file = "coverage-7.10.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e59d00830da411a1feef6ac828b90bbf74c9b6a8e87b8ca37964925bba76dbe"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:924563481c27941229cb4e16eefacc35da28563e80791b3ddc5597b062a5c386"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ca79146ee421b259f8131f153102220b84d1a5e6fb9c8aed13b3badfd1796de6"}, + {file = "coverage-7.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b225a06d227f23f386fdc0eab471506d9e644be699424814acc7d114595495f"}, + {file = "coverage-7.10.1-cp312-cp312-win32.whl", hash = "sha256:5ba9a8770effec5baaaab1567be916c87d8eea0c9ad11253722d86874d885eca"}, + {file = "coverage-7.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:9eb245a8d8dd0ad73b4062135a251ec55086fbc2c42e0eb9725a9b553fba18a3"}, + {file = "coverage-7.10.1-cp312-cp312-win_arm64.whl", hash = "sha256:7718060dd4434cc719803a5e526838a5d66e4efa5dc46d2b25c21965a9c6fcc4"}, + {file = "coverage-7.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebb08d0867c5a25dffa4823377292a0ffd7aaafb218b5d4e2e106378b1061e39"}, + {file = "coverage-7.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f32a95a83c2e17422f67af922a89422cd24c6fa94041f083dd0bb4f6057d0bc7"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4c746d11c8aba4b9f58ca8bfc6fbfd0da4efe7960ae5540d1a1b13655ee8892"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7f39edd52c23e5c7ed94e0e4bf088928029edf86ef10b95413e5ea670c5e92d7"}, + {file = "coverage-7.10.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab6e19b684981d0cd968906e293d5628e89faacb27977c92f3600b201926b994"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5121d8cf0eacb16133501455d216bb5f99899ae2f52d394fe45d59229e6611d0"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df1c742ca6f46a6f6cbcaef9ac694dc2cb1260d30a6a2f5c68c5f5bcfee1cfd7"}, + {file = "coverage-7.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40f9a38676f9c073bf4b9194707aa1eb97dca0e22cc3766d83879d72500132c7"}, + {file = "coverage-7.10.1-cp313-cp313-win32.whl", hash = "sha256:2348631f049e884839553b9974f0821d39241c6ffb01a418efce434f7eba0fe7"}, + {file = "coverage-7.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:4072b31361b0d6d23f750c524f694e1a417c1220a30d3ef02741eed28520c48e"}, + {file = "coverage-7.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:3e31dfb8271937cab9425f19259b1b1d1f556790e98eb266009e7a61d337b6d4"}, + {file = "coverage-7.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1c4f679c6b573a5257af6012f167a45be4c749c9925fd44d5178fd641ad8bf72"}, + {file = "coverage-7.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:871ebe8143da284bd77b84a9136200bd638be253618765d21a1fce71006d94af"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:998c4751dabf7d29b30594af416e4bf5091f11f92a8d88eb1512c7ba136d1ed7"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:780f750a25e7749d0af6b3631759c2c14f45de209f3faaa2398312d1c7a22759"}, + {file = "coverage-7.10.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:590bdba9445df4763bdbebc928d8182f094c1f3947a8dc0fc82ef014dbdd8324"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b2df80cb6a2af86d300e70acb82e9b79dab2c1e6971e44b78dbfc1a1e736b53"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d6a558c2725bfb6337bf57c1cd366c13798bfd3bfc9e3dd1f4a6f6fc95a4605f"}, + {file = "coverage-7.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e6150d167f32f2a54690e572e0a4c90296fb000a18e9b26ab81a6489e24e78dd"}, + {file = "coverage-7.10.1-cp313-cp313t-win32.whl", hash = "sha256:d946a0c067aa88be4a593aad1236493313bafaa27e2a2080bfe88db827972f3c"}, + {file = "coverage-7.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e37c72eaccdd5ed1130c67a92ad38f5b2af66eeff7b0abe29534225db2ef7b18"}, + {file = "coverage-7.10.1-cp313-cp313t-win_arm64.whl", hash = "sha256:89ec0ffc215c590c732918c95cd02b55c7d0f569d76b90bb1a5e78aa340618e4"}, + {file = "coverage-7.10.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:166d89c57e877e93d8827dac32cedae6b0277ca684c6511497311249f35a280c"}, + {file = "coverage-7.10.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bed4a2341b33cd1a7d9ffc47df4a78ee61d3416d43b4adc9e18b7d266650b83e"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ddca1e4f5f4c67980533df01430184c19b5359900e080248bbf4ed6789584d8b"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:37b69226001d8b7de7126cad7366b0778d36777e4d788c66991455ba817c5b41"}, + {file = "coverage-7.10.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2f22102197bcb1722691296f9e589f02b616f874e54a209284dd7b9294b0b7f"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1e0c768b0f9ac5839dac5cf88992a4bb459e488ee8a1f8489af4cb33b1af00f1"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:991196702d5e0b120a8fef2664e1b9c333a81d36d5f6bcf6b225c0cf8b0451a2"}, + {file = "coverage-7.10.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae8e59e5f4fd85d6ad34c2bb9d74037b5b11be072b8b7e9986beb11f957573d4"}, + {file = "coverage-7.10.1-cp314-cp314-win32.whl", hash = "sha256:042125c89cf74a074984002e165d61fe0e31c7bd40ebb4bbebf07939b5924613"}, + {file = "coverage-7.10.1-cp314-cp314-win_amd64.whl", hash = "sha256:a22c3bfe09f7a530e2c94c87ff7af867259c91bef87ed2089cd69b783af7b84e"}, + {file = "coverage-7.10.1-cp314-cp314-win_arm64.whl", hash = "sha256:ee6be07af68d9c4fca4027c70cea0c31a0f1bc9cb464ff3c84a1f916bf82e652"}, + {file = "coverage-7.10.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d24fb3c0c8ff0d517c5ca5de7cf3994a4cd559cde0315201511dbfa7ab528894"}, + {file = "coverage-7.10.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1217a54cfd79be20512a67ca81c7da3f2163f51bbfd188aab91054df012154f5"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:51f30da7a52c009667e02f125737229d7d8044ad84b79db454308033a7808ab2"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ed3718c757c82d920f1c94089066225ca2ad7f00bb904cb72b1c39ebdd906ccb"}, + {file = "coverage-7.10.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc452481e124a819ced0c25412ea2e144269ef2f2534b862d9f6a9dae4bda17b"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9d6f494c307e5cb9b1e052ec1a471060f1dea092c8116e642e7a23e79d9388ea"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fc0e46d86905ddd16b85991f1f4919028092b4e511689bbdaff0876bd8aab3dd"}, + {file = "coverage-7.10.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80b9ccd82e30038b61fc9a692a8dc4801504689651b281ed9109f10cc9fe8b4d"}, + {file = "coverage-7.10.1-cp314-cp314t-win32.whl", hash = "sha256:e58991a2b213417285ec866d3cd32db17a6a88061a985dbb7e8e8f13af429c47"}, + {file = "coverage-7.10.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e88dd71e4ecbc49d9d57d064117462c43f40a21a1383507811cf834a4a620651"}, + {file = "coverage-7.10.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1aadfb06a30c62c2eb82322171fe1f7c288c80ca4156d46af0ca039052814bab"}, + {file = "coverage-7.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:57b6e8789cbefdef0667e4a94f8ffa40f9402cee5fc3b8e4274c894737890145"}, + {file = "coverage-7.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:85b22a9cce00cb03156334da67eb86e29f22b5e93876d0dd6a98646bb8a74e53"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:97b6983a2f9c76d345ca395e843a049390b39652984e4a3b45b2442fa733992d"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ddf2a63b91399a1c2f88f40bc1705d5a7777e31c7e9eb27c602280f477b582ba"}, + {file = "coverage-7.10.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47ab6dbbc31a14c5486420c2c1077fcae692097f673cf5be9ddbec8cdaa4cdbc"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21eb7d8b45d3700e7c2936a736f732794c47615a20f739f4133d5230a6512a88"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:283005bb4d98ae33e45f2861cd2cde6a21878661c9ad49697f6951b358a0379b"}, + {file = "coverage-7.10.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fefe31d61d02a8b2c419700b1fade9784a43d726de26495f243b663cd9fe1513"}, + {file = "coverage-7.10.1-cp39-cp39-win32.whl", hash = "sha256:e8ab8e4c7ec7f8a55ac05b5b715a051d74eac62511c6d96d5bb79aaafa3b04cf"}, + {file = "coverage-7.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:c36baa0ecde742784aa76c2b816466d3ea888d5297fda0edbac1bf48fa94688a"}, + {file = "coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7"}, + {file = "coverage-7.10.1.tar.gz", hash = "sha256:ae2b4856f29ddfe827106794f3589949a57da6f0d38ab01e24ec35107979ba57"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.18.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] + +[[package]] +name = "identify" +version = "2.6.12" +description = "File identification library for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, + {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mock" +version = "5.2.0" +description = "Rolling backport of unittest.mock for all Pythons" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f"}, + {file = "mock-5.2.0.tar.gz", hash = "sha256:4e460e818629b4b173f32d08bf30d3af8123afbb8e04bb5707a1fd4799e503f0"}, +] + +[package.extras] +build = ["blurb", "twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "parameterized" +version = "0.9.0" +description = "Parameterized testing with any Python test framework" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b"}, + {file = "parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1"}, +] + +[package.extras] +dev = ["jinja2"] + +[[package]] +name = "pbr" +version = "6.1.1" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +groups = ["dev"] +files = [ + {file = "pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76"}, + {file = "pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "platformdirs" +version = "4.3.8" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.8.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.4.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "rich" +version = "14.1.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["dev"] +files = [ + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruff" +version = "0.1.15" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "simplejson" +version = "3.20.1" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.5" +groups = ["main"] +files = [ + {file = "simplejson-3.20.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:f5272b5866b259fe6c33c4a8c5073bf8b359c3c97b70c298a2f09a69b52c7c41"}, + {file = "simplejson-3.20.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5c0de368f3052a59a1acf21f8b2dd28686a9e4eba2da7efae7ed9554cb31e7bc"}, + {file = "simplejson-3.20.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:0821871404a537fd0e22eba240c74c0467c28af6cc435903eca394cfc74a0497"}, + {file = "simplejson-3.20.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c939a1e576bded47d7d03aa2afc2ae90b928b2cf1d9dc2070ceec51fd463f430"}, + {file = "simplejson-3.20.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3c4f0a61cdc05550782ca4a2cdb311ea196c2e6be6b24a09bf71360ca8c3ca9b"}, + {file = "simplejson-3.20.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6c21f5c026ca633cfffcb6bc1fac2e99f65cb2b24657d3bef21aed9916cc3bbf"}, + {file = "simplejson-3.20.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:8d23b7f8d6b72319d6d55a0261089ff621ce87e54731c2d3de6a9bf7be5c028c"}, + {file = "simplejson-3.20.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:cda5c32a98f392909088111ecec23f2b0d39346ceae1a0fea23ab2d1f84ec21d"}, + {file = "simplejson-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e580aa65d5f6c3bf41b9b4afe74be5d5ddba9576701c107c772d936ea2b5043a"}, + {file = "simplejson-3.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a586ce4f78cec11f22fe55c5bee0f067e803aab9bad3441afe2181693b5ebb5"}, + {file = "simplejson-3.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74a1608f9e6e8c27a4008d70a54270868306d80ed48c9df7872f9f4b8ac87808"}, + {file = "simplejson-3.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03db8cb64154189a92a7786209f24e391644f3a3fa335658be2df2af1960b8d8"}, + {file = "simplejson-3.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eea7e2b7d858f6fdfbf0fe3cb846d6bd8a45446865bc09960e51f3d473c2271b"}, + {file = "simplejson-3.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e66712b17d8425bb7ff8968d4c7c7fd5a2dd7bd63728b28356223c000dd2f91f"}, + {file = "simplejson-3.20.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cc4f6486f9f515b62f5831ff1888886619b84fc837de68f26d919ba7bbdcbc"}, + {file = "simplejson-3.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3c2df555ee4016148fa192e2b9cd9e60bc1d40769366134882685e90aee2a1e"}, + {file = "simplejson-3.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:78520f04b7548a5e476b5396c0847e066f1e0a4c0c5e920da1ad65e95f410b11"}, + {file = "simplejson-3.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f4bd49ecde87b0fe9f55cc971449a32832bca9910821f7072bbfae1155eaa007"}, + {file = "simplejson-3.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7eaae2b88eb5da53caaffdfa50e2e12022553949b88c0df4f9a9663609373f72"}, + {file = "simplejson-3.20.1-cp310-cp310-win32.whl", hash = "sha256:e836fb88902799eac8debc2b642300748f4860a197fa3d9ea502112b6bb8e142"}, + {file = "simplejson-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a19b552b212fc3b5b96fc5ce92333d4a9ac0a800803e1f17ebb16dac4be5"}, + {file = "simplejson-3.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:325b8c107253d3217e89d7b50c71015b5b31e2433e6c5bf38967b2f80630a8ca"}, + {file = "simplejson-3.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88a7baa8211089b9e58d78fbc1b0b322103f3f3d459ff16f03a36cece0d0fcf0"}, + {file = "simplejson-3.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:299b1007b8101d50d95bc0db1bf5c38dc372e85b504cf77f596462083ee77e3f"}, + {file = "simplejson-3.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ec618ed65caab48e81e3ed29586236a8e57daef792f1f3bb59504a7e98cd10"}, + {file = "simplejson-3.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2cdead1d3197f0ff43373cf4730213420523ba48697743e135e26f3d179f38"}, + {file = "simplejson-3.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3466d2839fdc83e1af42e07b90bc8ff361c4e8796cd66722a40ba14e458faddd"}, + {file = "simplejson-3.20.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d492ed8e92f3a9f9be829205f44b1d0a89af6582f0cf43e0d129fa477b93fe0c"}, + {file = "simplejson-3.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f924b485537b640dc69434565463fd6fc0c68c65a8c6e01a823dd26c9983cf79"}, + {file = "simplejson-3.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e8eacf6a3491bf76ea91a8d46726368a6be0eb94993f60b8583550baae9439e"}, + {file = "simplejson-3.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d04bf90b4cea7c22d8b19091633908f14a096caa301b24c2f3d85b5068fb8"}, + {file = "simplejson-3.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69dd28d4ce38390ea4aaf212902712c0fd1093dc4c1ff67e09687c3c3e15a749"}, + {file = "simplejson-3.20.1-cp311-cp311-win32.whl", hash = "sha256:dfe7a9da5fd2a3499436cd350f31539e0a6ded5da6b5b3d422df016444d65e43"}, + {file = "simplejson-3.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:896a6c04d7861d507d800da7642479c3547060bf97419d9ef73d98ced8258766"}, + {file = "simplejson-3.20.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f31c4a3a7ab18467ee73a27f3e59158255d1520f3aad74315edde7a940f1be23"}, + {file = "simplejson-3.20.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884e6183d16b725e113b83a6fc0230152ab6627d4d36cb05c89c2c5bccfa7bc6"}, + {file = "simplejson-3.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03d7a426e416fe0d3337115f04164cd9427eb4256e843a6b8751cacf70abc832"}, + {file = "simplejson-3.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:000602141d0bddfcff60ea6a6e97d5e10c9db6b17fd2d6c66199fa481b6214bb"}, + {file = "simplejson-3.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af8377a8af78226e82e3a4349efdde59ffa421ae88be67e18cef915e4023a595"}, + {file = "simplejson-3.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c7de4c88ab2fbcb8781a3b982ef883696736134e20b1210bca43fb42ff1acf"}, + {file = "simplejson-3.20.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455a882ff3f97d810709f7b620007d4e0aca8da71d06fc5c18ba11daf1c4df49"}, + {file = "simplejson-3.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc0f523ce923e7f38eb67804bc80e0a028c76d7868500aa3f59225574b5d0453"}, + {file = "simplejson-3.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76461ec929282dde4a08061071a47281ad939d0202dc4e63cdd135844e162fbc"}, + {file = "simplejson-3.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19c2da8c043607bde4d4ef3a6b633e668a7d2e3d56f40a476a74c5ea71949f"}, + {file = "simplejson-3.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2578bedaedf6294415197b267d4ef678fea336dd78ee2a6d2f4b028e9d07be3"}, + {file = "simplejson-3.20.1-cp312-cp312-win32.whl", hash = "sha256:339f407373325a36b7fd744b688ba5bae0666b5d340ec6d98aebc3014bf3d8ea"}, + {file = "simplejson-3.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:627d4486a1ea7edf1f66bb044ace1ce6b4c1698acd1b05353c97ba4864ea2e17"}, + {file = "simplejson-3.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71e849e7ceb2178344998cbe5ade101f1b329460243c79c27fbfc51c0447a7c3"}, + {file = "simplejson-3.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b63fdbab29dc3868d6f009a59797cefaba315fd43cd32ddd998ee1da28e50e29"}, + {file = "simplejson-3.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1190f9a3ce644fd50ec277ac4a98c0517f532cfebdcc4bd975c0979a9f05e1fb"}, + {file = "simplejson-3.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1336ba7bcb722ad487cd265701ff0583c0bb6de638364ca947bb84ecc0015d1"}, + {file = "simplejson-3.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e975aac6a5acd8b510eba58d5591e10a03e3d16c1cf8a8624ca177491f7230f0"}, + {file = "simplejson-3.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a6dd11ee282937ad749da6f3b8d87952ad585b26e5edfa10da3ae2536c73078"}, + {file = "simplejson-3.20.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab980fcc446ab87ea0879edad41a5c28f2d86020014eb035cf5161e8de4474c6"}, + {file = "simplejson-3.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f5aee2a4cb6b146bd17333ac623610f069f34e8f31d2f4f0c1a2186e50c594f0"}, + {file = "simplejson-3.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:652d8eecbb9a3b6461b21ec7cf11fd0acbab144e45e600c817ecf18e4580b99e"}, + {file = "simplejson-3.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c09948f1a486a89251ee3a67c9f8c969b379f6ffff1a6064b41fea3bce0a112"}, + {file = "simplejson-3.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd7b215ad4fc6f058b5dd4c26ee5c59f72e031dfda3ac183d7968a99e4ca3a"}, + {file = "simplejson-3.20.1-cp313-cp313-win32.whl", hash = "sha256:ae81e482476eaa088ef9d0120ae5345de924f23962c0c1e20abbdff597631f87"}, + {file = "simplejson-3.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b9fd15853b90aec3b1739f4471efbf1ac05066a2c7041bf8db821bb73cd2ddc"}, + {file = "simplejson-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c7edf279c1376f28bf41e916c015a2a08896597869d57d621f55b6a30c7e1e6d"}, + {file = "simplejson-3.20.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9202b9de38f12e99a40addd1a8d508a13c77f46d87ab1f9095f154667f4fe81"}, + {file = "simplejson-3.20.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:391345b4157cc4e120027e013bd35c45e2c191e2bf48b8913af488cdc3b9243c"}, + {file = "simplejson-3.20.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c6fdcc9debb711ddd2ad6d69f9386a3d9e8e253234bbb30513e0a7caa9510c51"}, + {file = "simplejson-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9daf8cdc7ee8a9e9f7a3b313ba0a003391857e90d0e82fbcd4d614aa05cb7c3b"}, + {file = "simplejson-3.20.1-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:c02f4868a3a46ffe284a51a88d134dc96feff6079a7115164885331a1ba8ed9f"}, + {file = "simplejson-3.20.1-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:3d7310172d5340febd258cb147f46aae30ad57c445f4d7e1ae8461c10aaf43b0"}, + {file = "simplejson-3.20.1-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:4762e05577955312a4c6802f58dd02e040cc79ae59cda510aa1564d84449c102"}, + {file = "simplejson-3.20.1-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:8bb98fdf318c05aefd08a92583bd6ee148e93c6756fb1befb7b2d5f27824be78"}, + {file = "simplejson-3.20.1-cp36-cp36m-win32.whl", hash = "sha256:9a74e70818818981294b8e6956ce3496c5e1bd4726ac864fae473197671f7b85"}, + {file = "simplejson-3.20.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e041add470e8f8535cc05509485eb7205729a84441f03b25cde80ad48823792e"}, + {file = "simplejson-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e9d73f46119240e4f4f07868241749d67d09873f40cb968d639aa9ccc488b86"}, + {file = "simplejson-3.20.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae6e637dc24f8fee332ed23dd070e81394138e42cd4fd9d0923e5045ba122e27"}, + {file = "simplejson-3.20.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efd3bc6c6b17e3d4620eb6be5196f0d1c08b6ce7c3101fa8e292b79e0908944b"}, + {file = "simplejson-3.20.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87fc623d457173a0213bc9ca4e346b83c9d443f63ed5cca847fb0cacea3cfc95"}, + {file = "simplejson-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec6a1e0a7aff76f0e008bebfa950188b9c50b58c1885d898145f48fc8e189a56"}, + {file = "simplejson-3.20.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:9c079606f461a6e950099167e21e13985147c8a24be8eea66c9ad68f73fad744"}, + {file = "simplejson-3.20.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:9faceb68fba27ef17eda306e4cd97a7b4b14fdadca5fbb15790ba8b26ebeec0c"}, + {file = "simplejson-3.20.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:7ceed598e4bacbf5133fe7a418f7991bb2df0683f3ac11fbf9e36a2bc7aa4b85"}, + {file = "simplejson-3.20.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ede69c765e9901861ad7c6139023b7b7d5807c48a2539d817b4ab40018002d5f"}, + {file = "simplejson-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:d8853c269a4c5146ddca4aa7c70e631795e9d11239d5fedb1c6bbc91ffdebcac"}, + {file = "simplejson-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ed6a17fd397f0e2b3ad668fc9e19253ed2e3875ad9086bd7f795c29a3223f4a1"}, + {file = "simplejson-3.20.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7551682b60bba3a9e2780742e101cf0a64250e76de7d09b1c4b0c8a7c7cc6834"}, + {file = "simplejson-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd9577ec1c8c3a43040e3787711e4c257c70035b7551a21854b5dec88dad09e1"}, + {file = "simplejson-3.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8e197e4cf6d42c2c57e7c52cd7c1e7b3e37c5911df1314fb393320131e2101"}, + {file = "simplejson-3.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bd09c8c75666e7f62a33d2f1fb57f81da1fcbb19a9fe7d7910b5756e1dd6048"}, + {file = "simplejson-3.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bd6bfe5678d73fbd5328eea6a35216503796428fc47f1237432522febaf3a0c"}, + {file = "simplejson-3.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b75d448fd0ceb2e7c90e72bb82c41f8462550d48529980bc0bab1d2495bfbb"}, + {file = "simplejson-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7e15b716d09f318c8cda3e20f82fae81684ce3d3acd1d7770fa3007df1769de"}, + {file = "simplejson-3.20.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3e7963197d958fcf9e98b212b80977d56c022384621ff463d98afc3b6b1ce7e8"}, + {file = "simplejson-3.20.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:2e671dd62051129185d3a9a92c60101f56cbc174854a1a3dfb69114ebd9e1699"}, + {file = "simplejson-3.20.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e25b2a0c396f3b84fb89573d07b0e1846ed563eb364f2ea8230ca92b8a8cb786"}, + {file = "simplejson-3.20.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:489c3a43116082bad56795215786313832ba3991cca1f55838e52a553f451ab6"}, + {file = "simplejson-3.20.1-cp38-cp38-win32.whl", hash = "sha256:4a92e948bad8df7fa900ba2ba0667a98303f3db206cbaac574935c332838208e"}, + {file = "simplejson-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:49d059b8363327eee3c94799dd96782314b2dbd7bcc293b4ad48db69d6f4d362"}, + {file = "simplejson-3.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a8011f1dd1d676befcd4d675ebdbfdbbefd3bf350052b956ba8c699fca7d8cef"}, + {file = "simplejson-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e91703a4c5fec53e36875ae426ad785f4120bd1d93b65bed4752eeccd1789e0c"}, + {file = "simplejson-3.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e39eaa57c7757daa25bcd21f976c46be443b73dd6c3da47fe5ce7b7048ccefe2"}, + {file = "simplejson-3.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceab2ce2acdc7fbaa433a93006758db6ba9a659e80c4faa13b80b9d2318e9b17"}, + {file = "simplejson-3.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d4f320c33277a5b715db5bf5b10dae10c19076bd6d66c2843e04bd12d1f1ea5"}, + {file = "simplejson-3.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b6436c48e64378fa844d8c9e58a5ed0352bbcfd4028369a9b46679b7ab79d2d"}, + {file = "simplejson-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e18345c8dda5d699be8166b61f9d80aaee4545b709f1363f60813dc032dac53"}, + {file = "simplejson-3.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:90b573693d1526bed576f6817e2a492eaaef68f088b57d7a9e83d122bbb49e51"}, + {file = "simplejson-3.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:272cc767826e924a6bd369ea3dbf18e166ded29059c7a4d64d21a9a22424b5b5"}, + {file = "simplejson-3.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:51b41f284d603c4380732d7d619f8b34bd04bc4aa0ed0ed5f4ffd0539b14da44"}, + {file = "simplejson-3.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6e6697a3067d281f01de0fe96fc7cba4ea870d96d7deb7bfcf85186d74456503"}, + {file = "simplejson-3.20.1-cp39-cp39-win32.whl", hash = "sha256:6dd3a1d5aca87bf947f3339b0f8e8e329f1badf548bdbff37fac63c17936da8e"}, + {file = "simplejson-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:463f1fca8fbf23d088e5850fdd0dd4d5faea8900a9f9680270bd98fd649814ca"}, + {file = "simplejson-3.20.1-py3-none-any.whl", hash = "sha256:8a6c1bbac39fa4a79f83cbf1df6ccd8ff7069582a9fd8db1e52cea073bc2c697"}, + {file = "simplejson-3.20.1.tar.gz", hash = "sha256:e64139b4ec4f1f24c142ff7dcafe55a22b811a74d86d66560c8815687143037d"}, +] + +[[package]] +name = "snapshot-restore-py" +version = "1.0.0" +description = "Runtime Hooks for AWS Lambda SnapStart - Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "snapshot-restore-py-1.0.0.tar.gz", hash = "sha256:4d27f82fb6f09968f422501e9c3c2dea48a46cd19dc798eb7d6cbc57523c8004"}, + {file = "snapshot_restore_py-1.0.0-py3-none-any.whl", hash = "sha256:38f99e696793790f54658e71c68c7a8a40cea877c81232b5052383b1301aceba"}, +] + +[[package]] +name = "stevedore" +version = "5.4.1" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe"}, + {file = "stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b"}, +] + +[package.dependencies] +pbr = ">=2.0.0" + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "virtualenv" +version = "20.32.0" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56"}, + {file = "virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.9" +content-hash = "36256ebce25e8e66039694f47ffe6303b754c88e69a9241f614a28f5ae0735e7" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0421ecb --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,64 @@ +[tool.poetry] +name = "awslambdaric" +version = "3.0.2" +description = "AWS Lambda Runtime Interface Client for Python" +authors = ["Amazon Web Services"] +license = "Apache-2.0" +readme = "README.md" +packages = [{ include = "awslambdaric" }] + +[tool.poetry.dependencies] +python = ">=3.9" +simplejson = ">=3.20.1" +snapshot-restore-py = ">=1.0.0" + +[tool.poetry.group.dev.dependencies] +coverage = ">=4.4.0" +pytest = ">=3.0.7" +mock = ">=2.0.0" +parameterized = ">=0.9.0" +ruff = "^0.1.0" +bandit = "^1.7.5" +pre-commit = "^3.0.0" + +# Development scripts +[tool.poetry.scripts] +init = "scripts.dev:init" +test = "scripts.dev:test" +lint = "scripts.dev:lint" +format = "scripts.dev:format_code" +clean = "scripts.dev:clean" +build = "scripts.dev:build" +local-test = "scripts.dev:local_test" + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0", "setuptools>=68", "wheel"] +build-backend = "poetry.core.masonry.api" + +# Ruff configuration +[tool.ruff] +target-version = "py39" +line-length = 88 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "C90", # mccabe complexity +] + +# Ignore rules that are too strict for existing codebases +ignore = [ + "E501", # Line too long (handled by formatter) + "PLR0913", # Too many arguments + "E722", # Bare except + "PLW0603", # Global statement + "UP031", # % formatting vs f-strings + "E402", # Module import not at top +] + +[tool.ruff.format] +quote-style = "double" diff --git a/requirements/base.txt b/requirements/base.txt deleted file mode 100644 index 4bb251e..0000000 --- a/requirements/base.txt +++ /dev/null @@ -1,2 +0,0 @@ -simplejson>=3.20.1 -snapshot-restore-py>=1.0.0 diff --git a/requirements/dev.txt b/requirements/dev.txt deleted file mode 100644 index 68377ce..0000000 --- a/requirements/dev.txt +++ /dev/null @@ -1,12 +0,0 @@ -coverage>=4.4.0 -flake8>=3.3.0 -tox>=2.2.1 -pytest-cov>=2.4.0 -pylint>=1.7.2 -black>=20.8b0 -bandit>=1.6.2 - -# Test requirements -pytest>=3.0.7 -mock>=2.0.0 -parameterized>=0.9.0 \ No newline at end of file diff --git a/scripts/dev.py b/scripts/dev.py new file mode 100644 index 0000000..800970c --- /dev/null +++ b/scripts/dev.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import argparse +import subprocess +import shutil +import sys +import os +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + +def run(cmd, check=True, env=None): + print("\n$ {}".format(' '.join(cmd) if isinstance(cmd, list) else cmd)) + result = subprocess.run(cmd, shell=isinstance(cmd, str), check=check, env=env) + if result.returncode != 0 and check: + sys.exit(result.returncode) + + +def init(): + print("Initializing environment") + run(["poetry", "install"]) + + +def test(): + print("Running tests") + run(["poetry", "run", "pytest", "tests"]) + + +def lint(): + print("Running linters") + run(["poetry", "run", "ruff", "check", "awslambdaric/", "tests/"]) + + +def format_code(): + print("Formatting code") + run(["poetry", "run", "ruff", "format", "awslambdaric/", "tests/"]) + + +def clean(): + print("Cleaning build artifacts") + dirs_to_remove = ["build", "dist", "*.egg-info"] + for pattern in dirs_to_remove: + for path in ROOT.glob(pattern): + if path.is_dir(): + shutil.rmtree(path) + print("Removed directory: {}".format(path)) + elif path.is_file(): + path.unlink() + print("Removed file: {}".format(path)) + + +def build(): + print("Building package") + env = os.environ.copy() + + # Set BUILD=true on Linux for native compilation + import platform + if platform.system() == "Linux": + env["BUILD"] = "true" + elif os.getenv("BUILD") == "true": + env["BUILD"] = "true" + + run([sys.executable, "setup.py", "sdist", "bdist_wheel"], env=env) + + +def main(): + parser = argparse.ArgumentParser(description="Development scripts") + parser.add_argument("command", choices=[ + "init", "test", "lint", "format", "clean", "build" + ]) + + args = parser.parse_args() + + command_map = { + "init": init, + "test": test, + "lint": lint, + "format": format_code, + "clean": clean, + "build": build, + + } + + command_map[args.command]() + + +if __name__ == "__main__": + main() + diff --git a/setup.py b/setup.py index 2bf28ef..2db51f2 100644 --- a/setup.py +++ b/setup.py @@ -1,43 +1,51 @@ -""" -Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -""" - -import io import os import platform +import sys from subprocess import check_call, check_output -from setuptools import Extension, find_packages, setup -from awslambdaric import __version__ - +from setuptools import Extension, setup, find_packages + +if sys.version_info >= (3, 11): + import tomllib +else: + try: + import tomli as tomllib + except ImportError: + import subprocess + subprocess.check_call([sys.executable, "-m", "pip", "install", "tomli"]) + import tomli as tomllib + +def get_metadata(): + with open("pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + + poetry_config = pyproject["tool"]["poetry"] + return { + "name": poetry_config["name"], + "version": poetry_config["version"], + "description": poetry_config["description"], + "author": poetry_config["authors"][0] if poetry_config["authors"] else "", + "license": poetry_config["license"], + "python_requires": poetry_config["dependencies"]["python"], + "install_requires": [ + f"{pkg}{version}" if not version.startswith("^") and not version.startswith("~") else f"{pkg}>={version[1:]}" + for pkg, version in poetry_config["dependencies"].items() + if pkg != "python" + ] + } def get_curl_extra_linker_flags(): - # We do not want to build the dependencies during packaging - if platform.system() != "Linux" or os.getenv("BUILD") == "true": + if platform.system() != "Linux" or os.getenv("BUILD") != "true": return [] - - # Build the dependencies check_call(["./scripts/preinstall.sh"]) - - # call curl-config to get the required linker flags cmd = ["./deps/artifacts/bin/curl-config", "--static-libs"] - curl_config = check_output(cmd).decode("utf-8").replace("\n", "") - - # It is expected that the result of the curl-config call is similar to - # "/tmp/pip-req-build-g9dlug7g/deps/artifacts/lib/libcurl.a -lidn2" - # we want to return just the extra flags - flags = curl_config.split(" ")[1:] - - return flags - + curl_config = check_output(cmd).decode("utf-8").strip() + return curl_config.split(" ")[1:] def get_runtime_client_extension(): if platform.system() != "Linux" and os.getenv("BUILD") != "true": - print( - "The native runtime_client only builds on Linux. Skipping its compilation." - ) + print("Native extension build skipped on non-Linux.") return [] - - runtime_client = Extension( + return [Extension( "runtime_client", ["awslambdaric/runtime_client.cpp"], extra_compile_args=["--std=c++11"], @@ -45,54 +53,18 @@ def get_runtime_client_extension(): libraries=["aws-lambda-runtime", "curl"], extra_link_args=get_curl_extra_linker_flags(), include_dirs=["deps/artifacts/include"], - ) - - return [runtime_client] - - -def read(*filenames, **kwargs): - encoding = kwargs.get("encoding", "utf-8") - sep = kwargs.get("sep", os.linesep) - buf = [] - for filename in filenames: - with io.open(filename, encoding=encoding) as f: - buf.append(f.read()) - return sep.join(buf) - - -def read_requirements(req="base.txt"): - content = read(os.path.join("requirements", req)) - return [ - line for line in content.split(os.linesep) if not line.strip().startswith("#") - ] + )] +metadata = get_metadata() setup( - name="awslambdaric", - version=__version__, - author="Amazon Web Services", - description="AWS Lambda Runtime Interface Client for Python", - long_description=read("README.md"), - long_description_content_type="text/markdown", - url="https://github.com/aws/aws-lambda-python-runtime-interface-client", - packages=find_packages( - exclude=("tests", "tests.*", "docs", "examples", "versions") - ), - install_requires=read_requirements("base.txt"), - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Natural Language :: English", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - ], - python_requires=">=3.9", + name=metadata["name"], + version=metadata["version"], + description=metadata["description"], + author=metadata["author"], + license=metadata["license"], + packages=find_packages(), + python_requires=metadata["python_requires"], + install_requires=metadata["install_requires"], ext_modules=get_runtime_client_extension(), - test_suite="tests", ) diff --git a/tests/integration/docker/Dockerfile.echo.alpine b/tests/integration/docker/Dockerfile.echo.alpine index f6790fa..7eaf9f0 100644 --- a/tests/integration/docker/Dockerfile.echo.alpine +++ b/tests/integration/docker/Dockerfile.echo.alpine @@ -22,7 +22,8 @@ RUN apk add --no-cache \ elfutils-dev \ make \ cmake \ - libcurl + libcurl \ + curl # Include global args in this stage of the build ARG RIC_BUILD_DIR="/home/build/" @@ -32,8 +33,13 @@ RUN mkdir -p ${RIC_BUILD_DIR} WORKDIR ${RIC_BUILD_DIR} COPY . . RUN pip3 install setuptools -RUN make init build test && \ - mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz +# Install Poetry +RUN curl -sSL https://install.python-poetry.org | python3 - && \ + ln -s /root/.local/bin/poetry /usr/local/bin/poetry + +RUN make init build + +RUN ls -la ./dist/ # Include global args in this stage of the build ARG FUNCTION_DIR="/home/app/" @@ -45,7 +51,7 @@ COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} # Install the function's dependencies WORKDIR ${FUNCTION_DIR} RUN python${RUNTIME_VERSION} -m pip install \ - ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz \ + ${RIC_BUILD_DIR}/dist/*.whl \ --target ${FUNCTION_DIR} diff --git a/tests/integration/docker/Dockerfile.echo.amazonlinux2 b/tests/integration/docker/Dockerfile.echo.amazonlinux2 index be05aa1..883067a 100644 --- a/tests/integration/docker/Dockerfile.echo.amazonlinux2 +++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2 @@ -22,7 +22,8 @@ RUN yum install -y \ openssl11-devel \ bzip2-devel \ libffi-devel \ - sqlite-devel + sqlite-devel \ + curl RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/ftp/python/ | \ grep -oE "href=\"$(echo ${RUNTIME_VERSION} | sed "s/\\./\\\./g")\.[0-9]+" | \ @@ -72,6 +73,9 @@ RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/down ENV PATH=/usr/local/bin:$PATH ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +# Create symlinks for consistent python usage +RUN ln -sf /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python3 && \ + ln -sf /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python # Include global args in this stage of the build ARG RIC_BUILD_DIR="/home/build/" @@ -81,8 +85,15 @@ RUN mkdir -p ${RIC_BUILD_DIR} WORKDIR ${RIC_BUILD_DIR} COPY . . -RUN make init build test && \ - mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz +# Install wheel and poetry using the correct python version +RUN /usr/local/bin/python${RUNTIME_VERSION} -m pip install wheel poetry + +# Configure poetry to use the correct python version +RUN poetry config virtualenvs.create false + +RUN make init build + +RUN ls -la ./dist/ # Include global args in this stage of the build ARG FUNCTION_DIR="/home/app/" @@ -90,15 +101,12 @@ ARG FUNCTION_DIR="/home/app/" RUN mkdir -p ${FUNCTION_DIR} # Copy function code COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} -# Copy Runtime Interface Client .tgz -RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz # Install the function's dependencies WORKDIR ${FUNCTION_DIR} RUN python${RUNTIME_VERSION} -m pip install \ - awslambdaric-test.tar.gz \ - --target ${FUNCTION_DIR} && \ - rm awslambdaric-test.tar.gz + ${RIC_BUILD_DIR}/dist/*.whl \ + --target ${FUNCTION_DIR} # Stage 4 - final runtime interface client image diff --git a/tests/integration/docker/Dockerfile.echo.amazonlinux2023 b/tests/integration/docker/Dockerfile.echo.amazonlinux2023 index 16bbc79..14ebc60 100644 --- a/tests/integration/docker/Dockerfile.echo.amazonlinux2023 +++ b/tests/integration/docker/Dockerfile.echo.amazonlinux2023 @@ -6,7 +6,7 @@ FROM public.ecr.aws/amazonlinux/amazonlinux:${DISTRO_VERSION} AS python-amazonli ARG RUNTIME_VERSION # Install apt dependencies -RUN dnf install -y \ +RUN dnf install -y --allowerasing \ gcc \ gcc-c++ \ tar \ @@ -22,7 +22,8 @@ RUN dnf install -y \ openssl-devel \ bzip2-devel \ libffi-devel \ - sqlite-devel + sqlite-devel \ + curl RUN RUNTIME_LATEST_VERSION=${RUNTIME_VERSION}.$(curl -s https://www.python.org/ftp/python/ | \ grep -oE "href=\"$(echo ${RUNTIME_VERSION} | sed "s/\\./\\\./g")\.[0-9]+" | \ @@ -73,6 +74,9 @@ RUN wget --quiet -O cmake-install https://github.com/Kitware/CMake/releases/down ENV PATH=/usr/local/bin:$PATH ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH +# Create symlinks for consistent python usage +RUN ln -sf /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python3 && \ + ln -sf /usr/local/bin/python${RUNTIME_VERSION} /usr/local/bin/python # Include global args in this stage of the build ARG RIC_BUILD_DIR="/home/build/" @@ -85,15 +89,14 @@ COPY . . # distutils no longer available in python3.12 and later # https://docs.python.org/3/whatsnew/3.12.html # https://peps.python.org/pep-0632/ -RUN pip3 install setuptools -RUN make init build +RUN /usr/local/bin/python${RUNTIME_VERSION} -m pip install setuptools wheel poetry -RUN mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz -RUN python${RUNTIME_VERSION} -m pip install \ - ./dist/awslambdaric-test.tar.gz \ - --target ${RIC_BUILD_DIR} +# Configure poetry to use the correct python version +RUN poetry config virtualenvs.create false + +RUN make init build -RUN make test +RUN ls -la ./dist/ # Include global args in this stage of the build ARG FUNCTION_DIR="/home/app/" @@ -101,15 +104,12 @@ ARG FUNCTION_DIR="/home/app/" RUN mkdir -p ${FUNCTION_DIR} # Copy function code COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} -# Copy Runtime Interface Client .tgz -RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz # Install the function's dependencies WORKDIR ${FUNCTION_DIR} RUN python${RUNTIME_VERSION} -m pip install \ - awslambdaric-test.tar.gz \ - --target ${FUNCTION_DIR} && \ - rm awslambdaric-test.tar.gz + ${RIC_BUILD_DIR}/dist/*.whl \ + --target ${FUNCTION_DIR} # Stage 4 - final runtime interface client image diff --git a/tests/integration/docker/Dockerfile.echo.debian b/tests/integration/docker/Dockerfile.echo.debian index bf0f4fa..42edc6e 100644 --- a/tests/integration/docker/Dockerfile.echo.debian +++ b/tests/integration/docker/Dockerfile.echo.debian @@ -10,7 +10,8 @@ RUN apt-get update && \ g++ \ make \ cmake \ - libcurl4-openssl-dev + libcurl4-openssl-dev \ + curl # Include global args in this stage of the build ARG RIC_BUILD_DIR="/home/build/" @@ -20,8 +21,13 @@ RUN mkdir -p ${RIC_BUILD_DIR} WORKDIR ${RIC_BUILD_DIR} COPY . . RUN pip3 install setuptools -RUN make init build test && \ - mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz +# Install Poetry +RUN curl -sSL https://install.python-poetry.org | python3 - && \ + ln -s /root/.local/bin/poetry /usr/local/bin/poetry + +RUN make init build + +RUN ls -la ./dist/ # Include global args in this stage of the build ARG FUNCTION_DIR="/home/app/" @@ -29,15 +35,12 @@ ARG FUNCTION_DIR="/home/app/" RUN mkdir -p ${FUNCTION_DIR} # Copy function code COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} -# Copy Runtime Interface Client .tgz -RUN cp ./dist/awslambdaric-test.tar.gz ${FUNCTION_DIR}/awslambdaric-test.tar.gz # Install the function's dependencies WORKDIR ${FUNCTION_DIR} RUN pip install \ - awslambdaric-test.tar.gz \ - --target ${FUNCTION_DIR} && \ - rm awslambdaric-test.tar.gz + ${RIC_BUILD_DIR}/dist/*.whl \ + --target ${FUNCTION_DIR} # Stage 2 - final runtime interface client image diff --git a/tests/integration/docker/Dockerfile.echo.ubuntu b/tests/integration/docker/Dockerfile.echo.ubuntu index 0ce3000..dc19642 100644 --- a/tests/integration/docker/Dockerfile.echo.ubuntu +++ b/tests/integration/docker/Dockerfile.echo.ubuntu @@ -54,8 +54,11 @@ WORKDIR ${RIC_BUILD_DIR} COPY . . RUN . /home/venv/bin/activate && \ pip install setuptools && \ - make init build test && \ - mv ./dist/awslambdaric-*.tar.gz ./dist/awslambdaric-test.tar.gz + curl -sSL https://install.python-poetry.org | python3 - && \ + ln -s /root/.local/bin/poetry /usr/local/bin/poetry && \ + make init build + +RUN ls -la ./dist/ @@ -68,7 +71,7 @@ COPY tests/integration/test-handlers/echo/* ${FUNCTION_DIR} # Install the function's dependencies WORKDIR ${FUNCTION_DIR} RUN . /home/venv/bin/activate && \ - pip install ${RIC_BUILD_DIR}/dist/awslambdaric-test.tar.gz --target ${FUNCTION_DIR} + pip install ${RIC_BUILD_DIR}/dist/*.whl --target ${FUNCTION_DIR} diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 33afb1c..2b27844 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -88,7 +88,6 @@ def test_handle_event_request_happy_case(self): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) self.lambda_runtime.post_invocation_result.assert_called_once_with( @@ -112,7 +111,6 @@ def test_handle_event_request_invalid_client_context(self): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -154,7 +152,6 @@ def test_handle_event_request_invalid_cognito_idenity(self): "invalid_cognito_identity", "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -197,7 +194,6 @@ def test_handle_event_request_invalid_event_body(self): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -242,7 +238,6 @@ def invalid_json_response(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -288,7 +283,6 @@ def __init__(self, message): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -341,7 +335,6 @@ def __init__(self, message): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -393,7 +386,6 @@ def unable_to_import_module(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -433,7 +425,6 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -484,12 +475,15 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) # NOTE: Indentation characters are NO-BREAK SPACE (U+00A0) not SPACE (U+0020) - error_logs = "[ERROR] FaultExceptionType: Fault exception msg\r" + error_logs = ( + lambda_unhandled_exception_warning_message + + "\n" + + "[ERROR] FaultExceptionType: Fault exception msg\r" + ) error_logs += "Traceback (most recent call last):\r" error_logs += '  File "spam.py", line 3, in \r' error_logs += "    spam.eggs()\r" @@ -520,10 +514,13 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n" + error_logs = ( + lambda_unhandled_exception_warning_message + + "\n" + + "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n" + ) self.assertEqual(mock_stdout.getvalue(), error_logs) @@ -549,10 +546,13 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" + error_logs = ( + lambda_unhandled_exception_warning_message + + "\n" + + "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" + ) self.assertEqual(mock_stdout.getvalue(), error_logs) @@ -578,10 +578,13 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = "[ERROR] Fault exception msg\rTraceback (most recent call last):\n" + error_logs = ( + lambda_unhandled_exception_warning_message + + "\n" + + "[ERROR] Fault exception msg\rTraceback (most recent call last):\n" + ) self.assertEqual(mock_stdout.getvalue(), error_logs) @@ -616,10 +619,9 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = "[ERROR]\r" + error_logs = lambda_unhandled_exception_warning_message + "\n[ERROR]\r" error_logs += "Traceback (most recent call last):\r" error_logs += '  File "spam.py", line 3, in \r' error_logs += "    spam.eggs()\r" @@ -650,16 +652,25 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, - "tenant_id", bootstrap.StandardLogSink(), ) stdout_value = mock_stdout.getvalue() + received_warning = stdout_value.split("\n")[0] + received_rest = stdout_value[len(received_warning) + 1 :] + + warning = json.loads(received_warning) + self.assertEqual(warning["level"], "WARNING") + self.assertEqual(warning["message"], lambda_unhandled_exception_warning_message) + self.assertEqual(warning["logger"], "root") + self.assertIn("timestamp", warning) # this line is not in json because of the way the test runtime is bootstrapped - error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" + error_logs = ( + "\n[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" + ) - self.assertEqual(stdout_value, error_logs) + self.assertEqual(received_rest, error_logs) class TestXrayFault(unittest.TestCase): @@ -857,7 +868,6 @@ def test_application_json(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, - tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -877,7 +887,6 @@ def test_binary_request_binary_response(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, - tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -897,7 +906,6 @@ def test_json_request_binary_response(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, - tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -916,7 +924,6 @@ def test_binary_with_application_json(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, - tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -924,10 +931,13 @@ def test_binary_with_application_json(self): self.lambda_runtime.post_invocation_error.assert_called_once() ( - invoke_id, - error_result, - xray_fault, - ), _ = self.lambda_runtime.post_invocation_error.call_args + ( + invoke_id, + error_result, + xray_fault, + ), + _, + ) = self.lambda_runtime.post_invocation_error.call_args error_dict = json.loads(error_result) self.assertEqual("invoke-id", invoke_id) @@ -1350,31 +1360,6 @@ def test_json_formatter(self, mock_stderr): ) self.assertEqual(mock_stderr.getvalue(), "") - @patch("awslambdaric.bootstrap._GLOBAL_TENANT_ID", "test-tenant-id") - @patch("sys.stderr", new_callable=StringIO) - def test_json_formatter_with_tenant_id(self, mock_stderr): - logger = logging.getLogger("a.b") - level = logging.INFO - message = "Test json formatting with tenant id" - expected = { - "level": "INFO", - "logger": "a.b", - "message": message, - "requestId": "", - "tenantId": "test-tenant-id", - } - - with patch("sys.stdout", new_callable=StringIO) as mock_stdout: - logger.log(level, message) - - data = json.loads(mock_stdout.getvalue()) - data.pop("timestamp") - self.assertEqual( - data, - expected, - ) - self.assertEqual(mock_stderr.getvalue(), "") - @patch("sys.stdout", new_callable=StringIO) @patch("sys.stderr", new_callable=StringIO) def test_exception(self, mock_stderr, mock_stdout): From 3ec0f508685d2010fcf3811c14a12379ef78b41b Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi Date: Wed, 30 Jul 2025 13:37:34 +0100 Subject: [PATCH 2/7] Format files according to ruff standards --- awslambdaric/bootstrap.py | 1 - tests/test_bootstrap.py | 84 ++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 34 deletions(-) diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py index cb8d5c3..f63e765 100644 --- a/awslambdaric/bootstrap.py +++ b/awslambdaric/bootstrap.py @@ -201,7 +201,6 @@ def handle_event_request( ) if error_result is not None: - log_error(error_result, log_sink) lambda_runtime_client.post_invocation_error( invoke_id, to_json(error_result), to_json(xray_fault) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 2b27844..d08d900 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -88,6 +88,7 @@ def test_handle_event_request_happy_case(self): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) self.lambda_runtime.post_invocation_result.assert_called_once_with( @@ -111,6 +112,7 @@ def test_handle_event_request_invalid_client_context(self): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -152,6 +154,7 @@ def test_handle_event_request_invalid_cognito_idenity(self): "invalid_cognito_identity", "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -194,6 +197,7 @@ def test_handle_event_request_invalid_event_body(self): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -238,6 +242,7 @@ def invalid_json_response(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -283,6 +288,7 @@ def __init__(self, message): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -335,6 +341,7 @@ def __init__(self, message): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -386,6 +393,7 @@ def unable_to_import_module(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -425,6 +433,7 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) args, _ = self.lambda_runtime.post_invocation_error.call_args @@ -475,15 +484,12 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) # NOTE: Indentation characters are NO-BREAK SPACE (U+00A0) not SPACE (U+0020) - error_logs = ( - lambda_unhandled_exception_warning_message - + "\n" - + "[ERROR] FaultExceptionType: Fault exception msg\r" - ) + error_logs = "[ERROR] FaultExceptionType: Fault exception msg\r" error_logs += "Traceback (most recent call last):\r" error_logs += '  File "spam.py", line 3, in \r' error_logs += "    spam.eggs()\r" @@ -514,13 +520,10 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = ( - lambda_unhandled_exception_warning_message - + "\n" - + "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n" - ) + error_logs = "[ERROR] FaultExceptionType: Fault exception msg\rTraceback (most recent call last):\n" self.assertEqual(mock_stdout.getvalue(), error_logs) @@ -546,13 +549,10 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = ( - lambda_unhandled_exception_warning_message - + "\n" - + "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" - ) + error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" self.assertEqual(mock_stdout.getvalue(), error_logs) @@ -578,13 +578,10 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = ( - lambda_unhandled_exception_warning_message - + "\n" - + "[ERROR] Fault exception msg\rTraceback (most recent call last):\n" - ) + error_logs = "[ERROR] Fault exception msg\rTraceback (most recent call last):\n" self.assertEqual(mock_stdout.getvalue(), error_logs) @@ -619,9 +616,10 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) - error_logs = lambda_unhandled_exception_warning_message + "\n[ERROR]\r" + error_logs = "[ERROR]\r" error_logs += "Traceback (most recent call last):\r" error_logs += '  File "spam.py", line 3, in \r' error_logs += "    spam.eggs()\r" @@ -652,25 +650,16 @@ def raise_exception_handler(json_input, lambda_context): {}, "invoked_function_arn", 0, + "tenant_id", bootstrap.StandardLogSink(), ) stdout_value = mock_stdout.getvalue() - received_warning = stdout_value.split("\n")[0] - received_rest = stdout_value[len(received_warning) + 1 :] - - warning = json.loads(received_warning) - self.assertEqual(warning["level"], "WARNING") - self.assertEqual(warning["message"], lambda_unhandled_exception_warning_message) - self.assertEqual(warning["logger"], "root") - self.assertIn("timestamp", warning) # this line is not in json because of the way the test runtime is bootstrapped - error_logs = ( - "\n[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" - ) + error_logs = "[ERROR] FaultExceptionType\rTraceback (most recent call last):\n" - self.assertEqual(received_rest, error_logs) + self.assertEqual(stdout_value, error_logs) class TestXrayFault(unittest.TestCase): @@ -868,6 +857,7 @@ def test_application_json(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, + tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -887,6 +877,7 @@ def test_binary_request_binary_response(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, + tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -906,6 +897,7 @@ def test_json_request_binary_response(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, + tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -924,6 +916,7 @@ def test_binary_with_application_json(self): cognito_identity_json=None, invoked_function_arn="invocation-arn", epoch_deadline_time_in_ms=1415836801003, + tenant_id=None, log_sink=bootstrap.StandardLogSink(), ) @@ -1360,6 +1353,31 @@ def test_json_formatter(self, mock_stderr): ) self.assertEqual(mock_stderr.getvalue(), "") + @patch("awslambdaric.bootstrap._GLOBAL_TENANT_ID", "test-tenant-id") + @patch("sys.stderr", new_callable=StringIO) + def test_json_formatter_with_tenant_id(self, mock_stderr): + logger = logging.getLogger("a.b") + level = logging.INFO + message = "Test json formatting with tenant id" + expected = { + "level": "INFO", + "logger": "a.b", + "message": message, + "requestId": "", + "tenantId": "test-tenant-id", + } + + with patch("sys.stdout", new_callable=StringIO) as mock_stdout: + logger.log(level, message) + + data = json.loads(mock_stdout.getvalue()) + data.pop("timestamp") + self.assertEqual( + data, + expected, + ) + self.assertEqual(mock_stderr.getvalue(), "") + @patch("sys.stdout", new_callable=StringIO) @patch("sys.stderr", new_callable=StringIO) def test_exception(self, mock_stderr, mock_stdout): From 0288d2773c19e0d2d45d8b3589b3eaa8bb6b4812 Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi Date: Wed, 30 Jul 2025 14:49:51 +0100 Subject: [PATCH 3/7] Implement local Lambda testing with Runtime Interface Emulator --- .gitignore | 3 ++ Dockerfile.build | 28 +++++++++++++++++ Dockerfile.rie | 26 ++++++++++++++++ Makefile | 10 ++++++ README.md | 64 ++++++++++++++++++++------------------ pyproject.toml | 3 +- scripts/build-container.sh | 25 +++++++++++++++ scripts/dev.py | 15 +++++++-- scripts/test-rie.sh | 26 ++++++++++++++++ 9 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 Dockerfile.build create mode 100644 Dockerfile.rie create mode 100755 scripts/build-container.sh create mode 100755 scripts/test-rie.sh diff --git a/.gitignore b/.gitignore index 08747aa..ea5d560 100644 --- a/.gitignore +++ b/.gitignore @@ -156,3 +156,6 @@ deps/curl-*/ # local caches .DS_Store + +# local build artifacts +build-artifacts diff --git a/Dockerfile.build b/Dockerfile.build new file mode 100644 index 0000000..a26c832 --- /dev/null +++ b/Dockerfile.build @@ -0,0 +1,28 @@ +FROM python:3.9-alpine AS build-image + +# Install build dependencies +RUN apk add --no-cache \ + build-base \ + libtool \ + autoconf \ + automake \ + elfutils-dev \ + make \ + cmake \ + libcurl \ + curl \ + libstdc++ \ + binutils + +# Build awslambdaric +ARG RIC_BUILD_DIR="/home/build/" +RUN mkdir -p ${RIC_BUILD_DIR} +WORKDIR ${RIC_BUILD_DIR} +COPY . . +RUN pip install setuptools +RUN curl -sSL https://install.python-poetry.org | python3 - && \ + ln -s /root/.local/bin/poetry /usr/local/bin/poetry +RUN make init build + +# Keep the built wheel accessible +CMD ["sh", "-c", "echo 'Build complete. Wheel available in /home/build/dist/'"] \ No newline at end of file diff --git a/Dockerfile.rie b/Dockerfile.rie new file mode 100644 index 0000000..1caa8d8 --- /dev/null +++ b/Dockerfile.rie @@ -0,0 +1,26 @@ +FROM python:3.9-alpine + +RUN apk add --no-cache libstdc++ curl + +# Install RIE +RUN curl -Lo /usr/local/bin/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \ + chmod +x /usr/local/bin/aws-lambda-rie + +# Add the pre-built wheel +ADD build-artifacts/*.whl /tmp/ + +# Install the wheel +RUN pip install /tmp/*.whl + +# Copy test handler +COPY tests/integration/test-handlers/echo/app.py /var/task/app.py + +# Set environment for local testing +ENV AWS_LAMBDA_RUNTIME_API="127.0.0.1:8080" +ENV LAMBDA_TASK_ROOT="/var/task" +ENV _HANDLER="app.handler" + +WORKDIR /var/task + +ENTRYPOINT ["/usr/local/bin/aws-lambda-rie"] +CMD ["python", "-m", "awslambdaric", "app.handler"] \ No newline at end of file diff --git a/Makefile b/Makefile index 70f415b..e62cb9c 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,14 @@ pr: init check-format check-security dev codebuild: setup-codebuild-agent CODEBUILD_IMAGE_TAG=codebuild-agent DISTRO="$(DISTRO)" tests/integration/codebuild-local/test_all.sh tests/integration/codebuild +.PHONY: build-container +build-container: + ./scripts/build-container.sh + +.PHONY: test-rie +test-rie: + ./scripts/test-rie.sh + define HELP_MESSAGE Usage: $ make [TARGETS] @@ -68,6 +76,8 @@ TARGETS clean Cleans the working directory using scripts/dev.py. dev Run all development tests using scripts/dev.py. init Install dependencies via scripts/dev.py. + build-container Build awslambdaric wheel in isolated container. + test-rie Test with RIE using pre-built wheel (run build-container first). pr Perform all checks before submitting a Pull Request. test Run unit tests using scripts/dev.py. lint Run all linters via scripts/dev.py. diff --git a/README.md b/README.md index 4a96a3f..f0aa532 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,26 @@ The Python Runtime Interface Client package currently supports Python versions: ## Usage +### Container-Based Builds + +For development or when you need to build awslambdaric from source, you can use container-based builds to ensure consistent compilation across different platforms, and native dependencies linking. + +```shell script +# Build awslambdaric wheel in a Linux container +make build-container +# Or with poetry (run 'poetry install' first): +poetry run build-container + +# Test with RIE using the built wheel +make test-rie +# Or with poetry: +poetry run test-rie +``` + +This approach builds the C++ extensions in a Linux environment, ensuring compatibility with Lambda's runtime environment regardless of your host OS. + +**Note**: Running `make build` (or `poetry run build`) on non-Linux machines will not properly link the native C++ dependencies, resulting in a non-functional runtime client. Always use container-based builds for development. + ### Creating a Docker Image for Lambda with the Runtime Interface Client First step is to choose the base image to be used. The supported Linux OS distributions are: @@ -103,38 +123,23 @@ def handler(event, context): ### Local Testing -To make it easy to locally test Lambda functions packaged as container images we open-sourced a lightweight web-server, Lambda Runtime Interface Emulator (RIE), which allows your function packaged as a container image to accept HTTP requests. You can install the [AWS Lambda Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) on your local machine to test your function. Then when you run the image function, you set the entrypoint to be the emulator. - -*To install the emulator and test your Lambda function* - -1) From your project directory, run the following command to download the RIE from GitHub and install it on your local machine. +To test Lambda functions with the Runtime Interface Client, use the [AWS Lambda Runtime Interface Emulator (RIE)](https://github.com/aws/aws-lambda-runtime-interface-emulator). To test your local changes with RIE (Runtime Interface Emulator): ```shell script -mkdir -p ~/.aws-lambda-rie && \ - curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie && \ - chmod +x ~/.aws-lambda-rie/aws-lambda-rie +# Build your current code (do this when you make changes) +# We build on a linux machine to ensure native build dependencies are met +make build-container +# Or with poetry: +poetry run build-container + +# Test with RIE (fast, repeatable) +make test-rie +# Or with poetry: +poetry run test-rie + +# Test the function +curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"message":"test"}' ``` -2) Run your Lambda image function using the docker run command. - -```shell script -docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \ - --entrypoint /aws-lambda/aws-lambda-rie \ - myfunction:latest \ - /usr/local/bin/python -m awslambdaric app.handler -``` - -This runs the image as a container and starts up an endpoint locally at `http://localhost:9000/2015-03-31/functions/function/invocations`. - -3) Post an event to the following endpoint using a curl command: - -```shell script -curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' -``` - -This command invokes the function running in the container image and returns a response. - -*Alternately, you can also include RIE as a part of your base image. See the AWS documentation on how to [Build RIE into your base image](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html#images-test-alternative).* - ## Development @@ -145,7 +150,6 @@ Clone this repository and run: make init make build ``` - ### Running tests Make sure the project is built: diff --git a/pyproject.toml b/pyproject.toml index 0421ecb..7c0c05b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,8 @@ lint = "scripts.dev:lint" format = "scripts.dev:format_code" clean = "scripts.dev:clean" build = "scripts.dev:build" -local-test = "scripts.dev:local_test" +build-container = "scripts.dev:build_container" +test-rie = "scripts.dev:test_rie" [build-system] requires = ["poetry-core>=2.0.0,<3.0.0", "setuptools>=68", "wheel"] diff --git a/scripts/build-container.sh b/scripts/build-container.sh new file mode 100755 index 0000000..2b7c9c6 --- /dev/null +++ b/scripts/build-container.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -euo pipefail + +TAG=${TAG:-latest} +PYTHON_VERSION=${PYTHON_VERSION:-3.9} + +echo "Building awslambdaric wheel for Python ${PYTHON_VERSION}..." + +echo "Building wheel in container..." +docker build \ + --build-arg PYTHON_VERSION="${PYTHON_VERSION}" \ + -f Dockerfile.build \ + -t "awslambdaric-builder:${TAG}" \ + . + +echo "Extracting built wheel..." +mkdir -p build-artifacts + +docker run --rm -v $(pwd)/build-artifacts:/output awslambdaric-builder:${TAG} /bin/sh -c " + cp /home/build/dist/*.whl /output/ + echo 'Wheel copied to build-artifacts/' + ls -la /output/ +" + +echo "Build complete! Wheel is available in build-artifacts/" \ No newline at end of file diff --git a/scripts/dev.py b/scripts/dev.py index 800970c..636d4b8 100644 --- a/scripts/dev.py +++ b/scripts/dev.py @@ -62,10 +62,20 @@ def build(): run([sys.executable, "setup.py", "sdist", "bdist_wheel"], env=env) +def build_container(): + print("Building awslambdaric wheel in container") + run(["./scripts/build-container.sh"]) + + +def test_rie(): + print("Testing with RIE using pre-built wheel") + run(["./scripts/test-rie.sh"]) + + def main(): parser = argparse.ArgumentParser(description="Development scripts") parser.add_argument("command", choices=[ - "init", "test", "lint", "format", "clean", "build" + "init", "test", "lint", "format", "clean", "build", "build-container", "test-rie" ]) args = parser.parse_args() @@ -77,7 +87,8 @@ def main(): "format": format_code, "clean": clean, "build": build, - + "build-container": build_container, + "test-rie": test_rie, } command_map[args.command]() diff --git a/scripts/test-rie.sh b/scripts/test-rie.sh new file mode 100755 index 0000000..195d5cc --- /dev/null +++ b/scripts/test-rie.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -euo pipefail + +DOCKERFILE="Dockerfile.rie" + +echo "Starting RIE test setup..." + +# Check if build artifacts exist +if [ ! -d "build-artifacts" ] || [ -z "$(ls -A build-artifacts/*.whl 2>/dev/null)" ]; then + echo "No build artifacts found. Please run 'make build-container' first." + exit 1 +fi + +echo "Building test Docker image..." +docker build \ + -f "${DOCKERFILE}" \ + -t awslambdaric-rie-test . + +echo "Starting test container on port 9000..." +echo "" +echo "Test with:" +echo "curl -XPOST \"http://localhost:9000/2015-03-31/functions/function/invocations\" -d '{\"message\":\"test\"}'" +echo "" + +docker run -it -p 9000:8080 \ + --rm awslambdaric-rie-test \ No newline at end of file From 9e4c57be731c3aeb1fa0b9433ab4bb1d50fe254a Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi Date: Wed, 30 Jul 2025 16:27:06 +0100 Subject: [PATCH 4/7] Add docstrings checking with ruff and implement the fixes --- Makefile | 5 +++ awslambdaric/__init__.py | 4 +- awslambdaric/__main__.py | 5 +-- awslambdaric/bootstrap.py | 50 ++++++++++++++++++--- awslambdaric/lambda_context.py | 17 +++++-- awslambdaric/lambda_literals.py | 4 +- awslambdaric/lambda_runtime_client.py | 21 +++++++-- awslambdaric/lambda_runtime_exception.py | 7 +-- awslambdaric/lambda_runtime_hooks_runner.py | 9 +++- awslambdaric/lambda_runtime_log_utils.py | 11 +++-- awslambdaric/lambda_runtime_marshaller.py | 14 ++++-- pyproject.toml | 3 +- 12 files changed, 116 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index e62cb9c..7c27881 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,10 @@ build-container: test-rie: ./scripts/test-rie.sh +.PHONY: check-docstr +check-docstr: + poetry run ruff check --select D --ignore D105 awslambdaric/ + define HELP_MESSAGE Usage: $ make [TARGETS] @@ -83,4 +87,5 @@ TARGETS lint Run all linters via scripts/dev.py. test-smoke Run smoke tests inside Docker. test-integ Run all integration tests. + check-docstr Check docstrings in project using ruff format check. endef \ No newline at end of file diff --git a/awslambdaric/__init__.py b/awslambdaric/__init__.py index 5605903..0f94db0 100644 --- a/awslambdaric/__init__.py +++ b/awslambdaric/__init__.py @@ -1,5 +1,3 @@ -""" -Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. -""" +"""Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.""" __version__ = "3.1.1" diff --git a/awslambdaric/__main__.py b/awslambdaric/__main__.py index 5cbbaab..3cd1ad4 100644 --- a/awslambdaric/__main__.py +++ b/awslambdaric/__main__.py @@ -1,6 +1,4 @@ -""" -Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -""" +"""Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.""" import os import sys @@ -9,6 +7,7 @@ def main(args): + """Run the Lambda runtime main entry point.""" app_root = os.getcwd() try: diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py index f63e765..40463c2 100644 --- a/awslambdaric/bootstrap.py +++ b/awslambdaric/bootstrap.py @@ -1,6 +1,4 @@ -""" -Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. -""" +"""Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.""" import importlib import json @@ -36,6 +34,7 @@ def _get_handler(handler): + """Get handler function from module.""" try: (modname, fname) = handler.rsplit(".", 1) except ValueError as e: @@ -82,6 +81,7 @@ def make_error( stack_trace, invoke_id=None, ): + """Create error response.""" result = { "errorMessage": error_message if error_message else "", "errorType": error_type if error_type else "", @@ -92,6 +92,7 @@ def make_error( def replace_line_indentation(line, indent_char, new_indent_char): + """Replace line indentation characters.""" ident_chars_count = 0 for c in line: if c != indent_char: @@ -104,6 +105,7 @@ def replace_line_indentation(line, indent_char, new_indent_char): _ERROR_FRAME_TYPE = _JSON_FRAME_TYPES[logging.ERROR] def log_error(error_result, log_sink): + """Log error in JSON format.""" error_result = { "timestamp": time.strftime( _DATETIME_FORMAT, logging.Formatter.converter(time.time()) @@ -119,6 +121,7 @@ def log_error(error_result, log_sink): _ERROR_FRAME_TYPE = _TEXT_FRAME_TYPES[logging.ERROR] def log_error(error_result, log_sink): + """Log error in text format.""" error_description = "[ERROR]" error_result_type = error_result.get("errorType") @@ -161,6 +164,7 @@ def handle_event_request( tenant_id, log_sink, ): + """Handle Lambda event request.""" error_result = None try: lambda_context = create_lambda_context( @@ -212,6 +216,7 @@ def handle_event_request( def parse_json_header(header, name): + """Parse JSON header.""" try: return json.loads(header) except Exception as e: @@ -230,6 +235,7 @@ def create_lambda_context( invoked_function_arn, tenant_id, ): + """Create Lambda context object.""" client_context = None if client_context_json: client_context = parse_json_header(client_context_json, "Client Context") @@ -248,6 +254,7 @@ def create_lambda_context( def build_fault_result(exc_info, msg): + """Build fault result from exception info.""" etype, value, tb = exc_info tb_tuples = extract_traceback(tb) for i in range(len(tb_tuples)): @@ -263,6 +270,7 @@ def build_fault_result(exc_info, msg): def make_xray_fault(ex_type, ex_msg, working_dir, tb_tuples): + """Create X-Ray fault object.""" stack = [] files = set() for t in tb_tuples: @@ -281,6 +289,7 @@ def make_xray_fault(ex_type, ex_msg, working_dir, tb_tuples): def extract_traceback(tb): + """Extract traceback information.""" return [ (frame.filename, frame.lineno, frame.name, frame.line) for frame in traceback.extract_tb(tb) @@ -288,6 +297,7 @@ def extract_traceback(tb): def on_init_complete(lambda_runtime_client, log_sink): + """Handle initialization completion.""" from . import lambda_runtime_hooks_runner try: @@ -311,21 +321,29 @@ def on_init_complete(lambda_runtime_client, log_sink): class LambdaLoggerHandler(logging.Handler): + """Lambda logger handler.""" + def __init__(self, log_sink): + """Initialize logger handler.""" logging.Handler.__init__(self) self.log_sink = log_sink def emit(self, record): + """Emit log record.""" msg = self.format(record) self.log_sink.log(msg) class LambdaLoggerHandlerWithFrameType(logging.Handler): + """Lambda logger handler with frame type.""" + def __init__(self, log_sink): + """Initialize logger handler.""" super().__init__() self.log_sink = log_sink def emit(self, record): + """Emit log record with frame type.""" self.log_sink.log( self.format(record), frame_type=( @@ -336,14 +354,20 @@ def emit(self, record): class LambdaLoggerFilter(logging.Filter): + """Lambda logger filter.""" + def filter(self, record): + """Filter log record.""" record.aws_request_id = _GLOBAL_AWS_REQUEST_ID or "" record.tenant_id = _GLOBAL_TENANT_ID return True class Unbuffered(object): + """Unbuffered stream wrapper.""" + def __init__(self, stream): + """Initialize unbuffered stream.""" self.stream = stream def __enter__(self): @@ -356,16 +380,21 @@ def __getattr__(self, attr): return getattr(self.stream, attr) def write(self, msg): + """Write message to stream.""" self.stream.write(msg) self.stream.flush() def writelines(self, msgs): + """Write multiple lines to stream.""" self.stream.writelines(msgs) self.stream.flush() class StandardLogSink(object): + """Standard log sink.""" + def __init__(self): + """Initialize standard log sink.""" pass def __enter__(self): @@ -375,17 +404,19 @@ def __exit__(self, exc_type, exc_value, exc_tb): pass def log(self, msg, frame_type=None): + """Log message to stdout.""" sys.stdout.write(msg) def log_error(self, message_lines): + """Log error message to stdout.""" error_message = ERROR_LOG_LINE_TERMINATE.join(message_lines) + "\n" sys.stdout.write(error_message) class FramedTelemetryLogSink(object): - """ - FramedTelemetryLogSink implements the logging contract between runtimes and the platform. It implements a simple - framing protocol so message boundaries can be determined. Each frame can be visualized as follows: + """FramedTelemetryLogSink implements the logging contract between runtimes and the platform. + + It implements a simple framing protocol so message boundaries can be determined. Each frame can be visualized as follows:
     {@code
     +----------------------+------------------------+---------------------+-----------------------+
@@ -399,6 +430,7 @@ class FramedTelemetryLogSink(object):
     """
 
     def __init__(self, fd):
+        """Initialize framed telemetry log sink."""
         self.fd = int(fd)
 
     def __enter__(self):
@@ -409,6 +441,7 @@ def __exit__(self, exc_type, exc_value, exc_tb):
         self.file.close()
 
     def log(self, msg, frame_type=None):
+        """Log message with frame type."""
         encoded_msg = msg.encode("utf8")
 
         timestamp = int(time.time_ns() / 1000)  # UNIX timestamp in microseconds
@@ -421,6 +454,7 @@ def log(self, msg, frame_type=None):
         self.file.write(log_msg)
 
     def log_error(self, message_lines):
+        """Log error message."""
         error_message = "\n".join(message_lines)
         self.log(
             error_message,
@@ -429,6 +463,7 @@ def log_error(self, message_lines):
 
 
 def update_xray_env_variable(xray_trace_id):
+    """Update X-Ray trace ID environment variable."""
     if xray_trace_id is not None:
         os.environ["_X_AMZN_TRACE_ID"] = xray_trace_id
     else:
@@ -437,6 +472,7 @@ def update_xray_env_variable(xray_trace_id):
 
 
 def create_log_sink():
+    """Create appropriate log sink."""
     if "_LAMBDA_TELEMETRY_LOG_FD" in os.environ:
         fd = os.environ["_LAMBDA_TELEMETRY_LOG_FD"]
         del os.environ["_LAMBDA_TELEMETRY_LOG_FD"]
@@ -451,6 +487,7 @@ def create_log_sink():
 
 
 def _setup_logging(log_format, log_level, log_sink):
+    """Set up logging configuration."""
     logging.Formatter.converter = time.gmtime
     logger = logging.getLogger()
 
@@ -477,6 +514,7 @@ def _setup_logging(log_format, log_level, log_sink):
 
 
 def run(app_root, handler, lambda_runtime_api_addr):
+    """Run Lambda runtime."""
     sys.stdout = Unbuffered(sys.stdout)
     sys.stderr = Unbuffered(sys.stderr)
 
diff --git a/awslambdaric/lambda_context.py b/awslambdaric/lambda_context.py
index e0a3363..e827993 100644
--- a/awslambdaric/lambda_context.py
+++ b/awslambdaric/lambda_context.py
@@ -1,6 +1,4 @@
-"""
-Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-"""
+"""Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved."""
 
 import logging
 import os
@@ -9,6 +7,8 @@
 
 
 class LambdaContext(object):
+    """Lambda context object."""
+
     def __init__(
         self,
         invoke_id,
@@ -18,6 +18,7 @@ def __init__(
         invoked_function_arn=None,
         tenant_id=None,
     ):
+        """Initialize Lambda context."""
         self.aws_request_id = invoke_id
         self.log_group_name = os.environ.get("AWS_LAMBDA_LOG_GROUP_NAME")
         self.log_stream_name = os.environ.get("AWS_LAMBDA_LOG_STREAM_NAME")
@@ -45,11 +46,13 @@ def __init__(
         self._epoch_deadline_time_in_ms = epoch_deadline_time_in_ms
 
     def get_remaining_time_in_millis(self):
+        """Get remaining time in milliseconds."""
         epoch_now_in_ms = int(time.time() * 1000)
         delta_ms = self._epoch_deadline_time_in_ms - epoch_now_in_ms
         return delta_ms if delta_ms > 0 else 0
 
     def log(self, msg):
+        """Log a message."""
         for handler in logging.getLogger().handlers:
             if hasattr(handler, "log_sink"):
                 handler.log_sink.log(str(msg))
@@ -74,6 +77,8 @@ def __repr__(self):
 
 
 class CognitoIdentity(object):
+    """Cognito identity information."""
+
     __slots__ = ["cognito_identity_id", "cognito_identity_pool_id"]
 
     def __repr__(self):
@@ -86,6 +91,8 @@ def __repr__(self):
 
 
 class Client(object):
+    """Client information."""
+
     __slots__ = [
         "installation_id",
         "app_title",
@@ -107,6 +114,8 @@ def __repr__(self):
 
 
 class ClientContext(object):
+    """Client context information."""
+
     __slots__ = ["custom", "env", "client"]
 
     def __repr__(self):
@@ -120,6 +129,7 @@ def __repr__(self):
 
 
 def make_obj_from_dict(_class, _dict, fields=None):
+    """Create object from dictionary."""
     if _dict is None:
         return None
     obj = _class()
@@ -128,6 +138,7 @@ def make_obj_from_dict(_class, _dict, fields=None):
 
 
 def set_obj_from_dict(obj, _dict, fields=None):
+    """Set object attributes from dictionary."""
     if fields is None:
         fields = obj.__class__.__slots__
     for field in fields:
diff --git a/awslambdaric/lambda_literals.py b/awslambdaric/lambda_literals.py
index 2585b89..a2a0746 100644
--- a/awslambdaric/lambda_literals.py
+++ b/awslambdaric/lambda_literals.py
@@ -1,6 +1,4 @@
-"""
-Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-"""
+"""Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved."""
 
 lambda_warning = "LAMBDA_WARNING"
 
diff --git a/awslambdaric/lambda_runtime_client.py b/awslambdaric/lambda_runtime_client.py
index ba4ad92..fd5fdf1 100644
--- a/awslambdaric/lambda_runtime_client.py
+++ b/awslambdaric/lambda_runtime_client.py
@@ -1,6 +1,4 @@
-"""
-Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-"""
+"""Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved."""
 
 import sys
 from awslambdaric import __version__
@@ -29,15 +27,22 @@ def _user_agent():
 
 
 class InvocationRequest(object):
+    """Lambda invocation request."""
+
     def __init__(self, **kwds):
+        """Initialize invocation request."""
         self.__dict__.update(kwds)
 
     def __eq__(self, other):
+        """Check equality."""
         return self.__dict__ == other.__dict__
 
 
 class LambdaRuntimeClientError(Exception):
+    """Lambda runtime client error."""
+
     def __init__(self, endpoint, response_code, response_body):
+        """Initialize runtime client error."""
         self.endpoint = endpoint
         self.response_code = response_code
         self.response_body = response_body
@@ -47,12 +52,15 @@ def __init__(self, endpoint, response_code, response_body):
 
 
 class LambdaRuntimeClient(object):
+    """Lambda runtime client."""
+
     marshaller = LambdaMarshaller()
     """marshaller is a class attribute that determines the unmarshalling and marshalling logic of a function's event
     and response. It allows for function authors to override the the default implementation, LambdaMarshaller which
     unmarshals and marshals JSON, to an instance of a class that implements the same interface."""
 
     def __init__(self, lambda_runtime_address, use_thread_for_polling_next=False):
+        """Initialize runtime client."""
         self.lambda_runtime_address = lambda_runtime_address
         self.use_thread_for_polling_next = use_thread_for_polling_next
         if self.use_thread_for_polling_next:
@@ -65,6 +73,7 @@ def __init__(self, lambda_runtime_address, use_thread_for_polling_next=False):
     def call_rapid(
         self, http_method, endpoint, expected_http_code, payload=None, headers=None
     ):
+        """Call RAPID endpoint."""
         # These imports are heavy-weight. They implicitly trigger `import ssl, hashlib`.
         # Importing them lazily to speed up critical path of a common case.
         import http.client
@@ -84,6 +93,7 @@ def call_rapid(
             raise LambdaRuntimeClientError(endpoint, response.code, response_body)
 
     def post_init_error(self, error_response_data, error_type_override=None):
+        """Post initialization error."""
         import http
 
         endpoint = "/2018-06-01/runtime/init/error"
@@ -99,12 +109,14 @@ def post_init_error(self, error_response_data, error_type_override=None):
         )
 
     def restore_next(self):
+        """Restore next invocation."""
         import http
 
         endpoint = "/2018-06-01/runtime/restore/next"
         self.call_rapid("GET", endpoint, http.HTTPStatus.OK)
 
     def report_restore_error(self, restore_error_data):
+        """Report restore error."""
         import http
 
         endpoint = "/2018-06-01/runtime/restore/error"
@@ -114,6 +126,7 @@ def report_restore_error(self, restore_error_data):
         )
 
     def wait_next_invocation(self):
+        """Wait for next invocation."""
         # Calling runtime_client.next() from a separate thread unblocks the main thread,
         # which can then process signals.
         if self.use_thread_for_polling_next:
@@ -145,6 +158,7 @@ def wait_next_invocation(self):
     def post_invocation_result(
         self, invoke_id, result_data, content_type="application/json"
     ):
+        """Post invocation result."""
         runtime_client.post_invocation_result(
             invoke_id,
             (
@@ -156,6 +170,7 @@ def post_invocation_result(
         )
 
     def post_invocation_error(self, invoke_id, error_response_data, xray_fault):
+        """Post invocation error."""
         max_header_size = 1024 * 1024  # 1MiB
         xray_fault = xray_fault if len(xray_fault.encode()) < max_header_size else ""
         runtime_client.post_error(invoke_id, error_response_data, xray_fault)
diff --git a/awslambdaric/lambda_runtime_exception.py b/awslambdaric/lambda_runtime_exception.py
index 3ea5b29..3c2e41a 100644
--- a/awslambdaric/lambda_runtime_exception.py
+++ b/awslambdaric/lambda_runtime_exception.py
@@ -1,9 +1,9 @@
-"""
-Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-"""
+"""Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved."""
 
 
 class FaultException(Exception):
+    """Exception class for Lambda runtime faults."""
+
     MARSHAL_ERROR = "Runtime.MarshalError"
     UNMARSHAL_ERROR = "Runtime.UnmarshalError"
     USER_CODE_SYNTAX_ERROR = "Runtime.UserCodeSyntaxError"
@@ -17,6 +17,7 @@ class FaultException(Exception):
     LAMBDA_RUNTIME_CLIENT_ERROR = "Runtime.LambdaRuntimeClientError"
 
     def __init__(self, exception_type, msg, trace=None):
+        """Initialize FaultException."""
         self.msg = msg
         self.exception_type = exception_type
         self.trace = trace
diff --git a/awslambdaric/lambda_runtime_hooks_runner.py b/awslambdaric/lambda_runtime_hooks_runner.py
index 8aee181..ab67e63 100644
--- a/awslambdaric/lambda_runtime_hooks_runner.py
+++ b/awslambdaric/lambda_runtime_hooks_runner.py
@@ -1,10 +1,14 @@
-# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
-# SPDX-License-Identifier: Apache-2.0
+"""Lambda runtime hooks runner.
+
+Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+SPDX-License-Identifier: Apache-2.0
+"""
 
 from snapshot_restore_py import get_before_snapshot, get_after_restore
 
 
 def run_before_snapshot():
+    """Run before snapshot hooks."""
     before_snapshot_callables = get_before_snapshot()
     while before_snapshot_callables:
         # Using pop as before checkpoint callables are executed in the reverse order of their registration
@@ -13,6 +17,7 @@ def run_before_snapshot():
 
 
 def run_after_restore():
+    """Run after restore hooks."""
     after_restore_callables = get_after_restore()
     for func, args, kwargs in after_restore_callables:
         func(*args, **kwargs)
diff --git a/awslambdaric/lambda_runtime_log_utils.py b/awslambdaric/lambda_runtime_log_utils.py
index 9ddbcfb..93a1d63 100644
--- a/awslambdaric/lambda_runtime_log_utils.py
+++ b/awslambdaric/lambda_runtime_log_utils.py
@@ -1,6 +1,4 @@
-"""
-Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-"""
+"""Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved."""
 
 import json
 import logging
@@ -36,11 +34,14 @@
 
 
 class LogFormat(IntEnum):
+    """Log format enumeration."""
+
     JSON = 0b0
     TEXT = 0b1
 
     @classmethod
     def from_str(cls, value: str):
+        """Convert string to LogFormat."""
         if value and value.upper() == "JSON":
             return cls.JSON.value
         return cls.TEXT.value
@@ -77,7 +78,10 @@ def _format_log_level(record: logging.LogRecord) -> int:
 
 
 class JsonFormatter(logging.Formatter):
+    """JSON formatter for Lambda logs."""
+
     def __init__(self):
+        """Initialize the JSON formatter."""
         super().__init__(datefmt=_DATETIME_FORMAT)
 
     @staticmethod
@@ -108,6 +112,7 @@ def __format_location(record: logging.LogRecord):
         return f"{record.pathname}:{record.funcName}:{record.lineno}"
 
     def format(self, record: logging.LogRecord) -> str:
+        """Format log record as JSON."""
         record.levelno = _format_log_level(record)
         record.levelname = logging.getLevelName(record.levelno)
         record._frame_type = _JSON_FRAME_TYPES.get(
diff --git a/awslambdaric/lambda_runtime_marshaller.py b/awslambdaric/lambda_runtime_marshaller.py
index 4256066..fe0dd8f 100644
--- a/awslambdaric/lambda_runtime_marshaller.py
+++ b/awslambdaric/lambda_runtime_marshaller.py
@@ -1,6 +1,4 @@
-"""
-Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-"""
+"""Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved."""
 
 import decimal
 import math
@@ -14,7 +12,10 @@
 # to get the good parts of Decimal support, we'll special-case NaN decimals and otherwise duplicate the encoding for decimals the same way simplejson does
 # We also set 'ensure_ascii=False' so that the encoded json contains unicode characters instead of unicode escape sequences
 class Encoder(json.JSONEncoder):
+    """Custom JSON encoder for Lambda responses."""
+
     def __init__(self):
+        """Initialize the encoder."""
         if os.environ.get("AWS_EXECUTION_ENV") in {
             "AWS_Lambda_python3.12",
             "AWS_Lambda_python3.13",
@@ -24,6 +25,7 @@ def __init__(self):
             super().__init__(use_decimal=False, allow_nan=True)
 
     def default(self, obj):
+        """Handle special object types during encoding."""
         if isinstance(obj, decimal.Decimal):
             if obj.is_nan():
                 return math.nan
@@ -32,14 +34,19 @@ def default(self, obj):
 
 
 def to_json(obj):
+    """Convert object to JSON string."""
     return Encoder().encode(obj)
 
 
 class LambdaMarshaller:
+    """Marshaller for Lambda requests and responses."""
+
     def __init__(self):
+        """Initialize the marshaller."""
         self.jsonEncoder = Encoder()
 
     def unmarshal_request(self, request, content_type="application/json"):
+        """Unmarshal incoming request."""
         if content_type != "application/json":
             return request
         try:
@@ -52,6 +59,7 @@ def unmarshal_request(self, request, content_type="application/json"):
             )
 
     def marshal_response(self, response):
+        """Marshal response for Lambda."""
         if isinstance(response, bytes):
             return response, "application/unknown"
 
diff --git a/pyproject.toml b/pyproject.toml
index 7c0c05b..0421ecb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,8 +29,7 @@ lint = "scripts.dev:lint"
 format = "scripts.dev:format_code"
 clean = "scripts.dev:clean"
 build = "scripts.dev:build"
-build-container = "scripts.dev:build_container"
-test-rie = "scripts.dev:test_rie"
+local-test = "scripts.dev:local_test"
 
 [build-system]
 requires = ["poetry-core>=2.0.0,<3.0.0", "setuptools>=68", "wheel"]

From 3ac0e0aa4355f0b0930c82d276115970933a9536 Mon Sep 17 00:00:00 2001
From: AntoniaSzecsi 
Date: Wed, 30 Jul 2025 16:36:57 +0100
Subject: [PATCH 5/7] Fix build and test invocations of RIE

---
 pyproject.toml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 0421ecb..7c0c05b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -29,7 +29,8 @@ lint = "scripts.dev:lint"
 format = "scripts.dev:format_code"
 clean = "scripts.dev:clean"
 build = "scripts.dev:build"
-local-test = "scripts.dev:local_test"
+build-container = "scripts.dev:build_container"
+test-rie = "scripts.dev:test_rie"
 
 [build-system]
 requires = ["poetry-core>=2.0.0,<3.0.0", "setuptools>=68", "wheel"]

From be21af817f3c77bb3386b59e8fa9591ce5e04feb Mon Sep 17 00:00:00 2001
From: AntoniaSzecsi 
Date: Wed, 30 Jul 2025 16:43:30 +0100
Subject: [PATCH 6/7] Format file according to linter requirements

---
 awslambdaric/bootstrap.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py
index 40463c2..9bf1382 100644
--- a/awslambdaric/bootstrap.py
+++ b/awslambdaric/bootstrap.py
@@ -415,7 +415,7 @@ def log_error(self, message_lines):
 
 class FramedTelemetryLogSink(object):
     """FramedTelemetryLogSink implements the logging contract between runtimes and the platform.
-    
+
     It implements a simple framing protocol so message boundaries can be determined. Each frame can be visualized as follows:
      
     {@code

From c8f869d2935e24e8700811104e12cb4a2ae0f6eb Mon Sep 17 00:00:00 2001
From: AntoniaSzecsi 
Date: Thu, 31 Jul 2025 14:54:23 +0100
Subject: [PATCH 7/7] Remove ignore rules and move docstring logic to dev file

---
 Makefile       | 38 +++++++++++++++++++-------------------
 pyproject.toml | 10 ----------
 scripts/dev.py |  8 +++++++-
 3 files changed, 26 insertions(+), 30 deletions(-)

diff --git a/Makefile b/Makefile
index 7c27881..694b1c3 100644
--- a/Makefile
+++ b/Makefile
@@ -47,11 +47,15 @@ format:
 check-format:
 	poetry run ruff format --check awslambdaric/ tests/
 
+.PHONY: check-docstr
+check-docstr:
+	python3 scripts/dev.py check-docstr
+
 .PHONY: dev
 dev: init test
 
 .PHONY: pr
-pr: init check-format check-security dev
+pr: init check-format check-annotations check-types check-type-usage check-security dev
 
 .PHONY: codebuild
 codebuild: setup-codebuild-agent
@@ -65,27 +69,23 @@ build-container:
 test-rie:
 	./scripts/test-rie.sh
 
-.PHONY: check-docstr
-check-docstr:
-	poetry run ruff check --select D --ignore D105 awslambdaric/
-
 define HELP_MESSAGE
 
 Usage: $ make [TARGETS]
 
 TARGETS
-	check-security	Run bandit to find security issues.
-	format       	Run black to automatically update your code to match formatting.
-	build       	Build the package using scripts/dev.py.
-	clean       	Cleans the working directory using scripts/dev.py.
-	dev         	Run all development tests using scripts/dev.py.
-	init        	Install dependencies via scripts/dev.py.
-	build-container	Build awslambdaric wheel in isolated container.
-	test-rie    	Test with RIE using pre-built wheel (run build-container first).
-	pr          	Perform all checks before submitting a Pull Request.
-	test        	Run unit tests using scripts/dev.py.
-	lint        	Run all linters via scripts/dev.py.
-	test-smoke  	Run smoke tests inside Docker.
-	test-integ  	Run all integration tests.
-	check-docstr	Check docstrings in project using ruff format check.
+	check-security		Run bandit to find security issues.
+	check-docstr		Check docstrings in project using ruff format check.
+	format       		Run ruff to automatically format your code.
+	build       		Build the package using scripts/dev.py.
+	clean       		Cleans the working directory using scripts/dev.py.
+	dev         		Run all development tests using scripts/dev.py.
+	init        		Install dependencies via scripts/dev.py.
+	build-container		Build awslambdaric wheel in isolated container.
+	test-rie    		Test with RIE using pre-built wheel (run build-container first).
+	pr          		Perform all checks before submitting a Pull Request.
+	test        		Run unit tests using scripts/dev.py.
+	lint        		Run all linters via scripts/dev.py.
+	test-smoke  		Run smoke tests inside Docker.
+	test-integ  		Run all integration tests.
 endef
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
index 7c0c05b..0007d8f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,15 +51,5 @@ select = [
     "C90", # mccabe complexity
 ]
 
-# Ignore rules that are too strict for existing codebases
-ignore = [
-    "E501",    # Line too long (handled by formatter)
-    "PLR0913", # Too many arguments
-    "E722",    # Bare except 
-    "PLW0603", # Global statement 
-    "UP031",   # % formatting vs f-strings
-    "E402",    # Module import not at top
-]
-
 [tool.ruff.format]
 quote-style = "double"
diff --git a/scripts/dev.py b/scripts/dev.py
index 636d4b8..e5e91b6 100644
--- a/scripts/dev.py
+++ b/scripts/dev.py
@@ -71,11 +71,16 @@ def test_rie():
     print("Testing with RIE using pre-built wheel")
     run(["./scripts/test-rie.sh"])
 
+def check_docstr():
+    print("Checking docstrings")
+    run(["poetry", "run", "ruff", "check", "--select", "D", "--ignore", "D105", "awslambdaric/"])
+
 
 def main():
     parser = argparse.ArgumentParser(description="Development scripts")
     parser.add_argument("command", choices=[
-        "init", "test", "lint", "format", "clean", "build", "build-container", "test-rie"
+        "init", "test", "lint", "format", "clean", "build", "build-container", "test-rie",
+        "check-docstr"
     ])
     
     args = parser.parse_args()
@@ -89,6 +94,7 @@ def main():
         "build": build,
         "build-container": build_container,
         "test-rie": test_rie,
+        "check-docstr": check_docstr,
     }
     
     command_map[args.command]()