From 52cfd3718f8cbf6dd6d6e8ffaec873b04a050088 Mon Sep 17 00:00:00 2001 From: Lukas Holecek Date: Mon, 4 Mar 2024 15:02:22 +0100 Subject: [PATCH] Upgrade to Pydantic 2 --- poetry.lock | 279 ++++++++++++++++++++------------ pyproject.toml | 10 +- resultsdb/__init__.py | 15 ++ resultsdb/config.py | 2 + resultsdb/controllers/api_v3.py | 32 ++-- resultsdb/parsers/api_v2.py | 89 +++++----- resultsdb/parsers/api_v3.py | 117 +++++++++----- testing/functest_api_v20.py | 162 ++++++++++--------- testing/test_api_v3.py | 82 +++++----- 9 files changed, 459 insertions(+), 329 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8a4b6e1..cd6e64f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "alembic" @@ -19,6 +19,17 @@ typing-extensions = ">=4" [package.extras] tz = ["backports.zoneinfo"] +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "attrs" version = "23.2.0" @@ -630,34 +641,34 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-pydantic" -version = "0.11.0" +version = "0.12.0" description = "Flask extension for integration with Pydantic library" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Flask-Pydantic-0.11.0.tar.gz", hash = "sha256:eef679b24e34ffb02e24e1d998ac023007aa2b531927fdc2f9b8114a12c19bb4"}, - {file = "Flask_Pydantic-0.11.0-py3-none-any.whl", hash = "sha256:c5a6283d1227631683ceb72a41f652036829476fbb6e7b6ccef8cae8ed51ebab"}, + {file = "Flask-Pydantic-0.12.0.tar.gz", hash = "sha256:b80f18cc7efe332b47eafbae141541e9875777132ba30d7c95f9fef9bf2a6977"}, + {file = "Flask_Pydantic-0.12.0-py3-none-any.whl", hash = "sha256:c80825d74eefa137d8d7f2396f6b376ee128329196d79eff1442bcfd816a6228"}, ] [package.dependencies] Flask = "*" -pydantic = ">=1.7" +pydantic = ">=2.0" [[package]] name = "flask-pyoidc" -version = "3.14.0" +version = "3.14.3" description = "Flask extension for OpenID Connect authentication." optional = false python-versions = "*" files = [ - {file = "Flask-pyoidc-3.14.0.tar.gz", hash = "sha256:a3f14943ec48ad547a2a08aff5ac2362b1aea23d3a64337e6f0d57385dca1338"}, - {file = "Flask_pyoidc-3.14.0-py3-none-any.whl", hash = "sha256:5dbf017b88739ecf55281fdb58be79704aa6487589a60597b91b14007c7dbc2f"}, + {file = "Flask-pyoidc-3.14.3.tar.gz", hash = "sha256:4a42589f76733c3968ac5f99595c1525a682754916c34f7c029acee6c8ce3bb7"}, + {file = "Flask_pyoidc-3.14.3-py3-none-any.whl", hash = "sha256:a29b7c7660aed9e7b602bfe3cf1d2e5b4e5bc52448cb58babffe2ed10a74cac5"}, ] [package.dependencies] Flask = "*" importlib-resources = "*" -oic = "1.6.0" +oic = "1.6.1" requests = "*" [[package]] @@ -1127,13 +1138,13 @@ files = [ [[package]] name = "oic" -version = "1.6.0" +version = "1.6.1" description = "Python implementation of OAuth2 and OpenID Connect" optional = false python-versions = "~=3.7" files = [ - {file = "oic-1.6.0-py3-none-any.whl", hash = "sha256:f82e087e0ffaba2194ebd24694721a25167d5d467fb06bf4f4da9f48d43e0cc6"}, - {file = "oic-1.6.0.tar.gz", hash = "sha256:2de3b83f1299dda8ed0460baad8bb2d4c6ac8bfc08a220c768b7e6e754caf9e7"}, + {file = "oic-1.6.1-py3-none-any.whl", hash = "sha256:fcbf948a22e4d4df66f6bf57d327933f32a7b539640d9b42883457634360ba78"}, + {file = "oic-1.6.1.tar.gz", hash = "sha256:385a1f64bb59519df1e23840530921bf416740240f505ea6d161e331d3d39fad"}, ] [package.dependencies] @@ -1141,7 +1152,7 @@ cryptography = "*" defusedxml = "*" mako = "*" pycryptodomex = "*" -pydantic = "*" +pydantic-settings = "*" pyjwkest = ">=1.3.6" requests = "*" @@ -1614,55 +1625,132 @@ files = [ [[package]] name = "pydantic" -version = "1.10.14" -description = "Data validation and settings management using python type hints" +version = "2.6.3" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, - {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, - {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, - {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, - {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, - {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, - {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, - {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, - {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, - {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, - {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, - {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, - {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, - {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, - {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, - {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, - {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, - {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, - {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, - {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, - {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, - {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, - {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, + {file = "pydantic-2.6.3-py3-none-any.whl", hash = "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a"}, + {file = "pydantic-2.6.3.tar.gz", hash = "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.16.3" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, + {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, + {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, + {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, + {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, + {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, + {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, + {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, + {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, + {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, + {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, + {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, + {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, + {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, + {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, + {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, + {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, + {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, + {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, + {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, + {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, + {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, + {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, + {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, + {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, + {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, + {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, + {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, + {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, + {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.2.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, +] + +[package.dependencies] +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pyjwkest" @@ -1757,6 +1845,20 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "python-ldap" version = "3.4.4" @@ -2002,60 +2104,23 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.27" +version = "2.0.28" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a3d4e7a472bfff2d28db838669fc437964e8af8df8ee1e4548e92710929adc"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3dd67b5d69794cfe82862c002512683b3db038b99002171f624712fa71aeaa"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0315d9125a38026227f559488fe7f7cee1bd2fbc19f9fd637739dc50bb6380b2"}, + {file = "SQLAlchemy-2.0.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81ba314a08c7ab701e621b7ad079c0c933c58cdef88593c59b90b996e8b58fa5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3499008ddec83127ab286c6f6ec82a34f39c9817f020f75eca96155f9765097"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b66fcd38659cab5d29e8de5409cdf91e9986817703e1078b2fdaad731ea66f5"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:124202b4e0edea7f08a4db8c81cc7859012f90a0d14ba2bf07c099aff6e96462"}, + {file = "SQLAlchemy-2.0.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b6303bfd78fb3221847723104d152e5972c22367ff66edf09120fcde5ddc2e2"}, + {file = "SQLAlchemy-2.0.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e91b5e341f8c7f1e5020db8e5602f3ed045a29f8e27f7f565e0bdee3338f2c7"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:843a882cadebecc655a68bd9a5b8aa39b3c52f4a9a5572a3036fb1bb2ccdc197"}, + {file = "SQLAlchemy-2.0.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dbb990612c36163c6072723523d2be7c3eb1517bbdd63fe50449f56afafd1133"}, + {file = "SQLAlchemy-2.0.28.tar.gz", hash = "sha256:dd53b6c4e6d960600fd6532b79ee28e2da489322fcf6648738134587faf767b6"}, ] [package.dependencies] @@ -2468,4 +2533,4 @@ test = ["pytest", "pytest-cov", "tox", "tox-docker"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "f1df9b5df9295f911e6882ffacbc10e8aa94a9c106dd005ed0c0653f9b3bd273" +content-hash = "24220a0851315c6ce681399da93cc26fa9ccc1f42c65e1529cd4e29a8eebd162" diff --git a/pyproject.toml b/pyproject.toml index f5a9c9e..c688a26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,16 +37,12 @@ SQLAlchemy = {version = "^2.0.24"} psycopg2-binary = {version = "^2.9.7"} alembic = "^1.13.1" iso8601 = "^2.1.0" -pydantic = "^1.10.14" -Flask-Pydantic = "^0.11.0" +pydantic = "^2.6.3" +Flask-Pydantic = "^0.12.0" email-validator = "^2.1.1" python-ldap = "^3.4.3" -Flask-pyoidc = "^3.14.0" -# dependency of Flask-pyoidc; -# oic-1.6.1 is compatible only with pydantic 2 -# whereas Flask-Pydantic needs older versions -oic = {version = "<=1.6.0", optional = true} +Flask-pyoidc = "^3.14.3" Flask-Session = "^0.6.0" # tracing support diff --git a/resultsdb/__init__.py b/resultsdb/__init__.py index 0631caa..98a6289 100644 --- a/resultsdb/__init__.py +++ b/resultsdb/__init__.py @@ -25,6 +25,7 @@ import os from flask import Flask, current_app, jsonify, send_from_directory, session +from flask_pydantic.exceptions import ValidationError from flask_pyoidc import OIDCAuthentication from flask_pyoidc.provider_configuration import ( ClientMetadata, @@ -52,6 +53,8 @@ except NameError: basestring = (str, bytes) +VALIDATION_KEYS = frozenset({"input", "loc", "msg", "type", "url"}) + def create_app(config_obj=None): app = Flask(__name__) @@ -225,6 +228,18 @@ def bad_gateway(error): app.logger.error("External error received: %s", error) return jsonify({"message": "Bad Gateway"}), 502 + app.register_error_handler(ValidationError, handle_validation_error) + + +def handle_validation_error(error: ValidationError): + errors = error.body_params or error.form_params or error.path_params or error.query_params + # Keep only interesting stuff and remove objects potentially + # unserializable in JSON. + err = [{k: v for k, v in e.items() if k in VALIDATION_KEYS} for e in errors] + response = jsonify({"validation_error": err}) + response.status_code = 400 + return response + def init_session(app): app.config["SESSION_SQLALCHEMY"] = db diff --git a/resultsdb/config.py b/resultsdb/config.py index d5eca68..14e8ee7 100644 --- a/resultsdb/config.py +++ b/resultsdb/config.py @@ -44,6 +44,8 @@ class Config(object): SQLALCHEMY_DATABASE_URI = "sqlite://" SHOW_DB_URI = True + FLASK_PYDANTIC_VALIDATION_ERROR_RAISE = True + LOGGING = { "version": 1, "disable_existing_loggers": False, diff --git a/resultsdb/controllers/api_v3.py b/resultsdb/controllers/api_v3.py index 9f36cac..24e1710 100644 --- a/resultsdb/controllers/api_v3.py +++ b/resultsdb/controllers/api_v3.py @@ -2,7 +2,7 @@ from flask import Blueprint, jsonify, render_template from flask import current_app as app from flask_pydantic import validate -from pydantic import BaseModel +from pydantic import RootModel from resultsdb.models import db from resultsdb.authorization import match_testcase_permissions, verify_authorization @@ -22,20 +22,6 @@ api = Blueprint("api_v3", __name__) -def ensure_dict_input(cls): - """ - Wraps Pydantic model to ensure that the input type is dict. - - This is a workaround for a bug in flask-pydantic that causes validation to - fail with unexpected exception. - """ - - class EnsureJsonObject(BaseModel): - __root__: cls - - return EnsureJsonObject - - def permissions(): return app.config.get("PERMISSIONS", []) @@ -58,13 +44,15 @@ def create_result(body: ResultParamsBase): app.logger.debug( "Updating ref_url for testcase %s: %s", body.testcase, body.testcase_ref_url ) - testcase.ref_url = body.testcase_ref_url + testcase.ref_url = str(body.testcase_ref_url) db.session.add(testcase) + ref_url = str(body.ref_url) if body.ref_url else None + result = Result( testcase=testcase, outcome=body.outcome, - ref_url=body.ref_url, + ref_url=ref_url, note=body.note, groups=[], ) @@ -83,8 +71,10 @@ def create_endpoint(params_class, oidc, provider): @oidc.token_auth(provider) @validate() - def create(body: ensure_dict_input(params_class)): - return create_result(body) + # Using RootModel is a workaround for a bug in flask-pydantic that causes + # validation to fail with unexpected exception. + def create(body: RootModel[params_class]): + return create_result(body.root) def get_schema(): return jsonify(params.construct().schema()), 200 @@ -126,8 +116,8 @@ def index(): "method": "POST", "description": example.__doc__, "query_type": "JSON", - "example": example.json(exclude_unset=True, indent=2), - "schema": example.schema(), + "example": example.model_dump_json(exclude_unset=True, indent=2), + "schema": example.model_json_schema(), "schema_endpoint": f".schemas_{example.artifact_type()}s", } for example in examples diff --git a/resultsdb/parsers/api_v2.py b/resultsdb/parsers/api_v2.py index a1a7f6c..748c877 100644 --- a/resultsdb/parsers/api_v2.py +++ b/resultsdb/parsers/api_v2.py @@ -1,10 +1,18 @@ # SPDX-License-Identifier: LGPL-2.0-or-later from datetime import datetime, timezone from numbers import Number -from typing import Any, List, Optional +from typing import Any, List, Optional, Union +from typing_extensions import Annotated import iso8601 -from pydantic import BaseModel, Field, validator +from pydantic import ( + AfterValidator, + BaseModel, + Field, + StringConstraints, + ValidationInfo, + field_validator, +) from pydantic.types import constr from resultsdb.models.results import result_outcomes @@ -41,49 +49,48 @@ class BaseListParams(BaseModel): class GroupsParams(BaseListParams): - uuid: Optional[str] - description: Optional[str] - description_like_: Optional[str] = Field(alias="description:like") + uuid: Optional[str] = None + description: Optional[str] = None + description_like_: Optional[str] = Field(alias="description:like", default=None) class CreateGroupParams(BaseModel): - uuid: Optional[str] - ref_url: Optional[str] - description: Optional[str] + uuid: Optional[str] = None + ref_url: Optional[str] = None + description: Optional[str] = None -class QueryList(List[str]): - @classmethod - def __get_validators__(cls): - yield cls.validate +def validate_query_list(v: Union[str, List[str]], info: ValidationInfo): + if isinstance(v, str): + return [x for x in (x.strip() for x in v.split(",")) if x] + if isinstance(v, list) and len(v) == 1 and isinstance(v[0], str): + return [x for x in (x.strip() for x in v[0].split(",")) if x] + return v - @classmethod - def validate(cls, v): - if isinstance(v, str): - return cls([x for x in (x.strip() for x in v.split(",")) if x]) - if isinstance(v, list) and len(v) == 1 and isinstance(v[0], str): - return cls([x for x in (x.strip() for x in v[0].split(",")) if x]) - return cls(v) + +QueryList = Annotated[Union[str, List[str]], AfterValidator(validate_query_list)] class ResultsParams(BaseListParams): sort_: str = Field(alias="_sort", default="") since: dict = {"start": None, "end": None} - outcome: Optional[QueryList] - groups: Optional[QueryList] - testcases: Optional[QueryList] - testcases_like_: Optional[QueryList] = Field(alias="testcases:like") - distinct_on_: Optional[QueryList] = Field(alias="_distinct_on") + outcome: Optional[QueryList] = None + groups: Optional[QueryList] = None + testcases: Optional[QueryList] = None + testcases_like_: Optional[QueryList] = Field(alias="testcases:like", default=None) + distinct_on_: Optional[QueryList] = Field(alias="_distinct_on", default=None) - @validator("since", pre=True) + @field_validator("since", mode="before") + @classmethod def parse_since(cls, v): try: - s, e = parse_since(v[0]) + s, e = parse_since(v) except iso8601.iso8601.ParseError: raise ValueError("must be in ISO8601 format") return {"start": s, "end": e} - @validator("outcome") + @field_validator("outcome", mode="after") + @classmethod def outcome_must_be_valid(cls, v): outcomes = [x.upper() for x in v] if any(x not in result_outcomes() for x in outcomes): @@ -92,15 +99,16 @@ def outcome_must_be_valid(cls, v): class CreateResultParams(BaseModel): - outcome: constr(min_length=1, strip_whitespace=True, to_upper=True) + outcome: Annotated[str, StringConstraints(min_length=1, strip_whitespace=True, to_upper=True)] testcase: dict - groups: Optional[list] - note: Optional[str] - data: Optional[dict] - ref_url: Optional[str] - submit_time: Any + groups: Optional[list] = None + note: Optional[str] = None + data: Optional[dict] = None + ref_url: Optional[str] = None + submit_time: Any = None - @validator("testcase", pre=True) + @field_validator("testcase", mode="before") + @classmethod def parse_testcase(cls, v): if not v or (isinstance(v, dict) and not v.get("name")): raise ValueError("testcase name must be non-empty") @@ -108,7 +116,8 @@ def parse_testcase(cls, v): return {"name": v} return v - @validator("submit_time", pre=True) + @field_validator("submit_time", mode="before") + @classmethod def parse_submit_time(cls, v): if isinstance(v, datetime): return v @@ -133,13 +142,13 @@ def parse_submit_time(cls, v): " got %r" % v ) - @validator("testcase") + @field_validator("testcase", mode="after") def testcase_must_be_valid(cls, v): if isinstance(v, dict) and not v.get("name"): raise ValueError("testcase name must be non-empty") return v - @validator("outcome") + @field_validator("outcome", mode="after") def outcome_must_be_valid(cls, v): if v not in result_outcomes(): raise ValueError(f'must be one of: {", ".join(result_outcomes())}') @@ -147,10 +156,10 @@ def outcome_must_be_valid(cls, v): class TestcasesParams(BaseListParams): - name: Optional[str] - name_like_: Optional[str] = Field(alias="name:like") + name: Optional[str] = None + name_like_: Optional[str] = Field(alias="name:like", default=None) class CreateTestcaseParams(BaseModel): name: constr(min_length=1) - ref_url: Optional[str] + ref_url: Optional[str] = None diff --git a/resultsdb/parsers/api_v3.py b/resultsdb/parsers/api_v3.py index 95166c8..7671cf8 100644 --- a/resultsdb/parsers/api_v3.py +++ b/resultsdb/parsers/api_v3.py @@ -1,17 +1,18 @@ # SPDX-License-Identifier: GPL-2.0+ from collections.abc import Iterator from textwrap import dedent -from typing import List, Optional +from typing import Annotated, List, Optional from pydantic import ( BaseModel, EmailStr, Field, HttpUrl, - root_validator, - validator, + PlainSerializer, + StringConstraints, + model_validator, + field_validator, ) -from pydantic.types import constr from resultsdb.models.results import result_outcomes @@ -44,6 +45,12 @@ MAX_STRING_SIZE = 8192 +UrlStr = Annotated[ + HttpUrl, + PlainSerializer(lambda x: str(x), when_used="always"), +] + + def result_outcomes_extended(): outcomes = result_outcomes() additional_outcomes = tuple( @@ -74,28 +81,31 @@ class ResultParamsBase(BaseModel): or a CI error (ERROR). """ ) - testcase: constr(min_length=1) = field( + testcase: Annotated[str, StringConstraints(min_length=1)] = field( """ Full test case name. """ ) - testcase_ref_url: Optional[HttpUrl] = field( + testcase_ref_url: Optional[UrlStr] = field( """ Link to documentation for testing events for distributed CI systems to make them sustainable. Should contain information about how to contribute to the specific test, how to reproduce it, ideally on localhost and how to retrigger the test. - """ + """, + default=None, ) note: Optional[str] = field( """ Optional note related to the test result. - """ + """, + default=None, ) - ref_url: Optional[HttpUrl] = field( + ref_url: Optional[UrlStr] = field( """ Specific runner URL. For example a Jenkins build URL. - """ + """, + default=None, ) error_reason: Optional[str] = field( @@ -103,15 +113,17 @@ class ResultParamsBase(BaseModel): Reason of the error. Required with ERROR outcome. - """ + """, + default=None, ) - issue_url: Optional[HttpUrl] = field( + issue_url: Optional[UrlStr] = field( """ If the CI system is able to automatically file an issue/ticket for the error, put the URL here. Only valid with ERROR outcome. - """ + """, + default=None, ) system_provider: Optional[str] = field( @@ -120,21 +132,24 @@ class ResultParamsBase(BaseModel): This can also be hostname of the specific system instance. Examples: openstack, beaker, beaker.example.com, openshift, rhev - """ + """, + default=None, ) system_architecture: Optional[str] = field( """ Architecture of the system/distro used for testing. Examples: x86_64, ppc64le, s390x, aarch64 - """ + """, + default=None, ) system_variant: Optional[str] = field( """ The compose or image variant, if applicable. Examples: Server, Workstation - """ + """, + default=None, ) scenario: Optional[str] = field( @@ -149,7 +164,8 @@ class ResultParamsBase(BaseModel): free form text where the tested item identifier is encoded. Examples: KDE-live-iso x86_64 64bit, Server x86_64 - """ + """, + default=None, ) ci_name: str = field( @@ -168,7 +184,7 @@ class ResultParamsBase(BaseModel): Examples: BaseOS QE, Libvirt QE, RTT, OSCI """ ) - ci_docs: HttpUrl = field( + ci_docs: UrlStr = field( """ Link to documentation with details about the system. """ @@ -178,17 +194,19 @@ class ResultParamsBase(BaseModel): Contact email address. """ ) - ci_url: Optional[HttpUrl] = field( + ci_url: Optional[UrlStr] = field( """ URL link to the system or system's web interface. - """ + """, + default=None, ) ci_irc: Optional[str] = field( """ IRC contact for help (prefix with '#' for channel). Examples: #osci - """ + """, + default=None, ) scratch: Optional[bool] = field( @@ -199,15 +217,17 @@ class ResultParamsBase(BaseModel): """, default=False, ) - rebuild: Optional[HttpUrl] = field( + rebuild: Optional[UrlStr] = field( """ URL to rebuild the run. Usually leads to a separate page with rebuild options. - """ + """, + default=None, ) - log: Optional[HttpUrl] = field( + log: Optional[UrlStr] = field( """ URL of build log. Can be an HTML page - """ + """, + default=None, ) class Config: @@ -228,19 +248,20 @@ def result_data(self) -> Iterator[int]: else: yield (name, str(value)) - @validator("outcome") + @field_validator("outcome", mode="before") + @classmethod def outcome_must_be_valid(cls, v): if v not in result_outcomes_extended(): raise ValueError(f'must be one of: {", ".join(result_outcomes_extended())}') return v - @root_validator - def only_available_for_error_outcome(cls, values): - if (values["error_reason"] is not None or values["issue_url"] is not None) and values.get( - "outcome" - ) != "ERROR": + @model_validator(mode="after") + def only_available_for_error_outcome(self): + if ( + self.error_reason is not None or self.issue_url is not None + ) and self.outcome != "ERROR": raise ValueError("error_reason and issue_url can be only set for ERROR outcome") - return values + return self @classmethod def exclude(cls): @@ -251,7 +272,7 @@ def exclude(cls): class BrewResultParams(ResultParamsBase): """Create new test result for a brew-build.""" - item: constr(min_length=1) = field( + item: Annotated[str, StringConstraints(min_length=1)] = field( """ Name-version-release of the brew-build. """ @@ -279,7 +300,7 @@ def artifact_type(cls) -> str: class RedHatContainerImageResultParams(ResultParamsBase): """Create new test result for a redhat-container-image.""" - item: constr(min_length=1) = field( + item: Annotated[str, StringConstraints(min_length=1)] = field( """ Name-version-release of the container image. """ @@ -317,36 +338,42 @@ class RedHatContainerImageResultParams(ResultParamsBase): brew_task_id: Optional[int] = field( """ Brew task ID of the buildContainer task. - """ + """, + default=None, ) brew_build_id: Optional[int] = field( """ Brew build ID of container. - """ + """, + default=None, ) registry_url: Optional[str] = field( """ Registry url from the container image full name. - """ + """, + default=None, ) tag: Optional[str] = field( """ Tag from the container image full name. - """ + """, + default=None, ) name: Optional[str] = field( """ Name from the container image full name. Example: python-27-rhel8 - """ + """, + default=None, ) namespace: Optional[str] = field( """ Namespace from the container image full name. Example: rhscl - """ + """, + default=None, ) source: Optional[str] = field( """ @@ -357,7 +384,8 @@ class RedHatContainerImageResultParams(ResultParamsBase): scratch build. Example: git+https://github.com/docker/rootfs.git#container:docker - """ + """, + default=None, ) @classmethod @@ -382,7 +410,7 @@ def artifact_type(cls): class RedHatModuleResultParams(ResultParamsBase): "Create new test result for a module." - item: constr(min_length=1) = field( + item: Annotated[str, StringConstraints(min_length=1)] = field( """ Name-version-release of the module """ @@ -403,7 +431,7 @@ def artifact_type(cls): class ProductmdComposeResultParams(ResultParamsBase): "Create new test result for a compose." - id: constr(min_length=1) = field( + id: Annotated[str, StringConstraints(min_length=1)] = field( """ ID of the compose as recorded in the productmd metadata (payload.compose.id field inside the metadata/composeinfo.json file @@ -454,7 +482,8 @@ class PermissionsParams(BaseModel): Filter only permissions matching test case name glob expression. Example: compose.* - """ + """, + default=None, ) diff --git a/testing/functest_api_v20.py b/testing/functest_api_v20.py index 430b563..82b8e3b 100644 --- a/testing/functest_api_v20.py +++ b/testing/functest_api_v20.py @@ -22,7 +22,7 @@ import os import copy from unittest import TestCase -from unittest.mock import patch +from unittest.mock import ANY, patch from flask import current_app as app @@ -151,11 +151,15 @@ def test_create_testcase_missing_data(self): r = self.app.post("/api/v2.0/testcases", data=ref_data, content_type="application/json") assert r.status_code == 400 assert r.json == { - "validation_error": { - "body_params": [ - {"loc": ["name"], "msg": "field required", "type": "value_error.missing"} - ] - } + "validation_error": [ + { + "loc": ["name"], + "msg": "Field required", + "type": "missing", + "input": ANY, + "url": ANY, + } + ] } def test_create_testcase_empty_name(self): @@ -164,16 +168,15 @@ def test_create_testcase_empty_name(self): r = self.app.post("/api/v2.0/testcases", data=ref_data, content_type="application/json") assert r.status_code == 400 assert r.json == { - "validation_error": { - "body_params": [ - { - "ctx": {"limit_value": 1}, - "loc": ["name"], - "msg": "ensure this value has at least 1 characters", - "type": "value_error.any_str.min_length", - } - ] - } + "validation_error": [ + { + "loc": ["name"], + "msg": "String should have at least 1 character", + "type": "string_too_short", + "input": ANY, + "url": ANY, + } + ] } def test_update_testcase(self): @@ -430,15 +433,15 @@ def test_create_result_empty_testcase(self): assert r.status_code == 400 assert data == { - "validation_error": { - "body_params": [ - { - "loc": ["testcase"], - "msg": "testcase name must be non-empty", - "type": "value_error", - } - ] - } + "validation_error": [ + { + "loc": ["testcase"], + "msg": "Value error, testcase name must be non-empty", + "type": "value_error", + "input": ANY, + "url": ANY, + } + ] } def test_create_result_empty_testcase_name(self): @@ -449,15 +452,15 @@ def test_create_result_empty_testcase_name(self): assert r.status_code == 400 assert data == { - "validation_error": { - "body_params": [ - { - "loc": ["testcase"], - "msg": "testcase name must be non-empty", - "type": "value_error", - } - ] - } + "validation_error": [ + { + "loc": ["testcase"], + "msg": "Value error, testcase name must be non-empty", + "type": "value_error", + "input": ANY, + "url": ANY, + } + ] } def test_create_result_empty_testcase_dict(self): @@ -466,15 +469,15 @@ def test_create_result_empty_testcase_dict(self): assert r.status_code == 400 assert data == { - "validation_error": { - "body_params": [ - { - "loc": ["testcase"], - "msg": "testcase name must be non-empty", - "type": "value_error", - } - ] - } + "validation_error": [ + { + "loc": ["testcase"], + "msg": "Value error, testcase name must be non-empty", + "type": "value_error", + "input": ANY, + "url": ANY, + } + ] } def test_create_result_missing_testcase(self): @@ -483,11 +486,15 @@ def test_create_result_missing_testcase(self): assert r.status_code == 400 assert data == { - "validation_error": { - "body_params": [ - {"loc": ["testcase"], "msg": "field required", "type": "value_error.missing"} - ] - } + "validation_error": [ + { + "loc": ["testcase"], + "msg": "Field required", + "type": "missing", + "input": ANY, + "url": ANY, + } + ] } def test_create_result_missing_outcome(self): @@ -497,11 +504,15 @@ def test_create_result_missing_outcome(self): assert r.status_code == 400 assert data == { - "validation_error": { - "body_params": [ - {"loc": ["outcome"], "msg": "field required", "type": "value_error.missing"} - ] - } + "validation_error": [ + { + "loc": ["outcome"], + "msg": "Field required", + "type": "missing", + "input": ANY, + "url": ANY, + } + ] } def test_create_result_multiple_groups(self): @@ -584,15 +595,18 @@ def test_create_result_invalid_outcome(self): assert r.status_code == 400 assert data == { - "validation_error": { - "body_params": [ - { - "loc": ["outcome"], - "msg": "must be one of: PASSED, INFO, FAILED, NEEDS_INSPECTION, AMAZING", - "type": "value_error", - } - ] - } + "validation_error": [ + { + "loc": ["outcome"], + "msg": ( + "Value error, must be one of:" + " PASSED, INFO, FAILED, NEEDS_INSPECTION, AMAZING" + ), + "type": "value_error", + "input": ANY, + "url": ANY, + } + ] } def test_create_result_invalid_data(self): @@ -670,18 +684,18 @@ def test_create_result_submit_time_as_invalid(self): assert r.status_code == 400, data assert data == { - "validation_error": { - "body_params": [ - { - "loc": ["submit_time"], - "msg": ( - "Expected timestamp in milliseconds or datetime" - " (in format YYYY-MM-DDTHH:MM:SS.ffffff), got 'now'" - ), - "type": "value_error", - } - ] - } + "validation_error": [ + { + "loc": ["submit_time"], + "msg": ( + "Value error, Expected timestamp in milliseconds or datetime" + " (in format YYYY-MM-DDTHH:MM:SS.ffffff), got 'now'" + ), + "type": "value_error", + "input": ANY, + "url": ANY, + } + ] } def test_get_result(self): diff --git a/testing/test_api_v3.py b/testing/test_api_v3.py index 57dcced..0a5e160 100644 --- a/testing/test_api_v3.py +++ b/testing/test_api_v3.py @@ -88,6 +88,7 @@ def test_api_v3_create_brew_build_full(client): data = brew_build_request_data( outcome="ERROR", testcase_ref_url="https://test.example.com/docs/testcase1", + ref_url="https://test.example.com/runner/100", error_reason="Some error", issue_url="https://issues.example.com/1", system_provider="openstack", @@ -95,6 +96,7 @@ def test_api_v3_create_brew_build_full(client): system_variant="Server", ci_url="https://test.example.com/ci", ci_irc="#testing", + ci_email="test@example.com", rebuild="https://test.example.com/ci/builds/1/rebuild", log="https://test.example.com/ci/builds/1/log", ) @@ -105,6 +107,7 @@ def test_api_v3_create_brew_build_full(client): "name": "testcase1", "ref_url": "https://test.example.com/docs/testcase1", } + assert r.json["ref_url"] == data["ref_url"] assert r.json["data"]["error_reason"] == [data["error_reason"]] assert r.json["data"]["issue_url"] == [data["issue_url"]] assert r.json["data"]["system_provider"] == [data["system_provider"]] @@ -112,6 +115,7 @@ def test_api_v3_create_brew_build_full(client): assert r.json["data"]["system_variant"] == [data["system_variant"]] assert r.json["data"]["ci_url"] == [data["ci_url"]] assert r.json["data"]["ci_irc"] == [data["ci_irc"]] + assert r.json["data"]["ci_email"] == [data["ci_email"]] assert r.json["data"]["rebuild"] == [data["rebuild"]] assert r.json["data"]["log"] == [data["log"]] @@ -421,15 +425,17 @@ def test_api_v3_bad_param_type_int(params_class, client): r = client.post(f"/api/v3/results/{artifact_type}s", json=0) assert r.status_code == 400, r.text assert r.json == { - "validation_error": { - "body_params": [ - { - "loc": ["__root__"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } + "validation_error": [ + { + "input": 0, + "msg": ( + f"Input should be a valid dictionary or instance of {params_class.__name__}" + ), + "type": "model_type", + "loc": [], + "url": ANY, + } + ] } @@ -442,15 +448,17 @@ def test_api_v3_bad_param_type_str(params_class, client): r = client.post(f"/api/v3/results/{artifact_type}s", json="BAD") assert r.status_code == 400, r.text assert r.json == { - "validation_error": { - "body_params": [ - { - "loc": ["__root__"], - "msg": "value is not a valid dict", - "type": "type_error.dict", - } - ] - } + "validation_error": [ + { + "input": "BAD", + "msg": ( + f"Input should be a valid dictionary or instance of {params_class.__name__}" + ), + "type": "model_type", + "loc": [], + "url": ANY, + } + ] } @@ -465,15 +473,17 @@ def test_api_v3_bad_param_type_null(params_class, client): ) assert r.status_code == 400, r.text assert r.json == { - "validation_error": { - "body_params": [ - { - "loc": ["__root__"], - "msg": "none is not an allowed value", - "type": "type_error.none.not_allowed", - } - ] - } + "validation_error": [ + { + "input": None, + "msg": ( + f"Input should be a valid dictionary or instance of {params_class.__name__}" + ), + "type": "model_type", + "loc": [], + "url": ANY, + } + ] } @@ -510,13 +520,13 @@ def test_api_v3_missing_param(params_class, client): r = client.post(f"/api/v3/results/{artifact_type}s", json=example) assert r.status_code == 400, r.text assert r.json == { - "validation_error": { - "body_params": [ - { - "loc": ["__root__", "outcome"], - "msg": "field required", - "type": "value_error.missing", - } - ] - } + "validation_error": [ + { + "loc": ["outcome"], + "msg": "Field required", + "type": "missing", + "input": ANY, + "url": ANY, + } + ] }