diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2fc3d42..bc4dccf 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.11.0 +current_version = 1.12.0 commit = False tag = False diff --git a/poetry.lock b/poetry.lock index 8bcc700..fba9a7d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -197,6 +197,18 @@ files = [ {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, ] +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + [[package]] name = "anyio" version = "4.12.1" @@ -968,26 +980,26 @@ develop = ["elastic-apm", "mock", "pytest", "pytest-cov", "structlog"] [[package]] name = "fastapi" -version = "0.125.0" +version = "0.128.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "fastapi-0.125.0-py3-none-any.whl", hash = "sha256:2570ec4f3aecf5cca8f0428aed2398b774fcdfee6c2116f86e80513f2f86a7a1"}, - {file = "fastapi-0.125.0.tar.gz", hash = "sha256:16b532691a33e2c5dee1dac32feb31dc6eb41a3dd4ff29a95f9487cb21c054c0"}, + {file = "fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d"}, + {file = "fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a"}, ] [package.dependencies] annotated-doc = ">=0.0.2" -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +pydantic = ">=2.7.0" starlette = ">=0.40.0,<0.51.0" typing-extensions = ">=4.8.0" [package.extras] all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] -standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "filelock" @@ -1448,14 +1460,14 @@ vector = ["http"] [[package]] name = "libpvarki" -version = "1.9.1" +version = "2.0.1" description = "Common helpers like standard logging init" optional = false python-versions = ">=3.8,<4.0" groups = ["main"] files = [ - {file = "libpvarki-1.9.1-py3-none-any.whl", hash = "sha256:de61747063e5f8912b069dbf6fa7bebe0daac71253462265bd670a143f202885"}, - {file = "libpvarki-1.9.1.tar.gz", hash = "sha256:bbfd7bd9b764d15371acc77421b5db9653a709adf652c83f498541d9b7734be4"}, + {file = "libpvarki-2.0.1-py3-none-any.whl", hash = "sha256:ab45cfd750ff222e3d2d7286761c9d3d7221ddb732d82a8b1287b1bbbf4e2a93"}, + {file = "libpvarki-2.0.1.tar.gz", hash = "sha256:81ee0dcf8ab5bc52302a329ea901793fce214674b36303d2f8ebe310d9692d9b"}, ] [package.dependencies] @@ -1465,8 +1477,7 @@ brotli = ">=1.0,<2.0" cryptography = ">=41.0" ecs-logging = ">=2.0,<3.0" fastapi = ">0.89,<1.0" -libadvian = ">=1.4,<2.0" -pydantic = ">=1.10,<2.0" +pydantic = ">=2.0,<3.0" pyopenssl = ">=23.2" [package.source] @@ -2387,73 +2398,183 @@ files = [ [[package]] name = "pydantic" -version = "1.10.26" -description = "Data validation and settings management using python type hints" +version = "2.12.5" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-1.10.26-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7ae36fa0ecef8d39884120f212e16c06bb096a38f523421278e2f39c1784546"}, - {file = "pydantic-1.10.26-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d95a76cf503f0f72ed7812a91de948440b2bf564269975738a4751e4fadeb572"}, - {file = "pydantic-1.10.26-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a943ce8e00ad708ed06a1d9df5b4fd28f5635a003b82a4908ece6f24c0b18464"}, - {file = "pydantic-1.10.26-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:465ad8edb29b15c10b779b16431fe8e77c380098badf6db367b7a1d3e572cf53"}, - {file = "pydantic-1.10.26-cp310-cp310-win_amd64.whl", hash = "sha256:80e6be6272839c8a7641d26ad569ab77772809dd78f91d0068dc0fc97f071945"}, - {file = "pydantic-1.10.26-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:116233e53889bcc536f617e38c1b8337d7fa9c280f0fd7a4045947515a785637"}, - {file = "pydantic-1.10.26-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3cfdd361addb6eb64ccd26ac356ad6514cee06a61ab26b27e16b5ed53108f77"}, - {file = "pydantic-1.10.26-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e4451951a9a93bf9a90576f3e25240b47ee49ab5236adccb8eff6ac943adf0f"}, - {file = "pydantic-1.10.26-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9858ed44c6bea5f29ffe95308db9e62060791c877766c67dd5f55d072c8612b5"}, - {file = "pydantic-1.10.26-cp311-cp311-win_amd64.whl", hash = "sha256:ac1089f723e2106ebde434377d31239e00870a7563245072968e5af5cc4d33df"}, - {file = "pydantic-1.10.26-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:468d5b9cacfcaadc76ed0a4645354ab6f263ec01a63fb6d05630ea1df6ae453f"}, - {file = "pydantic-1.10.26-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c1b0b914be31671000ca25cf7ea17fcaaa68cfeadf6924529c5c5aa24b7ab1f"}, - {file = "pydantic-1.10.26-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15b13b9f8ba8867095769e1156e0d7fbafa1f65b898dd40fd1c02e34430973cb"}, - {file = "pydantic-1.10.26-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad7025ca324ae263d4313998e25078dcaec5f9ed0392c06dedb57e053cc8086b"}, - {file = "pydantic-1.10.26-cp312-cp312-win_amd64.whl", hash = "sha256:4482b299874dabb88a6c3759e3d85c6557c407c3b586891f7d808d8a38b66b9c"}, - {file = "pydantic-1.10.26-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ae7913bb40a96c87e3d3f6fe4e918ef53bf181583de4e71824360a9b11aef1c"}, - {file = "pydantic-1.10.26-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8154c13f58d4de5d3a856bb6c909c7370f41fb876a5952a503af6b975265f4ba"}, - {file = "pydantic-1.10.26-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8af0507bf6118b054a9765fb2e402f18a8b70c964f420d95b525eb711122d62"}, - {file = "pydantic-1.10.26-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dcb5a7318fb43189fde6af6f21ac7149c4bcbcfffc54bc87b5becddc46084847"}, - {file = "pydantic-1.10.26-cp313-cp313-win_amd64.whl", hash = "sha256:71cde228bc0600cf8619f0ee62db050d1880dcc477eba0e90b23011b4ee0f314"}, - {file = "pydantic-1.10.26-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6b40730cc81d53d515dc0b8bb5c9b43fadb9bed46de4a3c03bd95e8571616dba"}, - {file = "pydantic-1.10.26-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c3bbb9c0eecdf599e4db9b372fa9cc55be12e80a0d9c6d307950a39050cb0e37"}, - {file = "pydantic-1.10.26-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc2e3fe7bc4993626ef6b6fa855defafa1d6f8996aa1caef2deb83c5ac4d043a"}, - {file = "pydantic-1.10.26-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:36d9e46b588aaeb1dcd2409fa4c467fe0b331f3cc9f227b03a7a00643704e962"}, - {file = "pydantic-1.10.26-cp314-cp314-win_amd64.whl", hash = "sha256:81ce3c8616d12a7be31b4aadfd3434f78f6b44b75adbfaec2fe1ad4f7f999b8c"}, - {file = "pydantic-1.10.26-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc5c91a3b3106caf07ac6735ec6efad8ba37b860b9eb569923386debe65039ad"}, - {file = "pydantic-1.10.26-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dde599e0388e04778480d57f49355c9cc7916de818bf674de5d5429f2feebfb6"}, - {file = "pydantic-1.10.26-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8be08b5cfe88e58198722861c7aab737c978423c3a27300911767931e5311d0d"}, - {file = "pydantic-1.10.26-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0141f4bafe5eda539d98c9755128a9ea933654c6ca4306b5059fc87a01a38573"}, - {file = "pydantic-1.10.26-cp38-cp38-win_amd64.whl", hash = "sha256:eb664305ffca8a9766a8629303bb596607d77eae35bb5f32ff9245984881b638"}, - {file = "pydantic-1.10.26-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:502b9d30d18a2dfaf81b7302f6ba0e5853474b1c96212449eb4db912cb604b7d"}, - {file = "pydantic-1.10.26-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0d8f6087bf697dec3bf7ffcd7fe8362674f16519f3151789f33cbe8f1d19fc15"}, - {file = "pydantic-1.10.26-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dd40a99c358419910c85e6f5d22f9c56684c25b5e7abc40879b3b4a52f34ae90"}, - {file = "pydantic-1.10.26-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ce3293b86ca9f4125df02ff0a70be91bc7946522467cbd98e7f1493f340616ba"}, - {file = "pydantic-1.10.26-cp39-cp39-win_amd64.whl", hash = "sha256:1a4e3062b71ab1d5df339ba12c48f9ed5817c5de6cb92a961dd5c64bb32e7b96"}, - {file = "pydantic-1.10.26-py3-none-any.whl", hash = "sha256:c43ad70dc3ce7787543d563792426a16fd7895e14be4b194b5665e36459dd917"}, - {file = "pydantic-1.10.26.tar.gz", hash = "sha256:8c6aa39b494c5af092e690127c283d84f363ac36017106a9e66cb33a22ac412e"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.6.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] -name = "pydantic-collections" -version = "0.6.0" -description = "Collections of pydantic models" +name = "pydantic-core" +version = "2.41.5" +description = "Core functionality for Pydantic validation and serialization" optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, +] + +[package.dependencies] +typing-extensions = ">=4.14.1" + +[[package]] +name = "pydantic-settings" +version = "2.12.0" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pydantic_collections-0.6.0-py3-none-any.whl", hash = "sha256:ec559722abf6a0f80e6f00b3d28f0f39c0ed5feb1641166230eb75e9da880162"}, - {file = "pydantic_collections-0.6.0.tar.gz", hash = "sha256:c34d3fd1df5600b315cdecdd8e74eacd4c8c607b7e3f2c9392b2a15850a4ef9e"}, + {file = "pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809"}, + {file = "pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0"}, ] [package.dependencies] -pydantic = ">=1.8.2,<3.0" -typing-extensions = ">=4.7.1" +pydantic = ">=2.7.0" +python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" + +[package.extras] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] +azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pygments" @@ -3030,18 +3151,18 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlmodel" -version = "0.0.30" +version = "0.0.31" description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "sqlmodel-0.0.30-py3-none-any.whl", hash = "sha256:1b7f992170ff3145d98a72d41271335c8bb51d579c7254a1626f82bde4ec64ed"}, - {file = "sqlmodel-0.0.30.tar.gz", hash = "sha256:076076976aac683bae3e10c791d9e2ec5fead57ab25f7e476dea36cdfe9505e1"}, + {file = "sqlmodel-0.0.31-py3-none-any.whl", hash = "sha256:6d946d56cac4c2db296ba1541357cee2e795d68174e2043cd138b916794b1513"}, + {file = "sqlmodel-0.0.31.tar.gz", hash = "sha256:2d41a8a9ee05e40736e2f9db8ea28cbfe9b5d4e5a18dd139e80605025e0c516c"}, ] [package.dependencies] -pydantic = ">=1.10.13,<3.0.0" +pydantic = ">=2.7.0" SQLAlchemy = ">=2.0.14,<2.1.0" [[package]] @@ -3154,6 +3275,21 @@ files = [ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + [[package]] name = "tzdata" version = "2025.3" @@ -3637,4 +3773,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = "^3.11" -content-hash = "c6eebd9e74f72afab411c3f9db8873c556e3536618f5749146ad7ead78d24343" +content-hash = "59d82c91e71f7ad2991a958fcc44fc794d377ea0626bf3dd036ed6cd7bf0bc9d" diff --git a/pyproject.toml b/pyproject.toml index e74ea3b..d84f1aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rasenmaeher_api" -version = "1.11.0" +version = "1.12.0" description = "python-rasenmaeher-api" authors = [ "Aciid <703382+Aciid@users.noreply.github.com>", @@ -79,16 +79,14 @@ python = "^3.11" libadvian = "^1.6" click = "^8.0" fastapi = ">=0.89,<1.0" # caret behaviour on 0.x is to lock to 0.x.* -# FIXME: Migrate to v2, see https://docs.pydantic.dev/2.3/migration/#basesettings-has-moved-to-pydantic-settings -pydantic= ">=1.10,<2.0" -pydantic-collections = ">=0.5,<1.0" # caret behaviour on 0.x is to lock to 0.x.* +pydantic = ">=2.0,<3.0" +pydantic-settings = ">=2.0,<3.0" requests = "^2.31" multikeyjwt = "^1.0" uvicorn = {version = "^0.20", extras = ["standard"]} gunicorn = "^20.1" pyopenssl = "^23.1" -# Can't update to 2.0 before pydantic migration is done -libpvarki = { version="^1.9", source="nexuslocal"} +libpvarki = { version="^2.0", source="nexuslocal"} openapi-readme = "^0.2" python-multipart = ">=0.0.21,<1.0.0" aiohttp = ">=3.11.10,<4.0" diff --git a/src/rasenmaeher_api/__init__.py b/src/rasenmaeher_api/__init__.py index 02fc8b4..bb352e4 100644 --- a/src/rasenmaeher_api/__init__.py +++ b/src/rasenmaeher_api/__init__.py @@ -1,3 +1,3 @@ """python-rasenmaeher-api""" -__version__ = "1.11.0" # NOTE Use `bump2version --config-file patch` to bump versions correctly +__version__ = "1.12.0" # NOTE Use `bump2version --config-file patch` to bump versions correctly diff --git a/src/rasenmaeher_api/db/base.py b/src/rasenmaeher_api/db/base.py index 09d4db5..8529cd4 100644 --- a/src/rasenmaeher_api/db/base.py +++ b/src/rasenmaeher_api/db/base.py @@ -18,7 +18,7 @@ LOGGER = logging.getLogger(__name__) -class ORMBaseModel(SQLModel, table=False): # type: ignore[call-arg] +class ORMBaseModel(SQLModel, table=False): """Baseclass with common fields""" __table_args__ = {"schema": "raesenmaeher"} diff --git a/src/rasenmaeher_api/db/enrollments.py b/src/rasenmaeher_api/db/enrollments.py index bf22716..e3bf0d4 100644 --- a/src/rasenmaeher_api/db/enrollments.py +++ b/src/rasenmaeher_api/db/enrollments.py @@ -34,7 +34,7 @@ def generate_code() -> str: return code -class EnrollmentPool(ORMBaseModel, table=True): # type: ignore[call-arg] +class EnrollmentPool(ORMBaseModel, table=True): """Enrollment pools aka links, pk is UUID and comes from basemodel""" __tablename__ = "enrollmentpools" @@ -150,7 +150,7 @@ class EnrollmentState(enum.IntEnum): REJECTED = 2 -class Enrollment(ORMBaseModel, table=True): # type: ignore[call-arg] +class Enrollment(ORMBaseModel, table=True): """Enrollments, pk is UUID and comes from basemodel""" __tablename__ = "enrollments" diff --git a/src/rasenmaeher_api/db/logincodes.py b/src/rasenmaeher_api/db/logincodes.py index c447a02..25cda35 100644 --- a/src/rasenmaeher_api/db/logincodes.py +++ b/src/rasenmaeher_api/db/logincodes.py @@ -20,7 +20,7 @@ CODE_MAX_ATTEMPTS = 100 -class LoginCode(ORMBaseModel, table=True): # type: ignore[call-arg] +class LoginCode(ORMBaseModel, table=True): """Track the login codes that can be exchanged for session JWTs""" __tablename__ = "logincodes" diff --git a/src/rasenmaeher_api/db/nonces.py b/src/rasenmaeher_api/db/nonces.py index c56d7fe..f55b451 100644 --- a/src/rasenmaeher_api/db/nonces.py +++ b/src/rasenmaeher_api/db/nonces.py @@ -14,7 +14,7 @@ LOGGER = logging.getLogger(__name__) -class SeenToken(ORMBaseModel, table=True): # type: ignore[call-arg] +class SeenToken(ORMBaseModel, table=True): """Store tokens we should see used only once""" __tablename__ = "seentokens" diff --git a/src/rasenmaeher_api/db/people.py b/src/rasenmaeher_api/db/people.py index 8e893e7..d33f6e2 100644 --- a/src/rasenmaeher_api/db/people.py +++ b/src/rasenmaeher_api/db/people.py @@ -33,7 +33,7 @@ LOGGER = logging.getLogger(__name__) -class Person(ORMBaseModel, table=True): # type: ignore[call-arg] # pylint: disable=too-many-public-methods +class Person(ORMBaseModel, table=True): # pylint: disable=too-many-public-methods """People, pk is UUID and comes from basemodel NOTE: at some point we want to stop keeping track of people in our own db @@ -380,7 +380,7 @@ async def roles(self) -> AsyncGenerator[str, None]: yield result.role -class Role(SQLModel, table=True): # type: ignore[call-arg] +class Role(SQLModel, table=True): """Give a person a role""" __tablename__ = "roles" @@ -399,7 +399,7 @@ async def post_user_crud(userinfo: UserCRUDRequest, endpoint_suffix: str) -> Non """Wrapper to be more DRY in the basic CRUD things""" endpoint = f"api/v1/users/{endpoint_suffix}" # We can't do anything about any issues with the responses so don't collect them - await post_to_all_products(endpoint, userinfo.dict(), OperationResultResponse, collect_responses=False) + await post_to_all_products(endpoint, userinfo.model_dump(), OperationResultResponse, collect_responses=False) async def user_created(person: Person) -> None: diff --git a/src/rasenmaeher_api/kchelpers.py b/src/rasenmaeher_api/kchelpers.py index e9aba2b..2566326 100644 --- a/src/rasenmaeher_api/kchelpers.py +++ b/src/rasenmaeher_api/kchelpers.py @@ -7,7 +7,7 @@ import json from libpvarki.schemas.product import UserCRUDRequest -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, Field, ConfigDict from keycloak.keycloak_admin import KeycloakAdmin from keycloak.exceptions import KeycloakError @@ -20,16 +20,13 @@ class KCUserData(BaseModel): """Represent KC user object manipulations""" + model_config = ConfigDict(extra="forbid") + productdata: UserCRUDRequest = Field(description="Data that would be sent to productAPIs") roles: Set[str] = Field(default_factory=set, description="Local roles") kc_id: Optional[str] = Field(description="KC id (uuid)", default=None) kc_data: Dict[str, Any] = Field(description="Full KC data", default_factory=dict) - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - # PONDER: Maybe switch to https://python-keycloak.readthedocs.io/en/latest/modules/async.html @dataclass diff --git a/src/rasenmaeher_api/rmsettings.py b/src/rasenmaeher_api/rmsettings.py index ee11b60..44f4f1c 100644 --- a/src/rasenmaeher_api/rmsettings.py +++ b/src/rasenmaeher_api/rmsettings.py @@ -6,7 +6,7 @@ import logging import json -from pydantic import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict LOGGER = logging.getLogger(__name__) @@ -37,12 +37,11 @@ class RMSettings(BaseSettings): # pylint: disable=too-few-public-methods with environment variables. """ - class Config: # pylint: disable=too-few-public-methods - """Configuration of settings.""" - - env_file = ".env" - env_prefix = "RM_" - env_file_encoding = "utf-8" + model_config = SettingsConfigDict( + env_file=".env", + env_prefix="RM_", + env_file_encoding="utf-8", + ) host: str = "127.0.0.1" port: int = 8000 @@ -54,18 +53,21 @@ class Config: # pylint: disable=too-few-public-methods # Current environment environment: str = "dev" - # Set log_level (str) and log_level_int (int) for later use - # if log_level is not set, then log level will be DEBUG + # Set log_level - default is DEBUG log_level: LogLevel = LogLevel.DEBUG - log_level_int: int = logging.DEBUG - if log_level == "INFO": - log_level_int = logging.INFO - elif log_level == "WARNING": - log_level_int = logging.WARNING - elif log_level == "ERROR": - log_level_int = logging.ERROR - elif log_level == "FATAL": - log_level_int = logging.FATAL + + @property + def log_level_int(self) -> int: + """Return the integer log level based on the LogLevel enum""" + level_map = { + LogLevel.NOTSET: logging.NOTSET, + LogLevel.DEBUG: logging.DEBUG, + LogLevel.INFO: logging.INFO, + LogLevel.WARNING: logging.WARNING, + LogLevel.ERROR: logging.ERROR, + LogLevel.FATAL: logging.FATAL, + } + return level_map.get(self.log_level, logging.DEBUG) # Manifest file from kraftwerk integration_api_port: int = 4625 @@ -95,12 +97,12 @@ class Config: # pylint: disable=too-few-public-methods ocsprest_port: str = "8887" cfssl_timeout: float = 2.5 - persistent_data_dir = "/data/persistent" + persistent_data_dir: str = "/data/persistent" # mtls mtls_client_cert_path: Optional[str] = None mtls_client_key_path: Optional[str] = None - mtls_client_cert_cn = "rasenmaeher" + mtls_client_cert_cn: str = "rasenmaeher" # LDAP configuration ldap_conn_string: Optional[str] = None diff --git a/src/rasenmaeher_api/web/api/descriptions.py b/src/rasenmaeher_api/web/api/descriptions.py index cab1988..e7973a9 100644 --- a/src/rasenmaeher_api/web/api/descriptions.py +++ b/src/rasenmaeher_api/web/api/descriptions.py @@ -1,11 +1,10 @@ """product descriptions endpoints""" -from typing import Literal, Optional, cast +from typing import Literal, Optional, List, cast import logging from fastapi import APIRouter, Depends -from pydantic import BaseModel, Extra, Field -from pydantic_collections import BaseCollectionModel +from pydantic import BaseModel, Field, ConfigDict, RootModel from libpvarki.middleware import MTLSHeader from rasenmaeher_api.web.api.middleware.user import ValidUser from ...productapihelpers import get_from_all_products, get_from_product @@ -23,17 +22,14 @@ class ProductDescription(BaseModel): """Description of a product""" + model_config = ConfigDict(extra="forbid") + shortname: str = Field(description="Short name for the product, used as slug/key in dicts and urls") title: str = Field(description="Fancy name for the product") icon: Optional[str] = Field(description="URL for icon") description: str = Field(description="Short-ish description of the product") language: str = Field(description="Language of this response") - class Config: # pylint: disable=too-few-public-methods - """Pydantic configs""" - - extra = Extra.forbid - class ProductComponent(BaseModel): """Product component info""" @@ -45,6 +41,8 @@ class ProductComponent(BaseModel): class ProductDescriptionExtended(BaseModel): """Description of a product""" + model_config = ConfigDict(extra="forbid") + shortname: str = Field(description="Short name for the product, used as slug/key in dicts and urls") title: str = Field(description="Fancy name for the product") icon: Optional[str] = Field(description="URL for icon") @@ -53,29 +51,16 @@ class ProductDescriptionExtended(BaseModel): docs: Optional[str] = Field(description="Link to documentation") component: ProductComponent = Field(description="Component type and ref") - class Config: # pylint: disable=too-few-public-methods - """Pydantic configs""" - - extra = Extra.forbid - -class ProductDescriptionList(BaseCollectionModel[ProductDescription]): # type: ignore[misc] # pylint: disable=too-few-public-methods +class ProductDescriptionList(RootModel[List[ProductDescription]]): # pylint: disable=too-few-public-methods """List of product descriptions""" - class Config: # pylint: disable=too-few-public-methods - """Pydantic configs""" - - extra = Extra.forbid - -class ProductDescriptionExtendedList(BaseCollectionModel[ProductDescriptionExtended]): # type: ignore[misc] # pylint: disable=too-few-public-methods +class ProductDescriptionExtendedList( + RootModel[List[ProductDescriptionExtended]] +): # pylint: disable=too-few-public-methods """List of product descriptions""" - class Config: # pylint: disable=too-few-public-methods - """Pydantic configs""" - - extra = Extra.forbid - @router.get( "/{language}", @@ -86,7 +71,7 @@ async def list_product_descriptions(language: str) -> ProductDescriptionList: responses = await get_from_all_products(f"api/v1/description/{language}", ProductDescription) if responses is None: raise ValueError("Everything is broken") - return ProductDescriptionList([res for res in responses.values() if res]) + return ProductDescriptionList([cast(ProductDescription, res) for res in responses.values() if res]) @router.get( @@ -112,7 +97,7 @@ async def list_product_descriptions_extended(language: str) -> ProductDescriptio responses = await get_from_all_products(f"api/v2/description/{language}", ProductDescriptionExtended) if responses is None: raise ValueError("Everything is broken") - return ProductDescriptionExtendedList([res for res in responses.values() if res]) + return ProductDescriptionExtendedList([cast(ProductDescriptionExtended, res) for res in responses.values() if res]) @router_v2.get( @@ -140,7 +125,7 @@ async def list_admin_product_descriptions_extended(language: str) -> ProductDesc responses = await get_from_all_products(f"api/v2/admin/description/{language}", ProductDescriptionExtended) if responses is None: raise ValueError("Everything is broken") - return ProductDescriptionExtendedList([res for res in responses.values() if res]) + return ProductDescriptionExtendedList([cast(ProductDescriptionExtended, res) for res in responses.values() if res]) @router_v2_admin.get( diff --git a/src/rasenmaeher_api/web/api/enrollment/schema.py b/src/rasenmaeher_api/web/api/enrollment/schema.py index 889151b..98547a2 100644 --- a/src/rasenmaeher_api/web/api/enrollment/schema.py +++ b/src/rasenmaeher_api/web/api/enrollment/schema.py @@ -2,38 +2,36 @@ from typing import List, Dict, Any, Optional -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, Field, ConfigDict class EnrollmentGenVerifiOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment gen verification code out""" - verification_code: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" + model_config = ConfigDict( + extra="forbid", + json_schema_extra={"example": {"verification_code": "[str] Generated verification code for enrollment."}}, + ) - extra = Extra.forbid - schema_extra = {"example": {"verification_code": "[str] Generated verification code for enrollment."}} + verification_code: str class EnrollmentConfigTaskDone(BaseModel): # pylint: disable=too-few-public-methods """Enrollment config add manager schema out""" - success_message: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "success_message": "[str] - Task completed message", } - } + }, + ) + + success_message: str -class EnrollmentStatusIn(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class EnrollmentStatusIn(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment status in schema""" callsign: str @@ -42,22 +40,21 @@ class EnrollmentStatusIn(BaseModel, extra=Extra.forbid): # pylint: disable=too- class EnrollmentStatusOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment status check schema""" - status: int - callsign: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "state": "[int] - Current state of enrollment", "callsign": "[str] User defined username/id/callsign", } - } + }, + ) + status: int + callsign: str -class EnrollmentShowVerificationCodeIn(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods + +class EnrollmentShowVerificationCodeIn(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment status in schema""" verification_code: str @@ -66,52 +63,45 @@ class EnrollmentShowVerificationCodeIn(BaseModel, extra=Extra.forbid): # pylint class EnrollmentShowVerificationCodeOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment status check schema""" - state: str - callsign: str - accepted: str - locked: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "state": "[str] - Current state of enrollment", "callsign": "[str] User defined username/id/callsign", "accepted": "[str] - Has this been already accepted, empty or 'na' == not accepted", "locked": "[str] - Contain info if the enrollment is locked. For unlocked enrollment, it's empty ''", } - } + }, + ) + + state: str + callsign: str + accepted: str + locked: str class EnrollmentHaveIBeenAcceptedOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment status check schema""" - have_i_been_accepted: bool - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "have_i_been_accepted": "[bool] - Accepted status. True/False", } - } + }, + ) + + have_i_been_accepted: bool class EnrollmentInitIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init in response schema""" - callsign: str = Field(description="Callsign to create enrollment for") - csr: Optional[str] = Field(description="CSR for mTLS key in PEM format", default=None) - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "with_values", @@ -122,32 +112,35 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + callsign: str = Field(description="Callsign to create enrollment for") + csr: Optional[str] = Field(description="CSR for mTLS key in PEM format", default=None) class EnrollmentInitOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init out response schema""" - callsign: str = Field(description="Callsign for which the enrollment got initialized") - approvecode: str = Field(description="Code used to approve the enrollment, must be delivered to an admin") - jwt: str = Field( - description="JWT that allows client to check enrollment approval status and fetc mTLS certs when approved" - ) - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "callsign": "OTTER01a", "approvecode": "12DFEE34555", "jwt": "...", } - } + }, + ) + callsign: str = Field(description="Callsign for which the enrollment got initialized") + approvecode: str = Field(description="Code used to approve the enrollment, must be delivered to an admin") + jwt: str = Field( + description="JWT that allows client to check enrollment approval status and fetc mTLS certs when approved" + ) -class EnrollmentDeliverIn(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods + +class EnrollmentDeliverIn(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment promote in schema""" callsign_hash: str @@ -156,17 +149,9 @@ class EnrollmentDeliverIn(BaseModel, extra=Extra.forbid): # pylint: disable=too class EnrollmentDeliverOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init out response schema""" - callsign: str - callsign_hash: str - cert_download_link: str - howto_download_link: str - mtls_test_link: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "callsign": "[str] User defined username/id/callsign", "callsign_hash": "[str] - Hash string for callsign", @@ -174,22 +159,22 @@ class Config: # pylint: disable=too-few-public-methods "howto_download_link": "[str] - Link where certificate install howto can be downloaded", "mtls_test_link": "[str] - Link that can be used to test mtls connection", } - } + }, + ) + + callsign: str + callsign_hash: str + cert_download_link: str + howto_download_link: str + mtls_test_link: str class EnrollmentAcceptIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init out response schema""" - callsign: str = Field(description="Callsign to approve") - approvecode: str = Field( - description="Approval code for the callsign, this must have been delivered by the person to be enrolled" - ) - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "with_values", @@ -201,35 +186,36 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + callsign: str = Field(description="Callsign to approve") + approvecode: str = Field( + description="Approval code for the callsign, this must have been delivered by the person to be enrolled" + ) class EnrollmentAcceptOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init out response schema""" - callsign: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "callsign": "[str] - Username/callsign/callsign", } - } + }, + ) + + callsign: str class EnrollmentPromoteIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment promote in schema""" - callsign: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "normal", @@ -248,19 +234,18 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + callsign: str class EnrollmentDemoteIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment demote in schema""" - callsign: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "normal", @@ -279,20 +264,18 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + callsign: str class EnrollmentLockIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment lock in schema""" - lock_reason: str - callsign: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "normal", @@ -311,48 +294,50 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + lock_reason: str + callsign: str class EnrollmentIsInvitecodeActiveIn(BaseModel): """Enrollment check if invitecode is still active""" - invitecode: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ {"invitecode": "[str] - Code that can be used to validate enrollment init"}, ] - } + }, + ) + + invitecode: str class EnrollmentIsInvitecodeActiveOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment config add manager schema out""" - invitecode_is_active: bool - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "invitecode_is_active": "[bool] - True = this code can still be used", } - } + }, + ) + invitecode_is_active: bool -class EnrollmentListOut(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods + +class EnrollmentListOut(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment list out response schema""" callsign_list: List[Dict[Any, Any]] -class EnrollmentPoolListItem(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class EnrollmentPoolListItem(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Items for EnrollmentPoolListOut""" invitecode: str = Field(description="The invitation code") @@ -361,13 +346,13 @@ class EnrollmentPoolListItem(BaseModel, extra=Extra.forbid): # pylint: disable= created: str = Field(description="ISO datetime of when this pool was created") -class EnrollmentPoolListOut(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class EnrollmentPoolListOut(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment pools list out response schema""" pools: List[EnrollmentPoolListItem] = Field(description="The pools") -class EnrollmentInviteCodeCreateOut(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class EnrollmentInviteCodeCreateOut(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment Invite code response schema""" invite_code: str @@ -376,20 +361,19 @@ class EnrollmentInviteCodeCreateOut(BaseModel, extra=Extra.forbid): # pylint: d class EnrollmentInviteCodeDeactivateIn(BaseModel): """Enrollment Invite code deactivate request schema""" - invite_code: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ {"invite_code": "[str] - Invite code that will be deactivated"}, ] - } + }, + ) + + invite_code: str -class EnrollmentInviteCodeDeactivateOut(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class EnrollmentInviteCodeDeactivateOut(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment Invite code deactivate response schema""" invite_code: str @@ -398,20 +382,19 @@ class EnrollmentInviteCodeDeactivateOut(BaseModel, extra=Extra.forbid): # pylin class EnrollmentInviteCodeActivateIn(BaseModel): """Enrollment Invite code activate request schema""" - invite_code: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ {"invite_code": "[str] - Invite code that will be reactivated"}, ] - } + }, + ) + + invite_code: str -class EnrollmentInviteCodeActivateOut(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class EnrollmentInviteCodeActivateOut(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Enrollment Invite code activate response schema""" invite_code: str @@ -420,35 +403,33 @@ class EnrollmentInviteCodeActivateOut(BaseModel, extra=Extra.forbid): # pylint: class EnrollmentInviteCodeEnrollIn(BaseModel): """Enrollment Enrollment Invite code request schema""" - invite_code: str - callsign: str - csr: Optional[str] = Field(description="CSR for mTLS key in PEM format", default=None) - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "invite_code": "[str] - Code that is used validate enrollment init for callsign", "callsign": "[str] User defined username/id/callsign", }, ] - } + }, + ) + + invite_code: str + callsign: str + csr: Optional[str] = Field(description="CSR for mTLS key in PEM format", default=None) class EnrollmentInviteCodeDeleteIn(BaseModel): """Enrollment Invite code deactivate request schema""" - invite_code: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ {"invite_code": "[str] - Invite code that will be removed."}, ] - } + }, + ) + + invite_code: str diff --git a/src/rasenmaeher_api/web/api/enrollment/views.py b/src/rasenmaeher_api/web/api/enrollment/views.py index e5000e4..4fb98eb 100644 --- a/src/rasenmaeher_api/web/api/enrollment/views.py +++ b/src/rasenmaeher_api/web/api/enrollment/views.py @@ -177,10 +177,7 @@ async def request_enrollment_list(code: Optional[str] = None) -> EnrollmentListO dependencies=[Depends(ValidUser(auto_error=True, require_roles=["admin"]))], ) async def request_enrollment_init( - request_in: EnrollmentInitIn = Body( - None, - examples=[EnrollmentInitIn.Config.schema_extra["examples"]], - ), + request_in: EnrollmentInitIn = Body(), ) -> EnrollmentInitOut: """ Add new callsign (enrollment) to environment. @@ -204,10 +201,7 @@ async def request_enrollment_init( ) async def request_enrollment_promote( request: Request, - request_in: EnrollmentPromoteIn = Body( - None, - examples=[EnrollmentPromoteIn.Config.schema_extra["examples"]], - ), + request_in: EnrollmentPromoteIn = Body(), ) -> OperationResultResponse: """ "Promote" callsign/user/enrollment to have 'admin' rights @@ -231,10 +225,7 @@ async def request_enrollment_promote( ) async def request_enrollment_demote( request: Request, - request_in: EnrollmentDemoteIn = Body( - None, - examples=[EnrollmentDemoteIn.Config.schema_extra["examples"]], - ), + request_in: EnrollmentDemoteIn = Body(), ) -> OperationResultResponse: """ "Demote" callsign/user/enrollment from having 'admin' rights. callsign_hash can be used too. @@ -257,10 +248,7 @@ async def request_enrollment_demote( ) async def request_enrollment_lock( request: Request, - request_in: EnrollmentLockIn = Body( - None, - examples=[EnrollmentLockIn.Config.schema_extra["examples"]], - ), + request_in: EnrollmentLockIn = Body(), ) -> OperationResultResponse: """ Lock callsign/user/enrollment so it cannot be used anymore. @@ -280,10 +268,7 @@ async def request_enrollment_lock( ) async def post_enrollment_accept( request: Request, - request_in: EnrollmentAcceptIn = Body( - None, - examples=[EnrollmentAcceptIn.Config.schema_extra["examples"]], - ), + request_in: EnrollmentAcceptIn = Body(), ) -> OperationResultResponse: """ Accept callsign_hash (callsign/enrollment) @@ -314,10 +299,7 @@ async def post_invite_code(request: Request) -> EnrollmentInviteCodeCreateOut: @ENROLLMENT_ROUTER.put("/invitecode/activate", response_model=OperationResultResponse) async def put_activate_invite_code( request: Request, - request_in: EnrollmentInviteCodeActivateIn = Body( - None, - examples=EnrollmentInviteCodeActivateIn.Config.schema_extra["examples"], - ), + request_in: EnrollmentInviteCodeActivateIn = Body(), ) -> OperationResultResponse: """ Activate an invite code @@ -336,10 +318,7 @@ async def put_activate_invite_code( @ENROLLMENT_ROUTER.put("/invitecode/deactivate", response_model=OperationResultResponse) async def put_deactivate_invite_code( request: Request, - request_in: EnrollmentInviteCodeDeactivateIn = Body( - None, - examples=EnrollmentInviteCodeDeactivateIn.Config.schema_extra["examples"], - ), + request_in: EnrollmentInviteCodeDeactivateIn = Body(), ) -> OperationResultResponse: """ Deactivate an invite code @@ -391,10 +370,7 @@ async def get_invite_codes( @NO_JWT_ENROLLMENT_ROUTER.post("/invitecode/enroll", response_model=EnrollmentInitOut) async def post_enroll_invite_code( request: Request, - request_in: EnrollmentInviteCodeEnrollIn = Body( - None, - examples=EnrollmentInviteCodeEnrollIn.Config.schema_extra["examples"], - ), + request_in: EnrollmentInviteCodeEnrollIn = Body(), ) -> EnrollmentInitOut: """ Enroll with an invite code diff --git a/src/rasenmaeher_api/web/api/firstuser/schema.py b/src/rasenmaeher_api/web/api/firstuser/schema.py index 48a9b97..f1523f1 100644 --- a/src/rasenmaeher_api/web/api/firstuser/schema.py +++ b/src/rasenmaeher_api/web/api/firstuser/schema.py @@ -2,19 +2,15 @@ from typing import Optional -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, Field, ConfigDict class FirstuserCheckCodeIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init in response schema""" - temp_admin_code: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "normal", @@ -29,37 +25,33 @@ class Config: # pylint: disable=too-few-public-methods "value": {"temp_admin_code": "some_permit_hash_abba_abc"}, }, ] - } + }, + ) + + temp_admin_code: str class FirstuserCheckCodeOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment config add manager schema out""" - code_ok: bool - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "code_ok": "[bool] - True = Requested 'first use admin code' was found and can be used.", } - } + }, + ) + + code_ok: bool class FirstuserAddAdminIn(BaseModel): # pylint: disable=too-few-public-methods """Enrollment init in response schema""" - # temp_admin_code: str - callsign: str - csr: Optional[str] = Field(default=None, description="CSR for mTLS key in PEM format") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "name": "normal", @@ -76,22 +68,26 @@ class Config: # pylint: disable=too-few-public-methods "value": {"callsign": "porakoira666"}, }, ] - } + }, + ) + + # temp_admin_code: str + callsign: str + csr: Optional[str] = Field(default=None, description="CSR for mTLS key in PEM format") class FirstuserAddAdminOut(BaseModel): # pylint: disable=too-few-public-methods """Enrollment config add manager schema out""" - admin_added: bool - jwt_exchange_code: str - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "example": { "admin_added": "[bool] - True = admin user were added", "jwt_exchange_code": "[str] - Code that can be exchanged to jwt token", } - } + }, + ) + + admin_added: bool + jwt_exchange_code: str diff --git a/src/rasenmaeher_api/web/api/firstuser/views.py b/src/rasenmaeher_api/web/api/firstuser/views.py index 0d28eec..481cba3 100644 --- a/src/rasenmaeher_api/web/api/firstuser/views.py +++ b/src/rasenmaeher_api/web/api/firstuser/views.py @@ -57,10 +57,7 @@ async def get_check_code( @router.post("/add-admin", response_model=FirstuserAddAdminOut) async def post_admin_add( request: Request, - request_in: FirstuserAddAdminIn = Body( - None, - examples=[FirstuserAddAdminIn.Config.schema_extra["examples"]], - ), + request_in: FirstuserAddAdminIn = Body(), jwt: JWTPayload = Depends(JWTBearer(auto_error=True)), ) -> FirstuserAddAdminOut: """ diff --git a/src/rasenmaeher_api/web/api/healthcheck/schema.py b/src/rasenmaeher_api/web/api/healthcheck/schema.py index fb747f7..6acb01f 100644 --- a/src/rasenmaeher_api/web/api/healthcheck/schema.py +++ b/src/rasenmaeher_api/web/api/healthcheck/schema.py @@ -2,23 +2,15 @@ from typing import Dict -from pydantic import BaseModel, Field, Extra +from pydantic import BaseModel, Field, ConfigDict class BasicHealthCheckResponse(BaseModel): """Basic healthcheck, basically are we running at all...""" - healthcheck: str = Field(description="Should contain 'success'") - dns: str = Field(description="Contains the FQDN of this instance") - deployment: str = Field(description="Contains the deployment name of this instance (host part of the FQDN)") - version: str = Field(description="Version number of the deployment (from ENV)") - rm_version: str = Field(description="Version of the API package") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "healthcheck": "success", @@ -28,20 +20,22 @@ class Config: # pylint: disable=too-few-public-methods "rm_version": "1.0.0", } ] - } + }, + ) + + healthcheck: str = Field(description="Should contain 'success'") + dns: str = Field(description="Contains the FQDN of this instance") + deployment: str = Field(description="Contains the deployment name of this instance (host part of the FQDN)") + version: str = Field(description="Version number of the deployment (from ENV)") + rm_version: str = Field(description="Version of the API package") class AllProductsHealthCheckResponse(BaseModel): """Check status of all products in manifest""" - all_ok: bool = Field(description="Is everything ok ?") - products: Dict[str, bool] = Field(description="Status for each product") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "all_ok": True, @@ -57,4 +51,8 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + all_ok: bool = Field(description="Is everything ok ?") + products: Dict[str, bool] = Field(description="Status for each product") diff --git a/src/rasenmaeher_api/web/api/instructions/schema.py b/src/rasenmaeher_api/web/api/instructions/schema.py index f19d864..4882246 100644 --- a/src/rasenmaeher_api/web/api/instructions/schema.py +++ b/src/rasenmaeher_api/web/api/instructions/schema.py @@ -1,10 +1,9 @@ """Instruction response schemas""" -from typing import Dict, Optional, Any +from typing import Dict, Optional, Any, List -from pydantic import BaseModel, Extra, Field +from pydantic import BaseModel, Field, ConfigDict, RootModel from libpvarki.schemas.product import UserInstructionFragment -from pydantic_collections import BaseCollectionModel # pylint: disable=too-few-public-methods @@ -12,15 +11,9 @@ class AllProductsInstructionFragments(BaseModel): """DEPRECATED! Fragments for all products""" - fragments: Dict[str, Optional[UserInstructionFragment]] = Field( - description="Instructions keyed by product short name, if fetching of fragment failed value for that product is null" # pylint: disable=C0301 - ) - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "fragments": { @@ -32,64 +25,52 @@ class Config: # pylint: disable=too-few-public-methods } }, ], - } + }, + ) + + fragments: Dict[str, Optional[UserInstructionFragment]] = Field( + description="Instructions keyed by product short name, if fetching of fragment failed value for that product is null" # pylint: disable=C0301 + ) class ProductFile(BaseModel): # pylint: disable=too-few-public-methods """File description""" + model_config = ConfigDict(extra="forbid") + title: str = Field(description="Title for the file") filename: str = Field(description="file name") data: str = Field(description="data-url for the file") - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - -class ProductFileList(BaseCollectionModel[ProductFile]): # type: ignore[misc] +class ProductFileList(RootModel[List[ProductFile]]): """List of files""" - class Config: # pylint: disable=too-few-public-methods - """Pydantic configs""" - - extra = Extra.forbid - class AllProductsInstructionFiles(BaseModel): """DEPRECATED! user files for all products""" + model_config = ConfigDict(extra="forbid") + files: Dict[str, Optional[ProductFileList]] = Field( description="files keyed by product short name, if fetching failed value for that product is null" ) - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - # FIXME: Move to libpvarki class InstructionData(BaseModel): """Instruction data response""" + model_config = ConfigDict(extra="forbid") + callsign: str = Field(description="Which callsign this was created for (can be used for sanity-checking)") language: str = Field(description="Language that was resolved, might not be same as requested") instructions: Any = Field(description="The actual instruction data, in whatever format the React UI wants it") - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - class ProductData(BaseModel): """Product user data for modular UI""" - data: Dict[str, Any] = Field(description="User data required for modular UI.") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" + model_config = ConfigDict(extra="forbid") - extra = Extra.forbid + data: Dict[str, Any] = Field(description="User data required for modular UI.") diff --git a/src/rasenmaeher_api/web/api/instructions/views.py b/src/rasenmaeher_api/web/api/instructions/views.py index a2db76e..577a306 100644 --- a/src/rasenmaeher_api/web/api/instructions/views.py +++ b/src/rasenmaeher_api/web/api/instructions/views.py @@ -55,7 +55,7 @@ async def user_instruction_fragment(request: Request) -> AllProductsInstructionF uuid=str(person.pk), callsign=person.callsign, x509cert=person.certfile.read_text(encoding="utf-8") ) LOGGER.debug("person={}, user={}".format(person, user)) - responses = await post_to_all_products("api/v1/clients/fragment", user.dict(), ProductFileList) + responses = await post_to_all_products("api/v1/clients/fragment", user.model_dump(), ProductFileList) if responses is None: raise ValueError("Everything is broken") return AllProductsInstructionFiles(files={key: cast(ProductFileList, val) for key, val in responses.items()}) @@ -73,7 +73,7 @@ async def get_product_instructions(request: Request, product: str, language: str uuid=str(person.pk), callsign=person.callsign, x509cert=person.certfile.read_text(encoding="utf-8") ) endpoint_url = f"api/v1/instructions/{language}" - response = await post_to_product(product, endpoint_url, user.dict(), InstructionData) + response = await post_to_product(product, endpoint_url, user.model_dump(), InstructionData) if response is None: _reason = f"Unable to get instructions for {product}" LOGGER.error("{} : {}".format(request.url, _reason)) @@ -93,7 +93,7 @@ async def get_product_data(request: Request, product: str) -> Optional[ProductDa uuid=str(person.pk), callsign=person.callsign, x509cert=person.certfile.read_text(encoding="utf-8") ) endpoint_url = "api/v2/clients/data" - response = await post_to_product(product, endpoint_url, user.dict(), ProductData) + response = await post_to_product(product, endpoint_url, user.model_dump(), ProductData) if response is None: _reason = f"Unable to get data for {product}" LOGGER.error("{} : {}".format(request.url, _reason)) @@ -113,7 +113,7 @@ async def get_admin_product_data(request: Request, product: str) -> Optional[Pro uuid=str(person.pk), callsign=person.callsign, x509cert=person.certfile.read_text(encoding="utf-8") ) endpoint_url = "api/v2/admin/clients/data" - response = await post_to_product(product, endpoint_url, user.dict(), ProductData) + response = await post_to_product(product, endpoint_url, user.model_dump(), ProductData) if response is None: _reason = f"Unable to get data for {product}" LOGGER.error("{} : {}".format(request.url, _reason)) diff --git a/src/rasenmaeher_api/web/api/people/schema.py b/src/rasenmaeher_api/web/api/people/schema.py index 0c6a247..1e1e25c 100644 --- a/src/rasenmaeher_api/web/api/people/schema.py +++ b/src/rasenmaeher_api/web/api/people/schema.py @@ -2,7 +2,7 @@ from typing import List, Dict, Any, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel class CallSignPerson(BaseModel): @@ -14,7 +14,7 @@ class CallSignPerson(BaseModel): revoked: Optional[str] -class PeopleListOut(BaseModel, extra=Extra.forbid): +class PeopleListOut(BaseModel, extra="forbid"): """People list out response schema""" callsign_list: List[CallSignPerson] diff --git a/src/rasenmaeher_api/web/api/product/schema.py b/src/rasenmaeher_api/web/api/product/schema.py index 54ae09b..876c13b 100644 --- a/src/rasenmaeher_api/web/api/product/schema.py +++ b/src/rasenmaeher_api/web/api/product/schema.py @@ -1,38 +1,33 @@ """Schema for product mTLS cert signing""" -from pydantic import BaseModel, Field, Extra +from pydantic import BaseModel, Field, ConfigDict class CertificatesResponse(BaseModel): # pylint: disable=too-few-public-methods """Respond with signed client cert and CA chain""" - ca: str = Field(description="CA chain, cfssl encoded (newlines -> \\n)") - certificate: str = Field(description="Signed cert, cfssl encoded (newlines -> \\n)") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "ca": """-----BEGIN CERTIFICATE-----\\nMIID9...\\n-----END CERTIFICATE-----\\n""", "certificate": """-----BEGIN CERTIFICATE-----\\nMIID9...\\n-----END CERTIFICATE-----\\n""", }, ] - } + }, + ) + + ca: str = Field(description="CA chain, cfssl encoded (newlines -> \\n)") + certificate: str = Field(description="Signed cert, cfssl encoded (newlines -> \\n)") class CertificatesRequest(BaseModel): # pylint: disable=too-few-public-methods """Request signed cert""" - csr: str = Field(description="CSR PEM") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "csr": """-----BEGIN CERTIFICATE REQUEST----- @@ -43,19 +38,18 @@ class Config: # pylint: disable=too-few-public-methods -----END CERTIFICATE REQUEST-----""" }, ] - } + }, + ) + + csr: str = Field(description="CSR PEM") class RevokeRequest(BaseModel): # pylint: disable=too-few-public-methods """Request a cert to be revoked""" - cert: str = Field(description="Cert PEM") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "cert": """-----BEGIN CERTIFICATE----- @@ -66,24 +60,18 @@ class Config: # pylint: disable=too-few-public-methods -----END CERTIFICATE-----""" }, ] - } + }, + ) + + cert: str = Field(description="Cert PEM") class KCClientToken(BaseModel): # pylint: disable=too-few-public-methods """Token for registering a KC client (for OIDC)""" - id: str = Field(description="Internal KC uuid for the token") - token: str = Field(description="JWT that allows to register a client to KC") - timestamp: int = Field(description="Unix timestamp") - expiration: int = Field(description="Expires in days") - count: int = Field(description="Number of uses total") - remainingCount: int = Field(description="Number of uses remaining") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "id": """ca27c7ee-bc2b-4a48-bd76-9f12d31758bb""", @@ -94,25 +82,32 @@ class Config: # pylint: disable=too-few-public-methods "remainingCount": 1, }, ] - } + }, + ) + + id: str = Field(description="Internal KC uuid for the token") + token: str = Field(description="JWT that allows to register a client to KC") + timestamp: int = Field(description="Unix timestamp") + expiration: int = Field(description="Expires in days") + count: int = Field(description="Number of uses total") + remainingCount: int = Field(description="Number of uses remaining") # FIXME: Move to libpvarki class ProductAddRequest(BaseModel): # pylint: disable=too-few-public-methods """Request to add product interoperability.""" - certcn: str = Field(description="CN of the certificate") - x509cert: str = Field(description="Certificate encoded with CFSSL conventions (newlines escaped)") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "certcn": "product.deployment.tld", "x509cert": "-----BEGIN CERTIFICATE-----\\nMIIEwjCC...\\n-----END CERTIFICATE-----\\n", }, ], - } + }, + ) + + certcn: str = Field(description="CN of the certificate") + x509cert: str = Field(description="Certificate encoded with CFSSL conventions (newlines escaped)") diff --git a/src/rasenmaeher_api/web/api/product/views.py b/src/rasenmaeher_api/web/api/product/views.py index 14c9da5..70a949e 100644 --- a/src/rasenmaeher_api/web/api/product/views.py +++ b/src/rasenmaeher_api/web/api/product/views.py @@ -164,7 +164,7 @@ async def add_interop( raise HTTPException(status_code=500, detail="Manifest does not have products key") if tgtproduct not in manifest["products"]: raise HTTPException(status_code=404, detail=f"Unknown product {tgtproduct}") - resp = await post_to_product(tgtproduct, "/api/v1/interop/add", srcproduct.dict(), OperationResultResponse) + resp = await post_to_product(tgtproduct, "/api/v1/interop/add", srcproduct.model_dump(), OperationResultResponse) if resp is None: return OperationResultResponse(success=False, error="post_to_product returned None") resp = cast(OperationResultResponse, resp) @@ -191,7 +191,7 @@ async def get_product_proxy( url = f"{productconf['api']}{tgtpath}" LOGGER.debug("calling POST({})".format(url)) response = await client.post( - url, json=user.dict(), timeout=aiohttp.ClientTimeout(total=rmconf.integration_api_timeout * 2) + url, json=user.model_dump(), timeout=aiohttp.ClientTimeout(total=rmconf.integration_api_timeout * 2) ) return Response( status_code=response.status, diff --git a/src/rasenmaeher_api/web/api/tokens/schema.py b/src/rasenmaeher_api/web/api/tokens/schema.py index fb46e77..fdc7ac1 100644 --- a/src/rasenmaeher_api/web/api/tokens/schema.py +++ b/src/rasenmaeher_api/web/api/tokens/schema.py @@ -2,36 +2,31 @@ from typing import Any, Dict -from pydantic import BaseModel, Field, Extra +from pydantic import BaseModel, Field, ConfigDict class JWTExchangeRequestResponse(BaseModel): # pylint: disable=too-few-public-methods """Exchange a TILAUSPALVELU single-use JWT for RASENMAEHER session JWT""" - jwt: str = Field(description="The token") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ {"jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJw...jHv3f3MlSQswcHhM"}, ] - } + }, + ) + + jwt: str = Field(description="The token") class LoginCodeCreateRequest(BaseModel): # pylint: disable=too-few-public-methods """TILAUSPALVELU asks us to create an one-time login code that user can input to a field. If TILAUSPALVELU wants to revoke a code it should just exchange it and discard the result""" - claims: Dict[str, Any] = Field(description="The claims that should be issued when this token is redeemed") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "claims": { @@ -39,22 +34,24 @@ class Config: # pylint: disable=too-few-public-methods }, }, ] - } + }, + ) + + claims: Dict[str, Any] = Field(description="The claims that should be issued when this token is redeemed") class LoginCodeRequestResponse(BaseModel): # pylint: disable=too-few-public-methods """The response to LoginCodeCreateRequest and also used to exchange the code""" - code: str = Field(description="The code user must provide to get a session JWT") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "code": "ABC1233GHIJ", }, ] - } + }, + ) + + code: str = Field(description="The code user must provide to get a session JWT") diff --git a/src/rasenmaeher_api/web/api/utils/schema.py b/src/rasenmaeher_api/web/api/utils/schema.py index 19c07c5..27091f0 100644 --- a/src/rasenmaeher_api/web/api/utils/schema.py +++ b/src/rasenmaeher_api/web/api/utils/schema.py @@ -1,10 +1,10 @@ """Schema for utils.""" from typing import Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel -class LdapConnString(BaseModel, extra=Extra.forbid): # pylint: disable=too-few-public-methods +class LdapConnString(BaseModel, extra="forbid"): # pylint: disable=too-few-public-methods """Utils / LDAP conn string schema""" ldap_conn_string: Optional[str] diff --git a/tests/ptfpapi/fprun.py b/tests/ptfpapi/fprun.py index d217bb3..1b22ca8 100644 --- a/tests/ptfpapi/fprun.py +++ b/tests/ptfpapi/fprun.py @@ -18,28 +18,27 @@ UserInstructionFragment, UserCRUDRequest, ) -from pydantic import BaseModel, Field, Extra +from pydantic import BaseModel, Field, ConfigDict # FIXME: Move to libpvarki class ProductAddRequest(BaseModel): # pylint: disable=too-few-public-methods,R0801 """Request to add product interoperability.""" - certcn: str = Field(description="CN of the certificate") - x509cert: str = Field(description="Certificate encoded with CFSSL conventions (newlines escaped)") - - class Config: # pylint: disable=too-few-public-methods - """Example values for schema""" - - extra = Extra.forbid - schema_extra = { + model_config = ConfigDict( + extra="forbid", + json_schema_extra={ "examples": [ { "certcn": "product.deployment.tld", "x509cert": "-----BEGIN CERTIFICATE-----\\nMIIEwjCC...\\n-----END CERTIFICATE-----\\n", }, ], - } + }, + ) + + certcn: str = Field(description="CN of the certificate") + x509cert: str = Field(description="Certificate encoded with CFSSL conventions (newlines escaped)") LOGGER = logging.getLogger(__name__) @@ -77,15 +76,15 @@ async def handle_user_crud(request: web.Request) -> web.Response: """Respond with success to all CRUD operations""" check_peer_cert(request) # Just to make sure the request itself uses valid schema - _req = UserCRUDRequest.parse_raw(await request.text()) + _req = UserCRUDRequest.model_validate_json(await request.text()) resp = OperationResultResponse(success=True, extra="Nothing was actually done, this is a fake endpoint for testing") - return web.json_response(resp.dict()) + return web.json_response(resp.model_dump()) async def handle_fragment(request: web.Request) -> web.Response: """Respond with hello_world for user""" check_peer_cert(request) - user = UserCRUDRequest.parse_raw(await request.text()) + user = UserCRUDRequest.model_validate_json(await request.text()) LOGGER.info("Called with user={}".format(user)) zip1_bytes = zip_pem(user.x509cert, f"{user.callsign}_1.pem") zip2_bytes = zip_pem(user.x509cert, f"{user.callsign}_2.pem") @@ -197,14 +196,14 @@ async def handle_admin_fragment(request: web.Request) -> web.Response: """Respond with success to all CRUD operations""" check_peer_cert(request) resp = UserInstructionFragment(html="

Hello admin!

") - return web.json_response(resp.dict()) + return web.json_response(resp.model_dump()) async def handle_interop_add(request: web.Request) -> web.Response: """Respond to additions""" - _req = ProductAddRequest.parse_raw(await request.text()) + _req = ProductAddRequest.model_validate_json(await request.text()) resp = OperationResultResponse(success=True, extra="Nothing was actually done, this is a fake endpoint for testing") - return web.json_response(resp.dict()) + return web.json_response(resp.model_dump()) async def handle_admin_clients_data_v2(request: web.Request) -> web.Response: @@ -241,7 +240,7 @@ async def handle_admin_clients_data_v2(request: web.Request) -> web.Response: def main() -> int: """Main entrypoint, return exit code""" LOGGER.debug("Called") - persistentdir = Path(environ.get("PERSISTENT_DATA_PATH", "/data/persistent")) + persistentdir = Path(environ.get("PERSISTENT_DATA_PATH", "./persistent")) extra_ca_certs_path = Path(environ.get("LOCAL_CA_CERTS_PATH", "/ca_public")) _hostname = environ.get("FPAPI_HOST_NAME", "fake.localmaeher.dev.pvarki.fi") bind_port = int(environ.get("FPAPI_BIND_PORT", 7788)) diff --git a/tests/test_rasenmaeher_api.py b/tests/test_rasenmaeher_api.py index 14041ac..5b699ec 100644 --- a/tests/test_rasenmaeher_api.py +++ b/tests/test_rasenmaeher_api.py @@ -16,7 +16,7 @@ def test_version() -> None: """Make sure version matches expected""" - assert __version__ == "1.11.0" + assert __version__ == "1.12.0" @pytest.mark.asyncio(loop_scope="session")