diff --git a/poetry.lock b/poetry.lock index 405be8852..269c8ac2f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -14,103 +14,103 @@ files = [ [[package]] name = "aiohttp" -version = "3.12.14" +version = "3.12.13" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906d5075b5ba0dd1c66fcaaf60eb09926a9fef3ca92d912d2a0bbdbecf8b1248"}, - {file = "aiohttp-3.12.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c875bf6fc2fd1a572aba0e02ef4e7a63694778c5646cdbda346ee24e630d30fb"}, - {file = "aiohttp-3.12.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbb284d15c6a45fab030740049d03c0ecd60edad9cd23b211d7e11d3be8d56fd"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e360381e02e1a05d36b223ecab7bc4a6e7b5ab15760022dc92589ee1d4238c"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:aaf90137b5e5d84a53632ad95ebee5c9e3e7468f0aab92ba3f608adcb914fa95"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e532a25e4a0a2685fa295a31acf65e027fbe2bea7a4b02cdfbbba8a064577663"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eab9762c4d1b08ae04a6c77474e6136da722e34fdc0e6d6eab5ee93ac29f35d1"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe53c3812b2899889a7fca763cdfaeee725f5be68ea89905e4275476ffd7e61"}, - {file = "aiohttp-3.12.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5760909b7080aa2ec1d320baee90d03b21745573780a072b66ce633eb77a8656"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:02fcd3f69051467bbaa7f84d7ec3267478c7df18d68b2e28279116e29d18d4f3"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dcd1172cd6794884c33e504d3da3c35648b8be9bfa946942d353b939d5f1288"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:224d0da41355b942b43ad08101b1b41ce633a654128ee07e36d75133443adcda"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e387668724f4d734e865c1776d841ed75b300ee61059aca0b05bce67061dcacc"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:dec9cde5b5a24171e0b0a4ca064b1414950904053fb77c707efd876a2da525d8"}, - {file = "aiohttp-3.12.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bbad68a2af4877cc103cd94af9160e45676fc6f0c14abb88e6e092b945c2c8e3"}, - {file = "aiohttp-3.12.14-cp310-cp310-win32.whl", hash = "sha256:ee580cb7c00bd857b3039ebca03c4448e84700dc1322f860cf7a500a6f62630c"}, - {file = "aiohttp-3.12.14-cp310-cp310-win_amd64.whl", hash = "sha256:cf4f05b8cea571e2ccc3ca744e35ead24992d90a72ca2cf7ab7a2efbac6716db"}, - {file = "aiohttp-3.12.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4552ff7b18bcec18b60a90c6982049cdb9dac1dba48cf00b97934a06ce2e597"}, - {file = "aiohttp-3.12.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8283f42181ff6ccbcf25acaae4e8ab2ff7e92b3ca4a4ced73b2c12d8cd971393"}, - {file = "aiohttp-3.12.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:040afa180ea514495aaff7ad34ec3d27826eaa5d19812730fe9e529b04bb2179"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b413c12f14c1149f0ffd890f4141a7471ba4b41234fe4fd4a0ff82b1dc299dbb"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d6f607ce2e1a93315414e3d448b831238f1874b9968e1195b06efaa5c87e245"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:565e70d03e924333004ed101599902bba09ebb14843c8ea39d657f037115201b"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4699979560728b168d5ab63c668a093c9570af2c7a78ea24ca5212c6cdc2b641"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5fdf6af93ec6c99bf800eba3af9a43d8bfd66dce920ac905c817ef4a712afe"}, - {file = "aiohttp-3.12.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ac76627c0b7ee0e80e871bde0d376a057916cb008a8f3ffc889570a838f5cc7"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:798204af1180885651b77bf03adc903743a86a39c7392c472891649610844635"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4f1205f97de92c37dd71cf2d5bcfb65fdaed3c255d246172cce729a8d849b4da"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:76ae6f1dd041f85065d9df77c6bc9c9703da9b5c018479d20262acc3df97d419"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a194ace7bc43ce765338ca2dfb5661489317db216ea7ea700b0332878b392cab"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:16260e8e03744a6fe3fcb05259eeab8e08342c4c33decf96a9dad9f1187275d0"}, - {file = "aiohttp-3.12.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c779e5ebbf0e2e15334ea404fcce54009dc069210164a244d2eac8352a44b28"}, - {file = "aiohttp-3.12.14-cp311-cp311-win32.whl", hash = "sha256:a289f50bf1bd5be227376c067927f78079a7bdeccf8daa6a9e65c38bae14324b"}, - {file = "aiohttp-3.12.14-cp311-cp311-win_amd64.whl", hash = "sha256:0b8a69acaf06b17e9c54151a6c956339cf46db4ff72b3ac28516d0f7068f4ced"}, - {file = "aiohttp-3.12.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a0ecbb32fc3e69bc25efcda7d28d38e987d007096cbbeed04f14a6662d0eee22"}, - {file = "aiohttp-3.12.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0400f0ca9bb3e0b02f6466421f253797f6384e9845820c8b05e976398ac1d81a"}, - {file = "aiohttp-3.12.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a56809fed4c8a830b5cae18454b7464e1529dbf66f71c4772e3cfa9cbec0a1ff"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f2e373276e4755691a963e5d11756d093e346119f0627c2d6518208483fb6d"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ca39e433630e9a16281125ef57ece6817afd1d54c9f1bf32e901f38f16035869"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c748b3f8b14c77720132b2510a7d9907a03c20ba80f469e58d5dfd90c079a1c"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a568abe1b15ce69d4cc37e23020720423f0728e3cb1f9bcd3f53420ec3bfe7"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9888e60c2c54eaf56704b17feb558c7ed6b7439bca1e07d4818ab878f2083660"}, - {file = "aiohttp-3.12.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3006a1dc579b9156de01e7916d38c63dc1ea0679b14627a37edf6151bc530088"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aa8ec5c15ab80e5501a26719eb48a55f3c567da45c6ea5bb78c52c036b2655c7"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:39b94e50959aa07844c7fe2206b9f75d63cc3ad1c648aaa755aa257f6f2498a9"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:04c11907492f416dad9885d503fbfc5dcb6768d90cad8639a771922d584609d3"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:88167bd9ab69bb46cee91bd9761db6dfd45b6e76a0438c7e884c3f8160ff21eb"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:791504763f25e8f9f251e4688195e8b455f8820274320204f7eafc467e609425"}, - {file = "aiohttp-3.12.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785b112346e435dd3a1a67f67713a3fe692d288542f1347ad255683f066d8e0"}, - {file = "aiohttp-3.12.14-cp312-cp312-win32.whl", hash = "sha256:15f5f4792c9c999a31d8decf444e79fcfd98497bf98e94284bf390a7bb8c1729"}, - {file = "aiohttp-3.12.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b66e1a182879f579b105a80d5c4bd448b91a57e8933564bf41665064796a338"}, - {file = "aiohttp-3.12.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3143a7893d94dc82bc409f7308bc10d60285a3cd831a68faf1aa0836c5c3c767"}, - {file = "aiohttp-3.12.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3d62ac3d506cef54b355bd34c2a7c230eb693880001dfcda0bf88b38f5d7af7e"}, - {file = "aiohttp-3.12.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48e43e075c6a438937c4de48ec30fa8ad8e6dfef122a038847456bfe7b947b63"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:077b4488411a9724cecc436cbc8c133e0d61e694995b8de51aaf351c7578949d"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d8c35632575653f297dcbc9546305b2c1133391089ab925a6a3706dfa775ccab"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b8ce87963f0035c6834b28f061df90cf525ff7c9b6283a8ac23acee6502afd4"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0a2cf66e32a2563bb0766eb24eae7e9a269ac0dc48db0aae90b575dc9583026"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdea089caf6d5cde975084a884c72d901e36ef9c2fd972c9f51efbbc64e96fbd"}, - {file = "aiohttp-3.12.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7865f27db67d49e81d463da64a59365ebd6b826e0e4847aa111056dcb9dc88"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ab5b38a6a39781d77713ad930cb5e7feea6f253de656a5f9f281a8f5931b086"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b3b15acee5c17e8848d90a4ebc27853f37077ba6aec4d8cb4dbbea56d156933"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4c972b0bdaac167c1e53e16a16101b17c6d0ed7eac178e653a07b9f7fad7151"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7442488b0039257a3bdbc55f7209587911f143fca11df9869578db6c26feeeb8"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f68d3067eecb64c5e9bab4a26aa11bd676f4c70eea9ef6536b0a4e490639add3"}, - {file = "aiohttp-3.12.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f88d3704c8b3d598a08ad17d06006cb1ca52a1182291f04979e305c8be6c9758"}, - {file = "aiohttp-3.12.14-cp313-cp313-win32.whl", hash = "sha256:a3c99ab19c7bf375c4ae3debd91ca5d394b98b6089a03231d4c580ef3c2ae4c5"}, - {file = "aiohttp-3.12.14-cp313-cp313-win_amd64.whl", hash = "sha256:3f8aad695e12edc9d571f878c62bedc91adf30c760c8632f09663e5f564f4baa"}, - {file = "aiohttp-3.12.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b8cc6b05e94d837bcd71c6531e2344e1ff0fb87abe4ad78a9261d67ef5d83eae"}, - {file = "aiohttp-3.12.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1dcb015ac6a3b8facd3677597edd5ff39d11d937456702f0bb2b762e390a21b"}, - {file = "aiohttp-3.12.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3779ed96105cd70ee5e85ca4f457adbce3d9ff33ec3d0ebcdf6c5727f26b21b3"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:717a0680729b4ebd7569c1dcd718c46b09b360745fd8eb12317abc74b14d14d0"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b5dd3a2ef7c7e968dbbac8f5574ebeac4d2b813b247e8cec28174a2ba3627170"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4710f77598c0092239bc12c1fcc278a444e16c7032d91babf5abbf7166463f7b"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f3e9f75ae842a6c22a195d4a127263dbf87cbab729829e0bd7857fb1672400b2"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f9c8d55d6802086edd188e3a7d85a77787e50d56ce3eb4757a3205fa4657922"}, - {file = "aiohttp-3.12.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79b29053ff3ad307880d94562cca80693c62062a098a5776ea8ef5ef4b28d140"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23e1332fff36bebd3183db0c7a547a1da9d3b4091509f6d818e098855f2f27d3"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a564188ce831fd110ea76bcc97085dd6c625b427db3f1dbb14ca4baa1447dcbc"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a7a1b4302f70bb3ec40ca86de82def532c97a80db49cac6a6700af0de41af5ee"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1b07ccef62950a2519f9bfc1e5b294de5dd84329f444ca0b329605ea787a3de5"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:938bd3ca6259e7e48b38d84f753d548bd863e0c222ed6ee6ace3fd6752768a84"}, - {file = "aiohttp-3.12.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8bc784302b6b9f163b54c4e93d7a6f09563bd01ff2b841b29ed3ac126e5040bf"}, - {file = "aiohttp-3.12.14-cp39-cp39-win32.whl", hash = "sha256:a3416f95961dd7d5393ecff99e3f41dc990fb72eda86c11f2a60308ac6dcd7a0"}, - {file = "aiohttp-3.12.14-cp39-cp39-win_amd64.whl", hash = "sha256:196858b8820d7f60578f8b47e5669b3195c21d8ab261e39b1d705346458f445f"}, - {file = "aiohttp-3.12.14.tar.gz", hash = "sha256:6e06e120e34d93100de448fd941522e11dafa78ef1a893c179901b7d66aa29f2"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0"}, + {file = "aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5"}, + {file = "aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40"}, + {file = "aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6"}, + {file = "aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad"}, + {file = "aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358"}, + {file = "aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc"}, + {file = "aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2"}, + {file = "aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3"}, + {file = "aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd"}, + {file = "aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347"}, + {file = "aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6"}, + {file = "aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a"}, + {file = "aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5"}, + {file = "aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf"}, + {file = "aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace"}, + {file = "aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103"}, + {file = "aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911"}, + {file = "aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3"}, + {file = "aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd"}, + {file = "aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:36f6c973e003dc9b0bb4e8492a643641ea8ef0e97ff7aaa5c0f53d68839357b4"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6cbfc73179bd67c229eb171e2e3745d2afd5c711ccd1e40a68b90427f282eab1"}, + {file = "aiohttp-3.12.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1e8b27b2d414f7e3205aa23bb4a692e935ef877e3a71f40d1884f6e04fd7fa74"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eabded0c2b2ef56243289112c48556c395d70150ce4220d9008e6b4b3dd15690"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:003038e83f1a3ff97409999995ec02fe3008a1d675478949643281141f54751d"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b6f46613031dbc92bdcaad9c4c22c7209236ec501f9c0c5f5f0b6a689bf50f3"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c332c6bb04650d59fb94ed96491f43812549a3ba6e7a16a218e612f99f04145e"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fea41a2c931fb582cb15dc86a3037329e7b941df52b487a9f8b5aa960153cbd"}, + {file = "aiohttp-3.12.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:846104f45d18fb390efd9b422b27d8f3cf8853f1218c537f36e71a385758c896"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d6c85ac7dd350f8da2520bac8205ce99df4435b399fa7f4dc4a70407073e390"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5a1ecce0ed281bec7da8550da052a6b89552db14d0a0a45554156f085a912f48"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5304d74867028cca8f64f1cc1215eb365388033c5a691ea7aa6b0dc47412f495"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:64d1f24ee95a2d1e094a4cd7a9b7d34d08db1bbcb8aa9fb717046b0a884ac294"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:119c79922a7001ca6a9e253228eb39b793ea994fd2eccb79481c64b5f9d2a055"}, + {file = "aiohttp-3.12.13-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bb18f00396d22e2f10cd8825d671d9f9a3ba968d708a559c02a627536b36d91c"}, + {file = "aiohttp-3.12.13-cp39-cp39-win32.whl", hash = "sha256:0022de47ef63fd06b065d430ac79c6b0bd24cdae7feaf0e8c6bac23b805a23a8"}, + {file = "aiohttp-3.12.13-cp39-cp39-win_amd64.whl", hash = "sha256:29e08111ccf81b2734ae03f1ad1cb03b9615e7d8f616764f22f71209c094f122"}, + {file = "aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce"}, ] [package.dependencies] aiohappyeyeballs = ">=2.5.0" -aiosignal = ">=1.4.0" +aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" @@ -122,14 +122,14 @@ speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (> [[package]] name = "aiosignal" -version = "1.4.0" +version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, - {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, ] [package.dependencies] @@ -3478,4 +3478,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = "^3.13" -content-hash = "ca918c8b9441327adf6c176055f47d8d3f9e058aa243c1c957e0f7a6a6e4159f" +content-hash = "350611ee13ef6aefb16bb0a69766b37d5691ba38f74e6dc6f4c08be2b0223b26" diff --git a/pyproject.toml b/pyproject.toml index 28d9d5b01..b55498847 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ pytest = "^8.4.1" pytest-asyncio = "^1.0.0" pytest-cov = "^6.0.0" pytest-nhsd-apim = "^5.0.0" -aiohttp = "^3.12.14" +aiohttp = "^3.12.13" awscli = "^1.37.24" awscli-local = "^0.22.0" polyfactory = "^2.20.0" diff --git a/src/eligibility_signposting_api/app.py b/src/eligibility_signposting_api/app.py index 26be07c99..6246a807c 100644 --- a/src/eligibility_signposting_api/app.py +++ b/src/eligibility_signposting_api/app.py @@ -8,10 +8,10 @@ from mangum.types import LambdaContext, LambdaEvent from eligibility_signposting_api import audit, repos, services +from eligibility_signposting_api.common.error_handler import handle_exception +from eligibility_signposting_api.common.request_validator import validate_request_params from eligibility_signposting_api.config.config import config, init_logging -from eligibility_signposting_api.error_handler import handle_exception from eligibility_signposting_api.views import eligibility_blueprint -from eligibility_signposting_api.wrapper import validate_request_params init_logging() logger = logging.getLogger(__name__) diff --git a/src/eligibility_signposting_api/audit/audit_context.py b/src/eligibility_signposting_api/audit/audit_context.py index b54276cbb..28a6f9072 100644 --- a/src/eligibility_signposting_api/audit/audit_context.py +++ b/src/eligibility_signposting_api/audit/audit_context.py @@ -18,7 +18,7 @@ RequestAuditQueryParams, ) from eligibility_signposting_api.audit.audit_service import AuditService -from eligibility_signposting_api.model.eligibility import ( +from eligibility_signposting_api.model.eligibility_status import ( CohortGroupResult, ConditionName, IterationResult, diff --git a/src/eligibility_signposting_api/common/__init__.py b/src/eligibility_signposting_api/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/eligibility_signposting_api/common/api-error-response-readme.md b/src/eligibility_signposting_api/common/api-error-response-readme.md new file mode 100644 index 000000000..db6190be7 --- /dev/null +++ b/src/eligibility_signposting_api/common/api-error-response-readme.md @@ -0,0 +1,88 @@ +# How to Use the API Error Response Module + +This document outlines how to use the `api_error_response.py` module for standardized error handling within the Eligibility Signposting API. The module ensures that all API errors are consistent, logged appropriately, and conform to the FHIR `OperationOutcome` standard. + +## Core Concepts + +The error handling mechanism is built around the class `APIErrorResponse`. + +1. **`APIErrorResponse` Class**: This class is a constructor for a specific type of error. An instance of this class holds configuration for an error, such as the `HTTPStatus`, severity, and various FHIR-specific codes. +2. **Pre-defined Error Instances**: The module defines several singleton instances of for common, application-specific errors. Examples include: + - `INVALID_CATEGORY_ERROR` + - `NHS_NUMBER_MISMATCH_ERROR` + - `INTERNAL_SERVER_ERROR` +3. **`log_and_generate_response()` Method**: This is the primary method to be used. When called on an `APIErrorResponse` instance, it performs two actions: + - Logs the error with a detailed internal message. + - Generates a complete HTTP response dictionary (`statusCode`, `headers`, `body`) containing a FHIR-compliant `OperationOutcome` payload. + +## How to Use + +The primary way to handle errors is to import a pre-defined error object from `api_error_response.py` and call its `log_and_generate_response()` method. + +### 1. Handling Specific, Known Errors + +For handling validation failures or other expected error conditions, use one of the pre-defined error instances. +The `wrapper.py` module uses this pattern to validate query parameters. If a parameter is invalid, it calls the corresponding error function. + +#### Example: Invalid "category" parameter + +``` python +# wrapper.py + +from eligibility_signposting_api.api_error_response import INVALID_CATEGORY_ERROR + +def get_category_error_response(category: str) -> dict[str, Any]: + """Generates an error response for an invalid category.""" + + return INVALID_CATEGORY_ERROR.log_and_generate_response( + log_message=f"Invalid category query param: '{category}'", + diagnostics=f"{category} is not a category that is supported by the API", + location_param="category" + ) +``` + +#### Key Parameters for `log_and_generate_response()` + +- `log_message`: A detailed message for internal logging. This should contain specific information useful for debugging. +- `diagnostics`: The user-facing error message that will be included in the API response body. +- `location_param` (optional): The name of the parameter that caused the error. This helps pinpoint the issue for API consumers. + +### 2. Handling Unexpected Exceptions (Global Error Handler) + +For unexpected errors, a global exception handler in `error_handler.py` catches any unhandled exception and returns a generic 500 Internal Server Error. This prevents sensitive information from leaking in stack traces. + +``` python +# error_handler.py + +from eligibility_signposting_api.api_error_response import INTERNAL_SERVER_ERROR + +def handle_exception(e: Exception) -> ResponseReturnValue | HTTPException: + # Generate a generic, safe response for the client + response = INTERNAL_SERVER_ERROR.log_and_generate_response( + log_message=f"An unexpected error occurred: {traceback.format_exception(e)}", + diagnostics="An unexpected error occurred." + ) + return make_response(response.get("body"), response.get("statusCode"), response.get("headers")) +``` + +### 3. Creating New Error Types + +If a new, reusable error condition is identified, you should add a new instance of `APIErrorResponse` to `api_error_response.py` +Follow the existing pattern: + +``` python +# api_error_response.py + +# ... (other error definitions) + +SOME_NEW_ERROR = APIErrorResponse( + status_code=HTTPStatus.BAD_REQUEST, + fhir_issue_code=FHIRIssueCode.VALUE, + fhir_issue_severity=FHIRIssueSeverity.ERROR, + fhir_coding_system=FHIR_SPINE_ERROR_CODE_SYSTEM, + fhir_error_code=FHIRSpineErrorCode.INVALID_PARAMETER, + fhir_display_message="A new specific error message for display", +) +``` + +By centralizing error definitions, we ensure that the API provides a consistent and predictable experience for its consumers. diff --git a/src/eligibility_signposting_api/api_error_response.py b/src/eligibility_signposting_api/common/api_error_response.py similarity index 100% rename from src/eligibility_signposting_api/api_error_response.py rename to src/eligibility_signposting_api/common/api_error_response.py diff --git a/src/eligibility_signposting_api/error_handler.py b/src/eligibility_signposting_api/common/error_handler.py similarity index 89% rename from src/eligibility_signposting_api/error_handler.py rename to src/eligibility_signposting_api/common/error_handler.py index 3841bf170..662e8bdda 100644 --- a/src/eligibility_signposting_api/error_handler.py +++ b/src/eligibility_signposting_api/common/error_handler.py @@ -5,7 +5,7 @@ from flask.typing import ResponseReturnValue from werkzeug.exceptions import HTTPException -from eligibility_signposting_api.api_error_response import INTERNAL_SERVER_ERROR +from eligibility_signposting_api.common.api_error_response import INTERNAL_SERVER_ERROR logger = logging.getLogger(__name__) diff --git a/src/eligibility_signposting_api/wrapper.py b/src/eligibility_signposting_api/common/request_validator.py similarity index 98% rename from src/eligibility_signposting_api/wrapper.py rename to src/eligibility_signposting_api/common/request_validator.py index c3437afea..1bc8d9d86 100644 --- a/src/eligibility_signposting_api/wrapper.py +++ b/src/eligibility_signposting_api/common/request_validator.py @@ -6,7 +6,7 @@ from mangum.types import LambdaContext, LambdaEvent -from eligibility_signposting_api.api_error_response import ( +from eligibility_signposting_api.common.api_error_response import ( INVALID_CATEGORY_ERROR, INVALID_CONDITION_FORMAT_ERROR, INVALID_INCLUDE_ACTIONS_ERROR, diff --git a/src/eligibility_signposting_api/model/eligibility.py b/src/eligibility_signposting_api/model/eligibility_status.py similarity index 100% rename from src/eligibility_signposting_api/model/eligibility.py rename to src/eligibility_signposting_api/model/eligibility_status.py diff --git a/src/eligibility_signposting_api/repos/person_repo.py b/src/eligibility_signposting_api/repos/person_repo.py index 41ea20745..cfa18fa12 100644 --- a/src/eligibility_signposting_api/repos/person_repo.py +++ b/src/eligibility_signposting_api/repos/person_repo.py @@ -5,7 +5,7 @@ from boto3.resources.base import ServiceResource from wireup import Inject, service -from eligibility_signposting_api.model.eligibility import NHSNumber +from eligibility_signposting_api.model.eligibility_status import NHSNumber from eligibility_signposting_api.repos.exceptions import NotFoundError logger = logging.getLogger(__name__) diff --git a/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py b/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py index fa586a4e7..a2e4f6bd8 100644 --- a/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py +++ b/src/eligibility_signposting_api/services/calculators/eligibility_calculator.py @@ -24,8 +24,8 @@ from wireup import service -from eligibility_signposting_api.model import eligibility, rules -from eligibility_signposting_api.model.eligibility import ( +from eligibility_signposting_api.model import eligibility_status, rules +from eligibility_signposting_api.model.eligibility_status import ( ActionCode, ActionDescription, ActionType, @@ -58,7 +58,7 @@ class EligibilityCalculator: person_data: Row campaign_configs: Collection[rules.CampaignConfig] - results: list[eligibility.Condition] = field(default_factory=list) + results: list[eligibility_status.Condition] = field(default_factory=list) @property def active_campaigns(self) -> list[rules.CampaignConfig]: @@ -66,7 +66,7 @@ def active_campaigns(self) -> list[rules.CampaignConfig]: def campaigns_grouped_by_condition_name( self, conditions: list[str], category: str - ) -> Iterator[tuple[eligibility.ConditionName, list[rules.CampaignConfig]]]: + ) -> Iterator[tuple[eligibility_status.ConditionName, list[rules.CampaignConfig]]]: """Generator that yields campaign groups filtered by condition names and campaign category.""" mapping = { @@ -100,9 +100,9 @@ def get_the_best_cohort_memberships( cohort_results: dict[str, CohortGroupResult], ) -> tuple[Status, list[CohortGroupResult]]: if not cohort_results: - return eligibility.Status.not_eligible, [] + return eligibility_status.Status.not_eligible, [] - best_status = eligibility.Status.best(*[result.status for result in cohort_results.values()]) + best_status = eligibility_status.Status.best(*[result.status for result in cohort_results.values()]) best_cohorts = [result for result in cohort_results.values() if result.status == best_status] best_cohorts = [ @@ -158,7 +158,7 @@ def get_action_rules_components( def evaluate_eligibility( self, include_actions: str, conditions: list[str], category: str - ) -> eligibility.EligibilityStatus: + ) -> eligibility_status.EligibilityStatus: include_actions_flag = include_actions.upper() == "Y" condition_results: dict[ConditionName, IterationResult] = {} actions: list[SuggestedAction] | None = [] @@ -186,7 +186,7 @@ def evaluate_eligibility( ), ) = max(iteration_results.items(), key=lambda item: item[1][1].status.value) else: - best_candidate = IterationResult(eligibility.Status.not_eligible, [], actions) + best_candidate = IterationResult(eligibility_status.Status.not_eligible, [], actions) best_campaign_id = None best_campaign_version = None best_active_iteration = None @@ -233,7 +233,7 @@ def evaluate_eligibility( # Consolidate all the results and return final_result = self.build_condition_results(condition_results) - return eligibility.EligibilityStatus(conditions=final_result) + return eligibility_status.EligibilityStatus(conditions=final_result) def get_iteration_results( self, actions: list[SuggestedAction] | None, campaign_group: list[CampaignConfig] @@ -406,20 +406,20 @@ def evaluate_suppression_rules( def evaluate_rules_priority_group( self, rules_group: Iterator[rules.IterationRule] - ) -> tuple[eligibility.Status, list[eligibility.Reason], bool]: + ) -> tuple[eligibility_status.Status, list[eligibility_status.Reason], bool]: is_rule_stop = False exclusion_reasons = [] - best_status = eligibility.Status.not_eligible + best_status = eligibility_status.Status.not_eligible for rule in rules_group: is_rule_stop = rule.rule_stop or is_rule_stop rule_calculator = RuleCalculator(person_data=self.person_data, rule=rule) status, reason = rule_calculator.evaluate_exclusion() if status.is_exclusion: - best_status = eligibility.Status.best(status, best_status) + best_status = eligibility_status.Status.best(status, best_status) exclusion_reasons.append(reason) else: - best_status = eligibility.Status.actionable + best_status = eligibility_status.Status.actionable return best_status, exclusion_reasons, is_rule_stop diff --git a/src/eligibility_signposting_api/services/calculators/rule_calculator.py b/src/eligibility_signposting_api/services/calculators/rule_calculator.py index 03641e3be..96b0dfd87 100644 --- a/src/eligibility_signposting_api/services/calculators/rule_calculator.py +++ b/src/eligibility_signposting_api/services/calculators/rule_calculator.py @@ -6,7 +6,7 @@ from hamcrest.core.string_description import StringDescription -from eligibility_signposting_api.model import eligibility, rules +from eligibility_signposting_api.model import eligibility_status, rules from eligibility_signposting_api.services.rules.operators import OperatorRegistry Row = Collection[Mapping[str, Any]] @@ -17,15 +17,15 @@ class RuleCalculator: person_data: Row rule: rules.IterationRule - def evaluate_exclusion(self) -> tuple[eligibility.Status, eligibility.Reason]: + def evaluate_exclusion(self) -> tuple[eligibility_status.Status, eligibility_status.Reason]: """Evaluate if a particular rule excludes this person. Return the result, and the reason for the result.""" attribute_value = self.get_attribute_value() status, reason, matcher_matched = self.evaluate_rule(attribute_value) - reason = eligibility.Reason( - rule_name=eligibility.RuleName(self.rule.name), - rule_type=eligibility.RuleType(self.rule.type), - rule_priority=eligibility.RulePriority(str(self.rule.priority)), - rule_description=eligibility.RuleDescription(self.rule.description), + reason = eligibility_status.Reason( + rule_name=eligibility_status.RuleName(self.rule.name), + rule_type=eligibility_status.RuleType(self.rule.type), + rule_priority=eligibility_status.RulePriority(str(self.rule.priority)), + rule_description=eligibility_status.RuleDescription(self.rule.description), matcher_matched=matcher_matched, ) return status, reason @@ -71,7 +71,7 @@ def get_value(dictionary: Mapping[str, Any] | None, key: str) -> dict: v = dictionary.get(key, {}) if isinstance(dictionary, dict) else {} return v if isinstance(v, dict) else {} - def evaluate_rule(self, attribute_value: str | None) -> tuple[eligibility.Status, str, bool]: + def evaluate_rule(self, attribute_value: str | None) -> tuple[eligibility_status.Status, str, bool]: """Evaluate a rule against a person data attribute. Return the result, and the reason for the result.""" matcher_class = OperatorRegistry.get(self.rule.operator) matcher = matcher_class(rule_value=self.rule.comparator) @@ -81,12 +81,12 @@ def evaluate_rule(self, attribute_value: str | None) -> tuple[eligibility.Status if matcher_matched: matcher.describe_match(attribute_value, reason) status = { - rules.RuleType.filter: eligibility.Status.not_eligible, - rules.RuleType.suppression: eligibility.Status.not_actionable, - rules.RuleType.redirect: eligibility.Status.actionable, - rules.RuleType.not_eligible_actions: eligibility.Status.not_eligible, - rules.RuleType.not_actionable_actions: eligibility.Status.not_actionable, + rules.RuleType.filter: eligibility_status.Status.not_eligible, + rules.RuleType.suppression: eligibility_status.Status.not_actionable, + rules.RuleType.redirect: eligibility_status.Status.actionable, + rules.RuleType.not_eligible_actions: eligibility_status.Status.not_eligible, + rules.RuleType.not_actionable_actions: eligibility_status.Status.not_actionable, }[self.rule.type] return status, str(reason), matcher_matched matcher.describe_mismatch(attribute_value, reason) - return eligibility.Status.actionable, str(reason), matcher_matched + return eligibility_status.Status.actionable, str(reason), matcher_matched diff --git a/src/eligibility_signposting_api/services/eligibility_services.py b/src/eligibility_signposting_api/services/eligibility_services.py index 48586290b..a4ff86ac1 100644 --- a/src/eligibility_signposting_api/services/eligibility_services.py +++ b/src/eligibility_signposting_api/services/eligibility_services.py @@ -2,7 +2,7 @@ from wireup import service -from eligibility_signposting_api.model import eligibility +from eligibility_signposting_api.model import eligibility_status from eligibility_signposting_api.repos import CampaignRepo, NotFoundError, PersonRepo from eligibility_signposting_api.services.calculators import eligibility_calculator as calculator @@ -32,11 +32,11 @@ def __init__( def get_eligibility_status( self, - nhs_number: eligibility.NHSNumber, + nhs_number: eligibility_status.NHSNumber, include_actions: str, conditions: list[str], category: str, - ) -> eligibility.EligibilityStatus: + ) -> eligibility_status.EligibilityStatus: """Calculate a person's eligibility for vaccination given an NHS number.""" if nhs_number: try: diff --git a/src/eligibility_signposting_api/views/eligibility.py b/src/eligibility_signposting_api/views/eligibility.py index 1ce27ca39..6f279653a 100644 --- a/src/eligibility_signposting_api/views/eligibility.py +++ b/src/eligibility_signposting_api/views/eligibility.py @@ -8,18 +8,18 @@ from flask.typing import ResponseReturnValue from wireup import Injected -from eligibility_signposting_api.api_error_response import NHS_NUMBER_NOT_FOUND_ERROR from eligibility_signposting_api.audit.audit_context import AuditContext from eligibility_signposting_api.audit.audit_service import AuditService -from eligibility_signposting_api.model.eligibility import Condition, EligibilityStatus, NHSNumber, Status +from eligibility_signposting_api.common.api_error_response import NHS_NUMBER_NOT_FOUND_ERROR +from eligibility_signposting_api.model.eligibility_status import Condition, EligibilityStatus, NHSNumber, Status from eligibility_signposting_api.services import EligibilityService, UnknownPersonError -from eligibility_signposting_api.views.response_model import eligibility -from eligibility_signposting_api.views.response_model.eligibility import ProcessedSuggestion +from eligibility_signposting_api.views.response_model import eligibility_response +from eligibility_signposting_api.views.response_model.eligibility_response import ProcessedSuggestion STATUS_MAPPING = { - Status.actionable: eligibility.Status.actionable, - Status.not_actionable: eligibility.Status.not_actionable, - Status.not_eligible: eligibility.Status.not_eligible, + Status.actionable: eligibility_response.Status.actionable, + Status.not_actionable: eligibility_response.Status.not_actionable, + Status.not_eligible: eligibility_response.Status.not_eligible, } logger = logging.getLogger(__name__) @@ -56,11 +56,9 @@ def check_eligibility( except UnknownPersonError: return handle_unknown_person_error(nhs_number) else: - eligibility_response: eligibility.EligibilityResponse = build_eligibility_response(eligibility_status) + response: eligibility_response.EligibilityResponse = build_eligibility_response(eligibility_status) AuditContext.write_to_firehose(audit_service) - return make_response( - eligibility_response.model_dump(by_alias=True, mode="json", exclude_none=True), HTTPStatus.OK - ) + return make_response(response.model_dump(by_alias=True, mode="json", exclude_none=True), HTTPStatus.OK) def get_or_default_query_params() -> dict[str, Any]: @@ -103,7 +101,7 @@ def handle_unknown_person_error(nhs_number: NHSNumber) -> ResponseReturnValue: return make_response(response.get("body"), response.get("statusCode"), response.get("headers")) -def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibility.EligibilityResponse: +def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibility_response.EligibilityResponse: """Return an object representing the API response we are going to send, given an evaluation of the person's eligibility.""" @@ -111,9 +109,9 @@ def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibi for condition in eligibility_status.conditions: suggestions = ProcessedSuggestion( # pyright: ignore[reportCallIssue] - condition=eligibility.ConditionName(condition.condition_name), # pyright: ignore[reportCallIssue] + condition=eligibility_response.ConditionName(condition.condition_name), # pyright: ignore[reportCallIssue] status=STATUS_MAPPING[condition.status], - statusText=eligibility.StatusText(condition.status_text), # pyright: ignore[reportCallIssue] + statusText=eligibility_response.StatusText(condition.status_text), # pyright: ignore[reportCallIssue] eligibilityCohorts=build_eligibility_cohorts(condition), # pyright: ignore[reportCallIssue] suitabilityRules=build_suitability_results(condition), # pyright: ignore[reportCallIssue] actions=build_actions(condition), @@ -122,27 +120,29 @@ def build_eligibility_response(eligibility_status: EligibilityStatus) -> eligibi processed_suggestions.append(suggestions) response_id = uuid.uuid4() - updated = eligibility.LastUpdated(datetime.now(tz=UTC)) + updated = eligibility_response.LastUpdated(datetime.now(tz=UTC)) AuditContext.add_response_details(response_id, updated) - return eligibility.EligibilityResponse( # pyright: ignore[reportCallIssue] + return eligibility_response.EligibilityResponse( # pyright: ignore[reportCallIssue] responseId=response_id, # pyright: ignore[reportCallIssue] - meta=eligibility.Meta(lastUpdated=updated), + meta=eligibility_response.Meta(lastUpdated=updated), # pyright: ignore[reportCallIssue] processedSuggestions=processed_suggestions, ) -def build_actions(condition: Condition) -> list[eligibility.Action] | None: +def build_actions(condition: Condition) -> list[eligibility_response.Action] | None: if condition.actions is not None: return [ - eligibility.Action( - actionType=eligibility.ActionType(action.action_type), - actionCode=eligibility.ActionCode(action.action_code), - description=eligibility.Description(action.action_description or ""), - urlLabel=eligibility.UrlLabel(action.url_label or ""), - urlLink=eligibility.UrlLink(str(action.url_link)) if action.url_link else eligibility.UrlLink(""), + eligibility_response.Action( + actionType=eligibility_response.ActionType(action.action_type), + actionCode=eligibility_response.ActionCode(action.action_code), + description=eligibility_response.Description(action.action_description or ""), + urlLabel=eligibility_response.UrlLabel(action.url_label or ""), + urlLink=eligibility_response.UrlLink(str(action.url_link)) + if action.url_link + else eligibility_response.UrlLink(""), ) for action in condition.actions ] @@ -150,13 +150,13 @@ def build_actions(condition: Condition) -> list[eligibility.Action] | None: return None -def build_eligibility_cohorts(condition: Condition) -> list[eligibility.EligibilityCohort]: +def build_eligibility_cohorts(condition: Condition) -> list[eligibility_response.EligibilityCohort]: """Group Iteration cohorts and make only one entry per cohort group""" return [ - eligibility.EligibilityCohort( - cohortCode=eligibility.CohortCode(cohort_result.cohort_code), - cohortText=eligibility.CohortText(cohort_result.description), + eligibility_response.EligibilityCohort( + cohortCode=eligibility_response.CohortCode(cohort_result.cohort_code), + cohortText=eligibility_response.CohortText(cohort_result.description), cohortStatus=STATUS_MAPPING[cohort_result.status], ) for cohort_result in condition.cohort_results @@ -164,7 +164,7 @@ def build_eligibility_cohorts(condition: Condition) -> list[eligibility.Eligibil ] -def build_suitability_results(condition: Condition) -> list[eligibility.SuitabilityRule]: +def build_suitability_results(condition: Condition) -> list[eligibility_response.SuitabilityRule]: """Make only one entry if there are duplicate rules""" if condition.status != Status.not_actionable: return [] @@ -178,10 +178,10 @@ def build_suitability_results(condition: Condition) -> list[eligibility.Suitabil if reason.rule_name not in unique_rule_codes and reason.rule_description: unique_rule_codes.add(reason.rule_name) suitability_results.append( - eligibility.SuitabilityRule( - ruleType=eligibility.RuleType(reason.rule_type.value), - ruleCode=eligibility.RuleCode(reason.rule_name), - ruleText=eligibility.RuleText(reason.rule_description), + eligibility_response.SuitabilityRule( + ruleType=eligibility_response.RuleType(reason.rule_type.value), + ruleCode=eligibility_response.RuleCode(reason.rule_name), + ruleText=eligibility_response.RuleText(reason.rule_description), ) ) diff --git a/src/eligibility_signposting_api/views/response_model/eligibility.py b/src/eligibility_signposting_api/views/response_model/eligibility_response.py similarity index 100% rename from src/eligibility_signposting_api/views/response_model/eligibility.py rename to src/eligibility_signposting_api/views/response_model/eligibility_response.py diff --git a/tests/e2e/tests/eligibility_signposting/test_eligibility_check.py b/tests/e2e/tests/eligibility_signposting/test_eligibility_check.py index 76dbd8194..817648eb9 100644 --- a/tests/e2e/tests/eligibility_signposting/test_eligibility_check.py +++ b/tests/e2e/tests/eligibility_signposting/test_eligibility_check.py @@ -27,7 +27,7 @@ def is_api_accessible(): return response.status_code != HTTP_STATUS_NOT_FOUND -@pytest.mark.eligibility +@pytest.mark.eligibility_response class TestEligibilityCheck: """Test suite for the Eligibility Check endpoint.""" diff --git a/tests/e2e/tests/eligibility_signposting/test_eligibility_check_bdd.py b/tests/e2e/tests/eligibility_signposting/test_eligibility_check_bdd.py index 3bb0360bd..ceb94642a 100644 --- a/tests/e2e/tests/eligibility_signposting/test_eligibility_check_bdd.py +++ b/tests/e2e/tests/eligibility_signposting/test_eligibility_check_bdd.py @@ -8,7 +8,7 @@ sys.path.append(str(Path(__file__).parent.parent.parent)) # Mark all tests as BDD tests -pytestmark = [pytest.mark.bdd, pytest.mark.eligibility] +pytestmark = [pytest.mark.bdd, pytest.mark.eligibility_response] # Load the scenarios from the feature file scenarios("../../features/eligibility_check/eligibility_check.feature") diff --git a/tests/fixtures/builders/model/eligibility.py b/tests/fixtures/builders/model/eligibility.py index 6a987e0d2..07f3c825c 100644 --- a/tests/fixtures/builders/model/eligibility.py +++ b/tests/fixtures/builders/model/eligibility.py @@ -4,28 +4,28 @@ from polyfactory import Use from polyfactory.factories import DataclassFactory -from eligibility_signposting_api.model import eligibility -from eligibility_signposting_api.model.eligibility import RuleType, UrlLink +from eligibility_signposting_api.model import eligibility_status +from eligibility_signposting_api.model.eligibility_status import RuleType, UrlLink -class SuggestedActionFactory(DataclassFactory[eligibility.SuggestedAction]): +class SuggestedActionFactory(DataclassFactory[eligibility_status.SuggestedAction]): url_link = UrlLink("https://test-example.com") -class ReasonFactory(DataclassFactory[eligibility.Reason]): +class ReasonFactory(DataclassFactory[eligibility_status.Reason]): rule_type = RuleType.filter -class CohortResultFactory(DataclassFactory[eligibility.CohortGroupResult]): +class CohortResultFactory(DataclassFactory[eligibility_status.CohortGroupResult]): reasons = Use(ReasonFactory.batch, size=2) -class ConditionFactory(DataclassFactory[eligibility.Condition]): +class ConditionFactory(DataclassFactory[eligibility_status.Condition]): actions = Use(SuggestedActionFactory.batch, size=2) cohort_results = Use(CohortResultFactory.batch, size=2) -class EligibilityStatusFactory(DataclassFactory[eligibility.EligibilityStatus]): +class EligibilityStatusFactory(DataclassFactory[eligibility_status.EligibilityStatus]): conditions = Use(ConditionFactory.batch, size=2) diff --git a/tests/fixtures/builders/views/response_model/eligibility.py b/tests/fixtures/builders/views/response_model/eligibility.py index 061036908..3b8bff75b 100644 --- a/tests/fixtures/builders/views/response_model/eligibility.py +++ b/tests/fixtures/builders/views/response_model/eligibility.py @@ -1,23 +1,23 @@ from polyfactory import Use from polyfactory.factories.pydantic_factory import ModelFactory -from eligibility_signposting_api.views.response_model import eligibility +from eligibility_signposting_api.views.response_model import eligibility_response -class EligibilityCohortFactory(ModelFactory[eligibility.EligibilityCohort]): ... +class EligibilityCohortFactory(ModelFactory[eligibility_response.EligibilityCohort]): ... -class SuitabilityRuleFactory(ModelFactory[eligibility.SuitabilityRule]): ... +class SuitabilityRuleFactory(ModelFactory[eligibility_response.SuitabilityRule]): ... -class ActionFactory(ModelFactory[eligibility.Action]): ... +class ActionFactory(ModelFactory[eligibility_response.Action]): ... -class ProcessedSuggestionFactory(ModelFactory[eligibility.ProcessedSuggestion]): +class ProcessedSuggestionFactory(ModelFactory[eligibility_response.ProcessedSuggestion]): eligibility_cohorts = Use(EligibilityCohortFactory.batch, size=2) suitability_rules = Use(SuitabilityRuleFactory.batch, size=2) actions = Use(ActionFactory.batch, size=2) -class EligibilityResponseFactory(ModelFactory[eligibility.EligibilityResponse]): +class EligibilityResponseFactory(ModelFactory[eligibility_response.EligibilityResponse]): processed_suggestions = Use(ProcessedSuggestionFactory.batch, size=2) diff --git a/tests/fixtures/matchers/eligibility.py b/tests/fixtures/matchers/eligibility.py index 98bfb4138..731a3de76 100644 --- a/tests/fixtures/matchers/eligibility.py +++ b/tests/fixtures/matchers/eligibility.py @@ -1,7 +1,11 @@ from hamcrest.core.matcher import Matcher -from eligibility_signposting_api.model.eligibility import CohortGroupResult, Condition, EligibilityStatus, Reason -from eligibility_signposting_api.views.response_model.eligibility import Action, EligibilityCohort, SuitabilityRule +from eligibility_signposting_api.model.eligibility_status import CohortGroupResult, Condition, EligibilityStatus, Reason +from eligibility_signposting_api.views.response_model.eligibility_response import ( + Action, + EligibilityCohort, + SuitabilityRule, +) from .meta import BaseAutoMatcher diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 75cf97878..f4b0856d9 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -16,7 +16,7 @@ from httpx import RequestError from yarl import URL -from eligibility_signposting_api.model import eligibility, rules +from eligibility_signposting_api.model import eligibility_status, rules from eligibility_signposting_api.repos.campaign_repo import BucketName from eligibility_signposting_api.repos.person_repo import TableName from tests.fixtures.builders.model import rule @@ -330,9 +330,9 @@ def person_table(dynamodb_resource: ServiceResource) -> Generator[Any]: @pytest.fixture -def persisted_person(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]: - nhs_number = eligibility.NHSNumber(faker.nhs_number()) - date_of_birth = eligibility.DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=65)) +def persisted_person(person_table: Any, faker: Faker) -> Generator[eligibility_status.NHSNumber]: + nhs_number = eligibility_status.NHSNumber(faker.nhs_number()) + date_of_birth = eligibility_status.DateOfBirth(faker.date_of_birth(minimum_age=18, maximum_age=65)) for row in ( rows := person_rows_builder(nhs_number, date_of_birth=date_of_birth, postcode="hp1", cohorts=["cohort1"]) @@ -346,9 +346,9 @@ def persisted_person(person_table: Any, faker: Faker) -> Generator[eligibility.N @pytest.fixture -def persisted_77yo_person(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]: - nhs_number = eligibility.NHSNumber(faker.nhs_number()) - date_of_birth = eligibility.DateOfBirth(faker.date_of_birth(minimum_age=77, maximum_age=77)) +def persisted_77yo_person(person_table: Any, faker: Faker) -> Generator[eligibility_status.NHSNumber]: + nhs_number = eligibility_status.NHSNumber(faker.nhs_number()) + date_of_birth = eligibility_status.DateOfBirth(faker.date_of_birth(minimum_age=77, maximum_age=77)) for row in ( rows := person_rows_builder( @@ -367,9 +367,9 @@ def persisted_77yo_person(person_table: Any, faker: Faker) -> Generator[eligibil @pytest.fixture -def persisted_person_all_cohorts(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]: - nhs_number = eligibility.NHSNumber(faker.nhs_number()) - date_of_birth = eligibility.DateOfBirth(faker.date_of_birth(minimum_age=74, maximum_age=74)) +def persisted_person_all_cohorts(person_table: Any, faker: Faker) -> Generator[eligibility_status.NHSNumber]: + nhs_number = eligibility_status.NHSNumber(faker.nhs_number()) + date_of_birth = eligibility_status.DateOfBirth(faker.date_of_birth(minimum_age=74, maximum_age=74)) for row in ( rows := person_rows_builder( @@ -389,8 +389,8 @@ def persisted_person_all_cohorts(person_table: Any, faker: Faker) -> Generator[e @pytest.fixture -def persisted_person_no_cohorts(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]: - nhs_number = eligibility.NHSNumber(faker.nhs_number()) +def persisted_person_no_cohorts(person_table: Any, faker: Faker) -> Generator[eligibility_status.NHSNumber]: + nhs_number = eligibility_status.NHSNumber(faker.nhs_number()) for row in (rows := person_rows_builder(nhs_number)): person_table.put_item(Item=row) @@ -402,8 +402,8 @@ def persisted_person_no_cohorts(person_table: Any, faker: Faker) -> Generator[el @pytest.fixture -def persisted_person_pc_sw19(person_table: Any, faker: Faker) -> Generator[eligibility.NHSNumber]: - nhs_number = eligibility.NHSNumber( +def persisted_person_pc_sw19(person_table: Any, faker: Faker) -> Generator[eligibility_status.NHSNumber]: + nhs_number = eligibility_status.NHSNumber( faker.nhs_number(), ) for row in (rows := person_rows_builder(nhs_number, postcode="SW19", cohorts=["cohort1"])): diff --git a/tests/integration/in_process/test_eligibility_endpoint.py b/tests/integration/in_process/test_eligibility_endpoint.py index 229d8652c..c39c48207 100644 --- a/tests/integration/in_process/test_eligibility_endpoint.py +++ b/tests/integration/in_process/test_eligibility_endpoint.py @@ -11,7 +11,7 @@ has_key, ) -from eligibility_signposting_api.model.eligibility import ( +from eligibility_signposting_api.model.eligibility_status import ( NHSNumber, ) from eligibility_signposting_api.model.rules import CampaignConfig diff --git a/tests/integration/lambda/test_app_running_as_lambda.py b/tests/integration/lambda/test_app_running_as_lambda.py index b157fe44f..85fb305ea 100644 --- a/tests/integration/lambda/test_app_running_as_lambda.py +++ b/tests/integration/lambda/test_app_running_as_lambda.py @@ -23,7 +23,7 @@ ) from yarl import URL -from eligibility_signposting_api.model.eligibility import NHSNumber +from eligibility_signposting_api.model.eligibility_status import NHSNumber from eligibility_signposting_api.model.rules import CampaignConfig from eligibility_signposting_api.repos.campaign_repo import BucketName diff --git a/tests/integration/repo/test_person_repo.py b/tests/integration/repo/test_person_repo.py index 7a444d20e..4dcf53383 100644 --- a/tests/integration/repo/test_person_repo.py +++ b/tests/integration/repo/test_person_repo.py @@ -4,7 +4,7 @@ from faker import Faker from hamcrest import assert_that, contains_inanyorder, has_entries -from eligibility_signposting_api.model.eligibility import NHSNumber +from eligibility_signposting_api.model.eligibility_status import NHSNumber from eligibility_signposting_api.repos import NotFoundError from eligibility_signposting_api.repos.person_repo import PersonRepo diff --git a/tests/unit/audit/test_audit_context.py b/tests/unit/audit/test_audit_context.py index 25897bd9a..b4f39f416 100644 --- a/tests/unit/audit/test_audit_context.py +++ b/tests/unit/audit/test_audit_context.py @@ -9,7 +9,7 @@ from eligibility_signposting_api.audit.audit_context import AuditContext from eligibility_signposting_api.audit.audit_models import AuditAction, AuditEvent from eligibility_signposting_api.audit.audit_service import AuditService -from eligibility_signposting_api.model.eligibility import ( +from eligibility_signposting_api.model.eligibility_status import ( ActionCode, ActionDescription, ActionType, diff --git a/tests/unit/common/__init__.py b/tests/unit/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/test_wrapper.py b/tests/unit/common/test_request_validator.py similarity index 90% rename from tests/unit/test_wrapper.py rename to tests/unit/common/test_request_validator.py index 18ef5d477..2787c981a 100644 --- a/tests/unit/test_wrapper.py +++ b/tests/unit/common/test_request_validator.py @@ -4,8 +4,8 @@ import pytest -from eligibility_signposting_api import wrapper -from eligibility_signposting_api.wrapper import logger +from eligibility_signposting_api.common import request_validator +from eligibility_signposting_api.common.request_validator import logger @pytest.fixture(autouse=True) @@ -27,7 +27,7 @@ def setup_logging_for_tests(): ) def test_validate_nhs_number(path_nhs, header_nhs, expected_result, expected_log_msg, caplog): with caplog.at_level(logging.ERROR): - result = wrapper.validate_nhs_number(path_nhs, header_nhs) + result = request_validator.validate_nhs_number(path_nhs, header_nhs) assert result == expected_result @@ -59,7 +59,7 @@ def test_validate_query_params_conditions(conditions_input, is_valid_expected, e params = {"conditions": conditions_input} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid == is_valid_expected if is_valid_expected: @@ -73,7 +73,7 @@ def test_validate_query_params_conditions(conditions_input, is_valid_expected, e def test_validate_query_params_conditions_default(caplog): params = {"category": "ALL", "includeActions": "Y"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is True assert problem is None assert not caplog.records @@ -97,7 +97,7 @@ def test_validate_query_params_conditions_default(caplog): def test_validate_query_params_category(category_input, is_valid_expected, expected_log_msg, caplog): params = {"category": category_input} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid == is_valid_expected if is_valid_expected: @@ -111,7 +111,7 @@ def test_validate_query_params_category(category_input, is_valid_expected, expec def test_validate_query_params_category_default(caplog): params = {"conditions": "ALL", "includeActions": "Y"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is True assert problem is None assert not caplog.records @@ -136,7 +136,7 @@ def test_validate_query_params_category_default(caplog): def test_validate_query_params_include_actions(include_actions_input, is_valid_expected, expected_log_msg, caplog): params = {"includeActions": include_actions_input} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid == is_valid_expected if is_valid_expected: @@ -150,7 +150,7 @@ def test_validate_query_params_include_actions(include_actions_input, is_valid_e def test_validate_query_params_include_actions_default(caplog): params = {"conditions": "ALL", "category": "ALL"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is True assert problem is None assert not caplog.records @@ -159,7 +159,7 @@ def test_validate_query_params_include_actions_default(caplog): def test_validate_query_params_all_valid_params(caplog): params = {"conditions": "COND1,COND2", "category": "SCREENING", "includeActions": "N"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is True assert problem is None assert not caplog.records @@ -168,7 +168,7 @@ def test_validate_query_params_all_valid_params(caplog): def test_validate_query_params_mixed_valid_invalid_conditions_fail_first(caplog): params = {"conditions": "VALID_COND,INVALID!,ANOTHER_VALID", "category": "SCREENING", "includeActions": "N"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is False assert problem is not None assert any( @@ -180,7 +180,7 @@ def test_validate_query_params_mixed_valid_invalid_conditions_fail_first(caplog) def test_validate_query_params_valid_conditions_invalid_category_fail_second(caplog): params = {"conditions": "CONDITION", "category": "BAD_CAT", "includeActions": "N"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is False assert problem is not None assert any( @@ -194,7 +194,7 @@ def test_validate_query_params_valid_conditions_invalid_category_fail_second(cap def test_validate_query_params_valid_conditions_category_invalid_actions_fail_third(caplog): params = {"conditions": "CONDITION", "category": "VACCINATIONS", "includeActions": "Nope"} with caplog.at_level(logging.ERROR): - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is False assert problem is not None assert any( @@ -209,7 +209,7 @@ def test_validate_query_params_returns_correct_problem_details_for_conditions_er invalid_condition = "FLU&COVID" params = {"conditions": invalid_condition} - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is False assert problem is not None @@ -247,7 +247,7 @@ def test_validate_query_params_returns_correct_problem_details_for_category_erro invalid_category = "HEALTHCHECKS" params = {"category": invalid_category} - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is False assert problem is not None @@ -282,7 +282,7 @@ def test_validate_query_params_returns_correct_problem_details_for_include_actio invalid_include_actions = "NAH" params = {"includeActions": invalid_include_actions} - is_valid, problem = wrapper.validate_query_params(params) + is_valid, problem = request_validator.validate_query_params(params) assert is_valid is False assert problem is not None diff --git a/tests/unit/config/__init__.py b/tests/unit/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/test_config.py b/tests/unit/config/test_config.py similarity index 100% rename from tests/unit/test_config.py rename to tests/unit/config/test_config.py diff --git a/tests/unit/model/test_status.py b/tests/unit/model/test_status.py index 72ba73b62..eff2b68f9 100644 --- a/tests/unit/model/test_status.py +++ b/tests/unit/model/test_status.py @@ -1,4 +1,4 @@ -from eligibility_signposting_api.model.eligibility import ConditionName, Status, StatusText +from eligibility_signposting_api.model.eligibility_status import ConditionName, Status, StatusText class TestStatus: diff --git a/tests/unit/services/calculators/test_eligibility_calculator.py b/tests/unit/services/calculators/test_eligibility_calculator.py index baa81b8b7..7b9c39bf5 100644 --- a/tests/unit/services/calculators/test_eligibility_calculator.py +++ b/tests/unit/services/calculators/test_eligibility_calculator.py @@ -11,7 +11,7 @@ from eligibility_signposting_api.audit.audit_models import AuditAction, AuditEvent from eligibility_signposting_api.model import rules from eligibility_signposting_api.model import rules as rules_model -from eligibility_signposting_api.model.eligibility import ( +from eligibility_signposting_api.model.eligibility_status import ( ActionCode, ActionDescription, ActionType, diff --git a/tests/unit/services/test_eligibility_services.py b/tests/unit/services/test_eligibility_services.py index 872347a00..504888f12 100644 --- a/tests/unit/services/test_eligibility_services.py +++ b/tests/unit/services/test_eligibility_services.py @@ -3,7 +3,7 @@ import pytest from hamcrest import assert_that, empty -from eligibility_signposting_api.model.eligibility import NHSNumber +from eligibility_signposting_api.model.eligibility_status import NHSNumber from eligibility_signposting_api.repos import CampaignRepo, NotFoundError, PersonRepo from eligibility_signposting_api.services import EligibilityService, UnknownPersonError from eligibility_signposting_api.services.calculators.eligibility_calculator import EligibilityCalculatorFactory diff --git a/tests/unit/views/test_eligibility.py b/tests/unit/views/test_eligibility.py index 7174f349f..ad39c01e7 100644 --- a/tests/unit/views/test_eligibility.py +++ b/tests/unit/views/test_eligibility.py @@ -14,7 +14,7 @@ from wireup.integration.flask import get_app_container from eligibility_signposting_api.audit.audit_service import AuditService -from eligibility_signposting_api.model.eligibility import ( +from eligibility_signposting_api.model.eligibility_status import ( ActionCode, ActionDescription, ActionType, @@ -39,7 +39,7 @@ build_suitability_results, get_or_default_query_params, ) -from eligibility_signposting_api.views.response_model import eligibility +from eligibility_signposting_api.views.response_model import eligibility_response from tests.fixtures.builders.model.eligibility import ( CohortResultFactory, ConditionFactory, @@ -435,12 +435,12 @@ def test_no_suitability_rules_for_actionable(): ) ], [ - eligibility.Action( - actionType=eligibility.ActionType("TYPE_A"), - actionCode=eligibility.ActionCode("CODE123"), - description=eligibility.Description("Some description"), - urlLink=eligibility.UrlLink("https://example.com"), - urlLabel=eligibility.UrlLabel("Learn more"), + eligibility_response.Action( + actionType=eligibility_response.ActionType("TYPE_A"), + actionCode=eligibility_response.ActionCode("CODE123"), + description=eligibility_response.Description("Some description"), + urlLink=eligibility_response.UrlLink("https://example.com"), + urlLabel=eligibility_response.UrlLabel("Learn more"), ) ], ), @@ -455,9 +455,9 @@ def test_no_suitability_rules_for_actionable(): ) ], [ - eligibility.Action( - actionType=eligibility.ActionType("TYPE_B"), - actionCode=eligibility.ActionCode("CODE123"), + eligibility_response.Action( + actionType=eligibility_response.ActionType("TYPE_B"), + actionCode=eligibility_response.ActionCode("CODE123"), description="", urlLink="", urlLabel="", @@ -483,23 +483,23 @@ def test_build_actions(suggested_actions, expected): def test_excludes_nulls_via_build_response(client: FlaskClient): - mocked_response = eligibility.EligibilityResponse( + mocked_response = eligibility_response.EligibilityResponse( responseId=uuid4(), - meta=eligibility.Meta(lastUpdated=eligibility.LastUpdated(datetime(2023, 1, 1, tzinfo=UTC))), + meta=eligibility_response.Meta(lastUpdated=eligibility_response.LastUpdated(datetime(2023, 1, 1, tzinfo=UTC))), processedSuggestions=[ - eligibility.ProcessedSuggestion( - condition=eligibility.ConditionName("ConditionA"), - status=eligibility.Status.actionable, - statusText=eligibility.StatusText("Go ahead"), + eligibility_response.ProcessedSuggestion( + condition=eligibility_response.ConditionName("ConditionA"), + status=eligibility_response.Status.actionable, + statusText=eligibility_response.StatusText("Go ahead"), eligibilityCohorts=[], suitabilityRules=[], actions=[ - eligibility.Action( - actionType=eligibility.ActionType("TYPE_A"), - actionCode=eligibility.ActionCode("CODE123"), - description=eligibility.Description(""), # Should be an empty string - urlLink=eligibility.UrlLink(""), # Should be an empty string - urlLabel=eligibility.UrlLabel(""), # Should be an empty string + eligibility_response.Action( + actionType=eligibility_response.ActionType("TYPE_A"), + actionCode=eligibility_response.ActionCode("CODE123"), + description=eligibility_response.Description(""), # Should be an empty string + urlLink=eligibility_response.UrlLink(""), # Should be an empty string + urlLabel=eligibility_response.UrlLabel(""), # Should be an empty string ) ], ) @@ -535,23 +535,23 @@ def test_excludes_nulls_via_build_response(client: FlaskClient): def test_build_response_include_values_that_are_not_null(client: FlaskClient): - mocked_response = eligibility.EligibilityResponse( + mocked_response = eligibility_response.EligibilityResponse( responseId=uuid4(), - meta=eligibility.Meta(lastUpdated=eligibility.LastUpdated(datetime(2023, 1, 1, tzinfo=UTC))), + meta=eligibility_response.Meta(lastUpdated=eligibility_response.LastUpdated(datetime(2023, 1, 1, tzinfo=UTC))), processedSuggestions=[ - eligibility.ProcessedSuggestion( - condition=eligibility.ConditionName("ConditionA"), - status=eligibility.Status.actionable, - statusText=eligibility.StatusText("Go ahead"), + eligibility_response.ProcessedSuggestion( + condition=eligibility_response.ConditionName("ConditionA"), + status=eligibility_response.Status.actionable, + statusText=eligibility_response.StatusText("Go ahead"), eligibilityCohorts=[], suitabilityRules=[], actions=[ - eligibility.Action( - actionType=eligibility.ActionType("TYPE_A"), - actionCode=eligibility.ActionCode("CODE123"), - description=eligibility.Description("Contact GP"), - urlLink=eligibility.UrlLink("https://example.dummy/"), - urlLabel=eligibility.UrlLabel("GP contact"), + eligibility_response.Action( + actionType=eligibility_response.ActionType("TYPE_A"), + actionCode=eligibility_response.ActionCode("CODE123"), + description=eligibility_response.Description("Contact GP"), + urlLink=eligibility_response.UrlLink("https://example.dummy/"), + urlLabel=eligibility_response.UrlLabel("GP contact"), ) ], )