diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..61b6cbd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +default_language_version: + python: python3.11 # set for project python version +repos: + - repo: local + hooks: + - id: black-apply + name: black-apply + entry: pipenv run black + language: system + pass_filenames: true + types: ["python"] + - id: mypy + name: mypy + entry: pipenv run mypy + exclude: tests/ + language: system + pass_filenames: true + types: ["python"] + - id: ruff-apply + name: ruff-apply + entry: pipenv run ruff check --fix + language: system + pass_filenames: true + types: ["python"] + - id: safety + name: safety + entry: pipenv check + language: system + pass_filenames: false \ No newline at end of file diff --git a/Makefile b/Makefile index 6860812..4193fb0 100644 --- a/Makefile +++ b/Makefile @@ -1,51 +1,60 @@ -### This is the Terraform-generated header for the timdex-pipeline-lambads Makefile ### +# ---- This is the Terraform-generated header for the timdex-pipeline-lambads Makefile ---- ## SHELL=/bin/bash DATETIME:=$(shell date -u +%Y%m%dT%H%M%SZ) -### This is the Terraform-generated header for alma-webhook-lambdas-dev ### +## ---- This is the Terraform-generated header for alma-webhook-lambdas-dev ---- ## ECR_NAME_DEV:=alma-webhook-lambdas-dev ECR_URL_DEV:=222053980223.dkr.ecr.us-east-1.amazonaws.com/alma-webhook-lambdas-dev FUNCTION_DEV:=alma-webhook-lambdas-dev -### End of Terraform-generated header ### +## ---- End of Terraform-generated header ---- ## help: ## Print this message @awk 'BEGIN { FS = ":.*##"; print "Usage: make \n\nTargets:" } \ /^[-_[:alpha:]]+:.?*##/ { printf " %-15s%s\n", $$1, $$2 }' $(MAKEFILE_LIST) -### Dependency commands ### -install: ## Install dependencies +## ---- Dependency commands ---- ## +install: # install python dependencies pipenv install --dev + pipenv run pre-commit install -update: install ## Update all Python dependencies +update: install # update all python dependencies pipenv clean pipenv update --dev - pipenv requirements -### Test commands ### -test: ## Run tests and print a coverage report +## ---- Test commands ---- ## +test: # run tests and print coverage report pipenv run coverage run --source=lambdas -m pytest -vv pipenv run coverage report -m coveralls: test pipenv run coverage lcov -o ./coverage/lcov.info -### Lint commands ### -lint: bandit black flake8 isort mypy ## Lint the repo +## ---- Code quality and safety commands ### -bandit: - pipenv run bandit -r lambdas +# linting commands +lint: black mypy ruff safety black: pipenv run black --check --diff . -flake8: - pipenv run flake8 . +mypy: + pipenv run mypy lambdas -v -isort: - pipenv run isort . --diff +ruff: + pipenv run ruff check . -mypy: - pipenv run mypy lambdas +safety: + pipenv check + pipenv verify + +# apply changes to resolve any linting errors +lint-apply: black-apply ruff-apply + +black-apply: + pipenv run black . + +ruff-apply: + pipenv run ruff check --fix . ### Terraform-generated Developer Deploy Commands for Dev environment ### dist-dev: ## Build docker container (intended for developer-based manual build) diff --git a/Pipfile b/Pipfile index c894851..4c941ed 100644 --- a/Pipfile +++ b/Pipfile @@ -9,15 +9,15 @@ requests = "*" sentry-sdk = "*" [dev-packages] -bandit = "*" black = "*" +boto3-stubs = {extras = ["essential"], version = "*"} coverage = "*" -flake8 = "*" freezegun = "*" -isort = "*" mypy = "*" +pre-commit = "*" pytest = "*" requests-mock = "*" +ruff = "*" types-requests = "*" typing-extensions = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 7679979..e45e28e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ee5c9d314a3e49da83372730019a5367a30246c613a89871959a67a5149b26bf" + "sha256": "9b00affc9d6ad2e5c99f2c6468d933672d146071ed0d2132c0081db5552b3d1c" }, "pipfile-spec": 6, "requires": { @@ -18,19 +18,19 @@ "default": { "boto3": { "hashes": [ - "sha256:63619ffa44bc7f799b525c86d73bdb7f7a70994942bbff78253585bf64084e6e", - "sha256:a15841c7d04f87c63c9f2587b2b48198bec04d307d7b9950cbe4a021f845a5ba" + "sha256:8da9621931291b6c261fdaae465f05737c16519b9667d8463181cb8b88444572", + "sha256:a336cf53a6d86ee6d27b2f6d8b78ec9b320209127e5126359881bbd68f33d0b9" ], "index": "pypi", - "version": "==1.28.26" + "version": "==1.28.27" }, "botocore": { "hashes": [ - "sha256:74d1c26144915312004a9f0232cdbe08946dfec9fc7dcd854456d2b73be9bfd9", - "sha256:e68a50ba76425ede8693fdf1f95b8411e283bc7619c03d7eb666db9f1de48153" + "sha256:13af1588023750c9bc66d202bb5a934c9412a7dc52587532264ab725c42c2c50", + "sha256:739d09e13751e3b9b0f341b5ffe5bf8d0452b8769d435c4084ee88739d42b7f7" ], "markers": "python_version >= '3.7'", - "version": "==1.31.26" + "version": "==1.31.27" }, "certifi": { "hashes": [ @@ -142,7 +142,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.2" }, "requests": { @@ -155,11 +155,11 @@ }, "s3transfer": { "hashes": [ - "sha256:3c0da2d074bf35d6870ef157158641178a4204a6e689e82546083e31e0311346", - "sha256:640bb492711f4c0c0905e1f62b6aaeb771881935ad27884852411f8e9cacbca9" + "sha256:b014be3a8a2aab98cfe1abc7229cc5a9a0cf05eb9c1f2b86b230fd8df3f78084", + "sha256:cab66d3380cca3e70939ef2255d01cd8aece6a4907a9528740f668c4b0611861" ], "markers": "python_version >= '3.7'", - "version": "==0.6.1" + "version": "==0.6.2" }, "sentry-sdk": { "hashes": [ @@ -174,7 +174,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, "urllib3": { @@ -187,14 +187,6 @@ } }, "develop": { - "bandit": { - "hashes": [ - "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549", - "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e" - ], - "index": "pypi", - "version": "==1.7.5" - }, "black": { "hashes": [ "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3", @@ -223,6 +215,25 @@ "index": "pypi", "version": "==23.7.0" }, + "boto3-stubs": { + "extras": [ + "essential" + ], + "hashes": [ + "sha256:066b6e751a16179fd4f528794a85eed6469f332fb7e71fc8cee070ce58a999cc", + "sha256:df9772c4b803600ad26ed15e75c6d915c747ed55e23e772c181b4ea36627108d" + ], + "index": "pypi", + "version": "==1.28.27" + }, + "botocore-stubs": { + "hashes": [ + "sha256:3c7d316ce756fd3a3726fca98f1acc2845c3f76928ed026c59e832f2a18028bb", + "sha256:a70662d238fa30a27960886eef03d96d14f103a408b0decddd7e351d88f7d4a3" + ], + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==1.31.27" + }, "certifi": { "hashes": [ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", @@ -231,6 +242,14 @@ "markers": "python_version >= '3.6'", "version": "==2023.7.22" }, + "cfgv": { + "hashes": [ + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, "charset-normalizer": { "hashes": [ "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", @@ -378,13 +397,20 @@ "index": "pypi", "version": "==7.3.0" }, - "flake8": { + "distlib": { "hashes": [ - "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23", - "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5" + "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057", + "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8" ], - "index": "pypi", - "version": "==6.1.0" + "version": "==0.3.7" + }, + "filelock": { + "hashes": [ + "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81", + "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec" + ], + "markers": "python_version >= '3.7'", + "version": "==3.12.2" }, "freezegun": { "hashes": [ @@ -394,21 +420,13 @@ "index": "pypi", "version": "==1.2.2" }, - "gitdb": { - "hashes": [ - "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a", - "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7" - ], - "markers": "python_version >= '3.7'", - "version": "==4.0.10" - }, - "gitpython": { + "identify": { "hashes": [ - "sha256:8d9b8cb1e80b9735e8717c9362079d3ce4c6e5ddeebedd0361b228c3a67a62f6", - "sha256:e3d59b1c2c6ebb9dfa7a184daf3b6dd4914237e7488a1730a6d8f6f5d0b4187f" + "sha256:7243800bce2f58404ed41b7c002e53d4d22bcf3ae1b7900c2d7aefd95394bf7f", + "sha256:c22a8ead0d4ca11f1edd6c9418c3220669b3b7533ada0a0ffa6cc0ef85cf9b54" ], - "markers": "python_version >= '3.7'", - "version": "==3.1.32" + "markers": "python_version >= '3.8'", + "version": "==2.5.26" }, "idna": { "hashes": [ @@ -426,38 +444,6 @@ "markers": "python_version >= '3.7'", "version": "==2.0.0" }, - "isort": { - "hashes": [ - "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", - "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" - ], - "index": "pypi", - "version": "==5.12.0" - }, - "markdown-it-py": { - "hashes": [ - "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", - "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" - ], - "markers": "python_version >= '3.8'", - "version": "==3.0.0" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "mdurl": { - "hashes": [ - "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" - ], - "markers": "python_version >= '3.7'", - "version": "==0.1.2" - }, "mypy": { "hashes": [ "sha256:1fe816e26e676c1311b9e04fd576543b873576d39439f7c24c8e5c7728391ecf", @@ -486,6 +472,55 @@ "index": "pypi", "version": "==1.5.0" }, + "mypy-boto3-cloudformation": { + "hashes": [ + "sha256:aadf78eb2f2e3b2e83a4844a80d0c5d0d72ad11c453a11efdd28b0c309b05bf6", + "sha256:efb08a2a6d7c744d0d8d60f04514c531355aa7972b53f025d9e08e3adf3a5504" + ], + "version": "==1.28.19" + }, + "mypy-boto3-dynamodb": { + "hashes": [ + "sha256:218f7bcb04010058aea5a735d52b87c4f70e8c5feb44e64ab6baf377ebb4e22a", + "sha256:b6786cf953e65293ec25c791e7efcd8ededceb6bda2e04910785b0f62584417d" + ], + "version": "==1.28.27" + }, + "mypy-boto3-ec2": { + "hashes": [ + "sha256:6ac52644ffb8b32ec52b19d61fc081e6b8a5b61299e6bc66357ca97eca62df21", + "sha256:fb76dc2462abb68d11b6330d4727e0d052ae65797c81844a5709b11d2eb9cf1b" + ], + "version": "==1.28.27" + }, + "mypy-boto3-lambda": { + "hashes": [ + "sha256:88582f8ca71bd7a6bbcf8b05155476f0a9dea79630a4da36d367482925241710", + "sha256:955b7702f02f2037ba4c058f6dcebfcce50090ac13c9d031a0052fa9136ec59e" + ], + "version": "==1.28.19" + }, + "mypy-boto3-rds": { + "hashes": [ + "sha256:1b1d2e89894b34d9ea37c44591d4bd7d63f802d30ac18a02a3607f596ba4b06a", + "sha256:d280b73e3045c9fc1dbbd02ed8b0abeb1219c3beb169a863bc0d4c2a2ebe568c" + ], + "version": "==1.28.19" + }, + "mypy-boto3-s3": { + "hashes": [ + "sha256:f1094344f68d1ffe2b998404e2e4ff9aa4239438692187fa83ad7b734739991c", + "sha256:f4fdefbfe084c92a6b3d000689e61ab12a985a72b07c5ff157f8a66bcbdb83ba" + ], + "version": "==1.28.27" + }, + "mypy-boto3-sqs": { + "hashes": [ + "sha256:43b3a07fac551334d64573c678d4a1657bb05b3e8e9f8a4f877dfccfa750b3c1", + "sha256:cc3977abc810570a5c7b95907db38889db5c7012a39bb90f2c940aca6da92181" + ], + "version": "==1.28.19" + }, "mypy-extensions": { "hashes": [ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", @@ -494,6 +529,14 @@ "markers": "python_version >= '3.5'", "version": "==1.0.0" }, + "nodeenv": { + "hashes": [ + "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2", + "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==1.8.0" + }, "packaging": { "hashes": [ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", @@ -510,14 +553,6 @@ "markers": "python_version >= '3.7'", "version": "==0.11.2" }, - "pbr": { - "hashes": [ - "sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b", - "sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3" - ], - "markers": "python_version >= '2.6'", - "version": "==5.11.1" - }, "platformdirs": { "hashes": [ "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d", @@ -534,29 +569,13 @@ "markers": "python_version >= '3.7'", "version": "==1.2.0" }, - "pycodestyle": { - "hashes": [ - "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0", - "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8" - ], - "markers": "python_version >= '3.8'", - "version": "==2.11.0" - }, - "pyflakes": { - "hashes": [ - "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774", - "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc" - ], - "markers": "python_version >= '3.8'", - "version": "==3.1.0" - }, - "pygments": { + "pre-commit": { "hashes": [ - "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", - "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" + "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb", + "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023" ], - "markers": "python_version >= '3.7'", - "version": "==2.16.1" + "index": "pypi", + "version": "==3.3.3" }, "pytest": { "hashes": [ @@ -571,7 +590,7 @@ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.8.2" }, "pyyaml": { @@ -636,37 +655,52 @@ "index": "pypi", "version": "==1.11.0" }, - "rich": { + "ruff": { + "hashes": [ + "sha256:0a3218458b140ea794da72b20ea09cbe13c4c1cdb7ac35e797370354628f4c05", + "sha256:1292cfc764eeec3cde35b3a31eae3f661d86418b5e220f5d5dba1c27a6eccbb6", + "sha256:1d1f7096038961d8bc3b956ee69d73826843eb5b39a5fa4ee717ed473ed69c95", + "sha256:735cd62fccc577032a367c31f6a9de7c1eb4c01fa9a2e60775067f44f3fc3091", + "sha256:88295fd649d0aa1f1271441df75bf06266a199497afd239fd392abcfd75acd7e", + "sha256:8b949084941232e2c27f8d12c78c5a6a010927d712ecff17231ee1a8371c205b", + "sha256:a3930d66b35e4dc96197422381dff2a4e965e9278b5533e71ae8474ef202fab0", + "sha256:b2fe880cff13fffd735387efbcad54ba0ff1272bceea07f86852a33ca71276f4", + "sha256:b3660b85a9d84162a055f1add334623ae2d8022a84dcd605d61c30a57b436c32", + "sha256:bcaf85907fc905d838f46490ee15f04031927bbea44c478394b0bfdeadc27362", + "sha256:c4c79ae3308e308b94635cd57a369d1e6f146d85019da2fbc63f55da183ee29b", + "sha256:d1d098ea74d0ce31478765d1f8b4fbdbba2efc532397b5c5e8e5ea0c13d7e5ae", + "sha256:d29dfbe314e1131aa53df213fdfea7ee874dd96ea0dd1471093d93b59498384d", + "sha256:e37e086f4d623c05cd45a6fe5006e77a2b37d57773aad96b7802a6b8ecf9c910", + "sha256:ebd3cc55cd499d326aac17a331deaea29bea206e01c08862f9b5c6e93d77a491", + "sha256:f67ed868d79fbcc61ad0fa034fe6eed2e8d438d32abce9c04b7c4c1464b2cf8e", + "sha256:f86b2b1e7033c00de45cc176cf26778650fb8804073a0495aca2f674797becbb" + ], + "index": "pypi", + "version": "==0.0.284" + }, + "setuptools": { "hashes": [ - "sha256:146a90b3b6b47cac4a73c12866a499e9817426423f57c5a66949c086191a8808", - "sha256:fb9d6c0a0f643c99eed3875b5377a184132ba9be4d61516a55273d3554d75a39" + "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91", + "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715" ], - "markers": "python_full_version >= '3.7.0'", - "version": "==13.5.2" + "markers": "python_version >= '3.8'", + "version": "==68.1.0" }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, - "smmap": { + "types-awscrt": { "hashes": [ - "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94", - "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936" + "sha256:0e31d7ba44e1898af37d224b94d28ffaef19baf89bb18ea2599de9ac0910a07f", + "sha256:eaef60422cf716b4ae216f164b74d679c82b0d9c53db380a37deb29ae5579b1b" ], - "markers": "python_version >= '3.6'", - "version": "==5.0.0" - }, - "stevedore": { - "hashes": [ - "sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d", - "sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c" - ], - "markers": "python_version >= '3.8'", - "version": "==5.1.0" + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==0.19.0" }, "types-requests": { "hashes": [ @@ -676,6 +710,14 @@ "index": "pypi", "version": "==2.31.0.2" }, + "types-s3transfer": { + "hashes": [ + "sha256:1068877b6e59be5226fa3006ae64371ac9d5bc590dfdbd9c66fd0a075d3254ac", + "sha256:4ba9b483796fdcd026aa162ee03bdcedd2bf7d08e9387c820dcdd158b0102057" + ], + "markers": "python_version >= '3.7' and python_version < '4.0'", + "version": "==0.6.2" + }, "types-urllib3": { "hashes": [ "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f", @@ -698,6 +740,14 @@ ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==1.26.16" + }, + "virtualenv": { + "hashes": [ + "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02", + "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc" + ], + "markers": "python_version >= '3.7'", + "version": "==20.24.3" } } } diff --git a/lambdas/helpers.py b/lambdas/helpers.py index 1c194bc..f7a58cf 100644 --- a/lambdas/helpers.py +++ b/lambdas/helpers.py @@ -49,8 +49,7 @@ def generate_signature(message_body: dict) -> str: message_hash = hmac.digest( secret.encode(), msg=message_string.encode(), digest="sha256" ) - signature = base64.b64encode(message_hash).decode() - return signature + return base64.b64encode(message_hash).decode() def send_get_to_lambda_function_url(challenge_phrase: str) -> str: @@ -64,7 +63,5 @@ def send_get_to_lambda_function_url(challenge_phrase: str) -> str: def send_post_to_lambda_function_url(message_body: dict) -> str: function_url = os.environ["LAMBDA_FUNCTION_URL"] headers = {"x-exl-signature": generate_signature(message_body)} - response = requests.post( - function_url, headers=headers, json=message_body, timeout=30 - ) + response = requests.post(function_url, headers=headers, json=message_body, timeout=30) return response.text diff --git a/lambdas/ping.py b/lambdas/ping.py index de7617b..4e33ad4 100644 --- a/lambdas/ping.py +++ b/lambdas/ping.py @@ -1,2 +1,2 @@ -def lambda_handler(event: dict, context: object) -> str: +def lambda_handler(event: dict, context: object) -> str: # noqa: ARG001 return "pong" diff --git a/lambdas/webhook.py b/lambdas/webhook.py index f62e6dc..0434090 100644 --- a/lambdas/webhook.py +++ b/lambdas/webhook.py @@ -3,7 +3,7 @@ import json import logging import os -from datetime import datetime +from datetime import UTC, datetime import boto3 import sentry_sdk @@ -27,11 +27,12 @@ logger.info("No Sentry DSN found, exceptions will not be sent to Sentry") -def lambda_handler(event: dict, context: object) -> dict[str, object]: +def lambda_handler(event: dict, context: object) -> dict[str, object]: # noqa: ARG001 logger.debug(json.dumps(event)) if not os.getenv("WORKSPACE"): - raise RuntimeError("Required env variable WORKSPACE is not set") + unset_workspace_error_message = "Required env variable WORKSPACE is not set" + raise RuntimeError(unset_workspace_error_message) base_response = { "headers": {"Content-Type": "text/plain"}, @@ -78,9 +79,7 @@ def handle_get_request(event: dict) -> dict[str, object]: def handle_post_request(event: dict) -> dict[str, object]: if not valid_signature(event): - logger.warning( - "Invalid signature in POST request, returning 401 error response." - ) + logger.warning("Invalid signature in POST request, returning 401 error response.") return { "statusCode": 401, "body": "Unable to validate signature. Has the webhook challenge secret " @@ -194,20 +193,15 @@ def count_exported_records(counter: list[dict]) -> int: "label.updated.records", "label.deleted.records", ] - count = sum( - [ - int(c["value"]) - for c in counter - if c["type"]["value"] in exported_record_types - ] + return sum( + [int(c["value"]) for c in counter if c["type"]["value"] in exported_record_types] ) - return count def generate_step_function_input( job_date: str, job_name: str, job_type: str ) -> tuple[str, str]: - timestamp = datetime.now().strftime("%Y-%m-%dt%H-%M-%S") + timestamp = datetime.now(tz=UTC).strftime("%Y-%m-%dt%H-%M-%S") if job_type == "PPOD": result = { "filename-prefix": "exlibris/pod/POD_ALMA_EXPORT_" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e7e49cc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[tool.black] +line-length = 90 + +[tool.mypy] +disallow_untyped_calls = true +disallow_untyped_defs = true +exclude = ["tests/"] + +[tool.pytest.ini_options] +log_level = "INFO" + +[tool.ruff] +target-version = "py311" +select = ["ALL", "PT"] + +ignore = [ + # default + "ANN101", + "ANN102", + "COM812", + "D107", + "N812", + "PTH", + + # project-specific + "D100", + "D103", + "D104" +] + +# allow autofix behavior for specified rules +fixable = ["E", "F", "I", "Q"] + +# set max line length +line-length = 90 + +# enumerate all fixed violations +show-fixes = true + +[tool.ruff.flake8-annotations] +mypy-init-return = true + +[tool.ruff.flake8-pytest-style] +fixture-parentheses = false + +[tool.ruff.per-file-ignores] +"tests/**/*" = [ + "ANN", + "ARG001", + "S101", + "S105" +] + +[tool.ruff.pycodestyle] +max-doc-length = 90 + +[tool.ruff.pydocstyle] +convention = "google" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 513bdf9..0000000 --- a/setup.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[flake8] -max-line-length = 90 -extend-ignore = E203 - -[isort] -profile = black - -[mypy] - -[mypy-boto3.*] -ignore_missing_imports = True - -[tool:pytest] -log_level = INFO diff --git a/tests/conftest.py b/tests/conftest.py index 1b3e5b3..d25f5f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ -import datetime import os import urllib +from datetime import UTC, datetime import botocore.session import pytest @@ -11,54 +11,50 @@ @pytest.fixture(autouse=True) -def test_env(): - os.environ = { - "AWS_ACCESS_KEY_ID": "testing", - "AWS_DEFAULT_REGION": "us-east-1", - "AWS_SECRET_ACCESS_KEY": "testing", - "AWS_SECURITY_TOKEN": "testing", - "AWS_SESSION_TOKEN": "testing", - "ALMA_CHALLENGE_SECRET": "itsasecret", - "ALMA_POD_EXPORT_JOB_NAME": "PPOD Export", - "ALMA_TIMDEX_EXPORT_JOB_NAME_PREFIX": "TIMDEX Export", - "LAMBDA_FUNCTION_URL": "http://example.com/lambda", - "PPOD_STATE_MACHINE_ARN": "arn:aws:states:us-east-1:account:stateMachine:" - "ppod-test", - "TIMDEX_STATE_MACHINE_ARN": "arn:aws:states:us-east-1:account:stateMachine:" - "timdex-test", - "VALID_POD_EXPORT_DATE": "2022-05-23", - "WORKSPACE": "test", - } - return - - -@pytest.fixture() +def _test_env(): + os.environ["AWS_ACCESS_KEY_ID"] = "testing" + os.environ["AWS_DEFAULT_REGION"] = "us-east-1" + os.environ["AWS_SECRET_ACCESS_KEY"] = "testing" + os.environ["AWS_SECURITY_TOKEN"] = "testing" + os.environ["AWS_SESSION_TOKEN"] = "testing" + os.environ["ALMA_CHALLENGE_SECRET"] = "itsasecret" + os.environ["ALMA_POD_EXPORT_JOB_NAME"] = "PPOD Export" + os.environ["ALMA_TIMDEX_EXPORT_JOB_NAME_PREFIX"] = "TIMDEX Export" + os.environ["LAMBDA_FUNCTION_URL"] = "http://example.com/lambda" + os.environ[ + "PPOD_STATE_MACHINE_ARN" + ] = "arn:aws:states:us-east-1:account:stateMachine:ppod-test" + os.environ[ + "TIMDEX_STATE_MACHINE_ARN" + ] = "arn:aws:states:us-east-1:account:stateMachine:timdex-test" + os.environ["VALID_POD_EXPORT_DATE"] = "2022-05-23" + os.environ["WORKSPACE"] = "test" + + +@pytest.fixture def get_request(): - request_data = { + return { "queryStringParameters": {"challenge": "challenge-accepted"}, "requestContext": {"http": {"method": "GET"}}, } - return request_data -@pytest.fixture() +@pytest.fixture def post_request_invalid_signature(): - request_data = { + return { "headers": {"x-exl-signature": "thisiswrong"}, "requestContext": {"http": {"method": "POST"}}, "body": "The POST request body", } - return request_data -@pytest.fixture() +@pytest.fixture def post_request_valid_signature(): - request_data = { + return { "headers": {"x-exl-signature": "e9SHoXK4MZrSGqhglMK4w+/u1pjYn0bfTEYtcFqj7CE="}, "requestContext": {"http": {"method": "POST"}}, "body": "The POST request body", } - return request_data def get_request_callback(request, context): @@ -82,7 +78,7 @@ def post_request_callback(request, context): return response["body"] -@pytest.fixture() +@pytest.fixture def mocked_lambda_function_url(): with requests_mock.Mocker() as m: m.get("http://example.com/lambda", text=get_request_callback) @@ -90,14 +86,14 @@ def mocked_lambda_function_url(): yield m -@pytest.fixture() +@pytest.fixture def stubbed_ppod_sfn_client(): sfn = botocore.session.get_session().create_client( "stepfunctions", region_name="us-east-1" ) expected_response = { "executionArn": "arn:aws:states:us-east-1:account:execution:ppod-test:12345", - "startDate": datetime.datetime(2022, 5, 1), + "startDate": datetime(2022, 5, 1, tzinfo=UTC), } expected_params = { "stateMachineArn": "arn:aws:states:us-east-1:account:stateMachine:ppod-test", @@ -109,14 +105,14 @@ def stubbed_ppod_sfn_client(): yield sfn -@pytest.fixture() +@pytest.fixture def stubbed_timdex_sfn_client(): sfn = botocore.session.get_session().create_client( "stepfunctions", region_name="us-east-1" ) expected_response = { "executionArn": "arn:aws:states:us-east-1:account:execution:timdex-test:12345", - "startDate": datetime.datetime(2022, 5, 1), + "startDate": datetime(2022, 5, 1, tzinfo=UTC), } expected_params = { "stateMachineArn": "arn:aws:states:us-east-1:account:stateMachine:timdex-test", @@ -127,3 +123,22 @@ def stubbed_timdex_sfn_client(): with Stubber(sfn) as stubber: stubber.add_response("start_execution", expected_response, expected_params) yield sfn + + +d = { + "AWS_ACCESS_KEY_ID": "testing", + "AWS_DEFAULT_REGION": "us-east-1", + "AWS_SECRET_ACCESS_KEY": "testing", + "AWS_SECURITY_TOKEN": "testing", + "AWS_SESSION_TOKEN": "testing", + "ALMA_CHALLENGE_SECRET": "itsasecret", + "ALMA_POD_EXPORT_JOB_NAME": "PPOD Export", + "ALMA_TIMDEX_EXPORT_JOB_NAME_PREFIX": "TIMDEX Export", + "LAMBDA_FUNCTION_URL": "http://example.com/lambda", + "PPOD_STATE_MACHINE_ARN": "arn:aws:states:us-east-1:account:stateMachine:" + "ppod-test", + "TIMDEX_STATE_MACHINE_ARN": "arn:aws:states:us-east-1:account:stateMachine:" + "timdex-test", + "VALID_POD_EXPORT_DATE": "2022-05-23", + "WORKSPACE": "test", +} diff --git a/tests/test_helpers.py b/tests/test_helpers.py index b61037d..b09fba3 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -14,9 +14,7 @@ def test_generate_signature_dict_body(): def test_generate_signature_string_body(): - assert ( - generate_signature("message") == "tp4YILPZGmIjcZLSaTa+3Ws+1BuNzeZI3byc7gcQ604=" - ) + assert generate_signature("message") == "tp4YILPZGmIjcZLSaTa+3Ws+1BuNzeZI3byc7gcQ604=" def test_send_get_to_lambda_function_url(mocked_lambda_function_url): diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 6f7da01..5534685 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -1,5 +1,5 @@ -import datetime import json +from datetime import UTC, datetime from importlib import reload from unittest.mock import patch @@ -19,8 +19,7 @@ def test_webhook_configures_sentry_if_dsn_present(caplog, monkeypatch): monkeypatch.setenv("SENTRY_DSN", "https://1234567890@00000.ingest.sentry.io/123456") reload(webhook) assert ( - "Sentry DSN found, exceptions will be sent to Sentry with env=test" - in caplog.text + "Sentry DSN found, exceptions will be sent to Sentry with env=test" in caplog.text ) @@ -315,9 +314,7 @@ def test_webhook_handles_post_request_timdex_export_job_success( "TIMDEX export from Alma completed successfully, initiating TIMDEX step " "function." in caplog.text ) - assert ( - "TIMDEX step function executed, returning 200 success response." in caplog.text - ) + assert "TIMDEX step function executed, returning 200 success response." in caplog.text def test_validate_missing_signature_returns_false(): @@ -425,7 +422,7 @@ def test_count_exported_records_with_records_exported(): "value": "0", }, ] - assert count_exported_records(counter) == 6 + assert count_exported_records(counter) == 6 # noqa: PLR2004 @freeze_time("2022-05-01") @@ -439,5 +436,5 @@ def test_execute_state_machine_success(stubbed_ppod_sfn_client): ) assert response == { "executionArn": "arn:aws:states:us-east-1:account:execution:ppod-test:12345", - "startDate": datetime.datetime(2022, 5, 1), + "startDate": datetime(2022, 5, 1, tzinfo=UTC), }