From 71d01f29e9c4d199abb9e018f7e7366daa9e5a14 Mon Sep 17 00:00:00 2001 From: Maciej Urbanski Date: Fri, 9 Feb 2024 13:51:54 +0100 Subject: [PATCH 1/3] add pydantic validation to tests --- pdm.lock | 272 ++++++++++++++++++++++++++++++++++++------------ pyproject.toml | 4 + test/helpers.py | 16 +++ 3 files changed, 224 insertions(+), 68 deletions(-) diff --git a/pdm.lock b/pdm.lock index af602e006..8c04eee96 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "doc", "format", "lint", "release", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:10acaf36178d2fa68d0b3eb3af65f8844349a6d9a42416258f6f133640f03528" +content_hash = "sha256:017fcb2d2edc149d5e929ec4d5e3812cbf2361db12760aa7e5d4df8b87b4fe8a" [[package]] name = "alabaster" @@ -18,6 +18,21 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "annotated-types" +version = "0.6.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["test"] +marker = "python_version >= \"3.8\" and platform_python_implementation != \"PyPy\"" +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +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 = "atomicwrites" version = "1.4.1" @@ -59,13 +74,13 @@ files = [ [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" requires_python = ">=3.6" summary = "Python package for providing Mozilla's CA Bundle." groups = ["default", "doc"] files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -347,6 +362,18 @@ files = [ {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, ] +[[package]] +name = "eval-type-backport" +version = "0.1.3" +requires_python = ">=3.7" +summary = "Like `typing._eval_type`, but lets older Python versions use newer typing features." +groups = ["test"] +marker = "python_version < \"3.10\"" +files = [ + {file = "eval_type_backport-0.1.3-py3-none-any.whl", hash = "sha256:519d2a993b3da286df9f90e17f503f66435106ad870cf26620c5720e2158ddf2"}, + {file = "eval_type_backport-0.1.3.tar.gz", hash = "sha256:d83ee225331dfa009493cec1f3608a71550b515ee4749abe78da14e3c5e314f5"}, +] + [[package]] name = "execnet" version = "2.0.2" @@ -556,71 +583,71 @@ files = [ [[package]] name = "markupsafe" -version = "2.1.4" +version = "2.1.5" requires_python = ">=3.7" summary = "Safely add untrusted strings to HTML/XML markup." groups = ["doc", "release"] files = [ - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, - {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -659,6 +686,115 @@ files = [ {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] +[[package]] +name = "pydantic" +version = "2.6.4" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["test"] +marker = "python_version >= \"3.8\" and platform_python_implementation != \"PyPy\"" +dependencies = [ + "annotated-types>=0.4.0", + "pydantic-core==2.16.3", + "typing-extensions>=4.6.1", +] +files = [ + {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, + {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, +] + +[[package]] +name = "pydantic-core" +version = "2.16.3" +requires_python = ">=3.8" +summary = "" +groups = ["test"] +marker = "python_version >= \"3.8\" and platform_python_implementation != \"PyPy\"" +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +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]] name = "pygments" version = "2.17.2" @@ -781,13 +917,13 @@ files = [ [[package]] name = "pytz" -version = "2023.4" +version = "2024.1" summary = "World timezone definitions, modern and historical" groups = ["doc"] marker = "python_version < \"3.9\"" files = [ - {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, - {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index 34ac0cccf..a1380b7b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -160,6 +160,10 @@ lint = [ ] test = [ "coverage==7.2.7", + "eval_type_backport>=0.1.3,<1; python_version<'3.10'", # used with pydantic + # remove `and platform_python_implementation!='PyPy'` after dropping Python 3.7 support as that + # will allow us to update pydantic to a version which installs properly under PyPy + "pydantic>=2.0.1,<3; python_version>='3.8' and platform_python_implementation!='PyPy'", "pytest==6.2.5", "pytest-cov==3.0.0", "pytest-mock==3.6.1", diff --git a/test/helpers.py b/test/helpers.py index 550e32493..d1e33fa8a 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -14,6 +14,11 @@ import io from unittest.mock import patch +try: + import pydantic +except ImportError: + pydantic = None + @contextlib.contextmanager def patch_bind_params(instance, method_name): @@ -44,3 +49,14 @@ def seek(self, *args, **kwargs): def seekable(self): return False + + +def type_validator_factory(type_): + """ + Equivalent of `TypeAdapter(type_).validate_python` and noop under Python <3.8. + + To be removed when we drop support for Python <3.8. + """ + if pydantic: + return pydantic.TypeAdapter(type_).validate_python + return lambda *args, **kwargs: None From aaf8569b81eb303c1619efb95e22b017a3ed01f9 Mon Sep 17 00:00:00 2001 From: Maciej Urbanski Date: Fri, 9 Feb 2024 14:42:52 +0100 Subject: [PATCH 2/3] add notification rules support --- b2sdk/_internal/bucket.py | 17 +++- b2sdk/_internal/exception.py | 55 ++++++++++++ b2sdk/_internal/raw_api.py | 105 ++++++++++++++++++++++- b2sdk/_internal/raw_simulator.py | 71 ++++++++++++++- b2sdk/_internal/session.py | 8 ++ b2sdk/_v3/__init__.py | 3 +- changelog.d/+notification_rules.added.md | 1 + test/integration/test_bucket.py | 36 ++++++++ test/integration/test_raw_api.py | 49 ++++++++++- test/unit/bucket/test_bucket.py | 39 +++++++++ test/unit/conftest.py | 35 ++++++++ test/unit/replication/conftest.py | 32 +------ 12 files changed, 414 insertions(+), 37 deletions(-) create mode 100644 changelog.d/+notification_rules.added.md create mode 100644 test/integration/test_bucket.py diff --git a/b2sdk/_internal/bucket.py b/b2sdk/_internal/bucket.py index d26738ec6..e2de5ff6a 100644 --- a/b2sdk/_internal/bucket.py +++ b/b2sdk/_internal/bucket.py @@ -14,7 +14,7 @@ import logging import pathlib from contextlib import suppress -from typing import Sequence +from typing import Iterable, Sequence from .encryption.setting import EncryptionSetting, EncryptionSettingFactory from .encryption.types import EncryptionMode @@ -37,7 +37,7 @@ from .filter import Filter, FilterMatcher from .http_constants import LIST_FILE_NAMES_MAX_LIMIT from .progress import AbstractProgressListener, DoNothingProgressListener -from .raw_api import LifecycleRule +from .raw_api import LifecycleRule, NotificationRule, NotificationRuleResponse from .replication.setting import ReplicationConfiguration, ReplicationConfigurationFactory from .transfer.emerge.executor import AUTO_CONTENT_TYPE from .transfer.emerge.unbound_write_intent import UnboundWriteIntentGenerator @@ -1492,6 +1492,19 @@ def as_dict(self): def __repr__(self): return f'Bucket<{self.id_},{self.name},{self.type_}>' + def get_notification_rules(self) -> list[NotificationRuleResponse]: + """ + Get all notification rules for this bucket. + """ + return self.api.session.get_bucket_notification_rules(self.id_) + + def set_notification_rules(self, + rules: Iterable[NotificationRule]) -> list[NotificationRuleResponse]: + """ + Set notification rules for this bucket. + """ + return self.api.session.set_bucket_notification_rules(self.id_, rules) + class BucketFactory: """ diff --git a/b2sdk/_internal/exception.py b/b2sdk/_internal/exception.py index f87f5416c..d13e9d147 100644 --- a/b2sdk/_internal/exception.py +++ b/b2sdk/_internal/exception.py @@ -11,6 +11,7 @@ import logging import re +import typing import warnings from abc import ABCMeta from typing import Any @@ -574,6 +575,47 @@ class DestinationDirectoryDoesntAllowOperation(DestinationDirectoryError): pass +class EventTypeError(BadRequest): + pass + + +class EventTypeCategoriesError(EventTypeError): + pass + + +class EventTypeOverlapError(EventTypeError): + pass + + +class EventTypesEmptyError(EventTypeError): + pass + + +class EventTypeInvalidError(EventTypeError): + pass + + +def _event_type_invalid_error(code: str, message: str, **_) -> B2Error: + from b2sdk._internal.raw_api import EVENT_TYPE + + valid_types = sorted(typing.get_args(EVENT_TYPE)) + return EventTypeInvalidError( + f"Event Type error: {message!r}. Valid types: {sorted(valid_types)!r}", code + ) + + +_error_handlers: dict[tuple[int, str | None], typing.Callable] = { + (400, "event_type_categories"): + lambda code, message, **_: EventTypeCategoriesError(message, code), + (400, "event_type_overlap"): + lambda code, message, **_: EventTypeOverlapError(message, code), + (400, "event_types_empty"): + lambda code, message, **_: EventTypesEmptyError(message, code), + (400, "event_type_invalid"): + _event_type_invalid_error, +} + + @trace_call(logger) def interpret_b2_error( status: int, @@ -583,6 +625,19 @@ def interpret_b2_error( post_params: dict[str, Any] | None = None ) -> B2Error: post_params = post_params or {} + + handler = _error_handlers.get((status, code)) + if handler: + error = handler( + status=status, + code=code, + message=message, + response_headers=response_headers, + post_params=post_params + ) + if error: + return error + if status == 400 and code == "already_hidden": return FileAlreadyHidden(post_params.get('fileName')) elif status == 400 and code == 'bad_json': diff --git a/b2sdk/_internal/raw_api.py b/b2sdk/_internal/raw_api.py index c76cc25c6..33679d425 100644 --- a/b2sdk/_internal/raw_api.py +++ b/b2sdk/_internal/raw_api.py @@ -13,15 +13,15 @@ from abc import ABCMeta, abstractmethod from enum import Enum, unique from logging import getLogger -from typing import Any +from typing import Any, Iterable from .utils.escape import unprintable_to_hex from .utils.typing import JSON try: - from typing_extensions import NotRequired, TypedDict + from typing_extensions import Literal, NotRequired, TypedDict except ImportError: - from typing import NotRequired, TypedDict + from typing import Literal, NotRequired, TypedDict from .encryption.setting import EncryptionMode, EncryptionSetting from .exception import ( @@ -73,6 +73,8 @@ 'shareFiles', 'writeFiles', 'deleteFiles', + 'readBucketNotifications', + 'writeBucketNotifications', ] # API version number to use when calling the service @@ -102,6 +104,67 @@ class LifecycleRule(TypedDict): daysFromUploadingToHiding: NotRequired[int | None] +class NameValueDict(TypedDict): + name: str + value: str + + +class NotificationTargetConfiguration(TypedDict): + """ + Notification Target Configuration. + + `hmacSha256SigningSecret`, if present, has to be a string of 32 alphanumeric characters. + """ + # TODO: add URL to the documentation + + targetType: Literal['webhook'] + url: str + customHeaders: NotRequired[list[NameValueDict] | None] + hmacSha256SigningSecret: NotRequired[str] + + +EVENT_TYPE = Literal[ + 'b2:ObjectCreated:*', 'b2:ObjectCreated:Upload', 'b2:ObjectCreated:MultipartUpload', + 'b2:ObjectCreated:Copy', 'b2:ObjectCreated:Replica', 'b2:ObjectCreated:MultipartReplica', + 'b2:ObjectDeleted:*', 'b2:ObjectDeleted:Delete', 'b2:ObjectDeleted:LifecycleRule', + 'b2:HideMarkerCreated:*', 'b2:HideMarkerCreated:Hide', 'b2:HideMarkerCreated:LifecycleRule',] + + +class _NotificationRule(TypedDict): + """ + Notification Rule. + """ + eventTypes: list[EVENT_TYPE] + isEnabled: bool + name: str + objectNamePrefix: str + targetConfiguration: NotificationTargetConfiguration + suspensionReason: NotRequired[str] + + +class NotificationRule(_NotificationRule): + """ + Notification Rule. + + When creating or modifying a notification rule, `isSuspended` and `suspensionReason` are ignored. + """ + isSuspended: NotRequired[bool] + + +class NotificationRuleResponse(_NotificationRule): + isSuspended: bool + + +def notification_rule_response_to_request(rule: NotificationRuleResponse) -> NotificationRule: + """ + Convert NotificationRuleResponse to NotificationRule. + """ + rule = rule.copy() + for key in ('isSuspended', 'suspensionReason'): + rule.pop(key, None) + return rule + + class AbstractRawApi(metaclass=ABCMeta): """ Direct access to the B2 web apis. @@ -415,6 +478,18 @@ def get_download_url_by_id(self, download_url, file_id): def get_download_url_by_name(self, download_url, bucket_name, file_name): return download_url + '/file/' + bucket_name + '/' + b2_url_encode(file_name) + @abstractmethod + def set_bucket_notification_rules( + self, api_url: str, account_auth_token: str, bucket_id: str, + rules: Iterable[NotificationRule] + ) -> list[NotificationRuleResponse]: + pass + + @abstractmethod + def get_bucket_notification_rules(self, api_url: str, account_auth_token: str, + bucket_id: str) -> list[NotificationRuleResponse]: + pass + class B2RawHTTPApi(AbstractRawApi): """ @@ -1088,6 +1163,30 @@ def copy_part( except AccessDenied: raise SSECKeyError() + def set_bucket_notification_rules( + self, api_url: str, account_auth_token: str, bucket_id: str, rules: list[NotificationRule] + ) -> list[NotificationRuleResponse]: + return self._post_json( + api_url, + 'b2_set_bucket_notification_rules', + account_auth_token, + **{ + 'bucketId': bucket_id, + 'eventNotificationRules': rules, + }, + )["eventNotificationRules"] + + def get_bucket_notification_rules(self, api_url: str, account_auth_token: str, + bucket_id: str) -> list[NotificationRuleResponse]: + return self._get_json( + api_url, + 'b2_get_bucket_notification_rules', + account_auth_token, + **{ + 'bucketId': bucket_id, + }, + )["eventNotificationRules"] + def _add_range_header(headers, range_): if range_ is not None: diff --git a/b2sdk/_internal/raw_simulator.py b/b2sdk/_internal/raw_simulator.py index 6b7cb77b0..029c4cff3 100644 --- a/b2sdk/_internal/raw_simulator.py +++ b/b2sdk/_internal/raw_simulator.py @@ -15,9 +15,11 @@ import logging import random import re +import secrets import threading import time from contextlib import contextmanager, suppress +from typing import Iterable from requests.structures import CaseInsensitiveDict @@ -40,6 +42,7 @@ MissingPart, NonExistentBucket, PartSha1Mismatch, + ResourceNotFound, SourceReplicationConflict, SSECKeyError, Unauthorized, @@ -54,7 +57,14 @@ ) from .file_version import UNVERIFIED_CHECKSUM_PREFIX from .http_constants import FILE_INFO_HEADER_PREFIX, HEX_DIGITS_AT_END -from .raw_api import ALL_CAPABILITIES, AbstractRawApi, LifecycleRule, MetadataDirectiveMode +from .raw_api import ( + ALL_CAPABILITIES, + AbstractRawApi, + LifecycleRule, + MetadataDirectiveMode, + NotificationRule, + NotificationRuleResponse, +) from .replication.setting import ReplicationConfiguration from .replication.types import ReplicationStatus from .stream.hashing import StreamWithHash @@ -542,6 +552,7 @@ def __init__( self.bucket_info = bucket_info or {} self.cors_rules = cors_rules or [] self.lifecycle_rules = lifecycle_rules or [] + self._notification_rules = [] self.options_set = options_set or set() self.revision = 1 self.upload_url_counter = iter(range(200)) @@ -1160,6 +1171,44 @@ def _chunks_number(self, content_length, chunk_size): def _next_file_id(self): return str(next(self.file_id_counter)) + def get_notification_rules(self) -> list[NotificationRule]: + return self._notification_rules + + def set_notification_rules(self, + rules: Iterable[NotificationRule]) -> list[NotificationRuleResponse]: + old_rules_by_name = {rule["name"]: rule for rule in self._notification_rules} + new_rules: list[NotificationRuleResponse] = [] + for rule in rules: + for field in ("isSuspended", "suspensionReason"): + rule.pop(field, None) + old_rule = old_rules_by_name.get(rule["name"], {"targetConfiguration": {}}) + new_rule = { + **{ + "isSuspended": False, + "suspensionReason": "", + }, + **old_rule, + **rule, + "targetConfiguration": + { + **old_rule.get("targetConfiguration", {}), + **rule.get("targetConfiguration", {}), + }, + } + new_rules.append(new_rule) + self._notification_rules = new_rules + return self._notification_rules + + def simulate_notification_rule_suspension( + self, rule_name: str, reason: str, is_suspended: bool | None = None + ) -> None: + for rule in self._notification_rules: + if rule["name"] == rule_name: + rule["isSuspended"] = bool(reason) if is_suspended is None else is_suspended + rule["suspensionReason"] = reason + return + raise ResourceNotFound(f"Rule {rule_name} not found") + class RawSimulator(AbstractRawApi): """ @@ -1235,6 +1284,8 @@ def expire_auth_token(self, auth_token): def create_account(self): """ + Simulate creating an account. + Return (accountId, masterApplicationKey) for a newly created account. """ # Pick the IDs for the account and the key @@ -1973,3 +2024,21 @@ def _get_bucket_by_name(self, bucket_name): if bucket_name not in self.bucket_name_to_bucket: raise NonExistentBucket(bucket_name) return self.bucket_name_to_bucket[bucket_name] + + def set_bucket_notification_rules( + self, api_url: str, account_auth_token: str, bucket_id: str, + rules: Iterable[NotificationRule] + ): + bucket = self._get_bucket_by_id(bucket_id) + self._assert_account_auth( + api_url, account_auth_token, bucket.account_id, 'writeBucketNotifications' + ) + return bucket.set_notification_rules(rules) + + def get_bucket_notification_rules(self, api_url: str, account_auth_token: str, + bucket_id: str) -> list[NotificationRule]: + bucket = self._get_bucket_by_id(bucket_id) + self._assert_account_auth( + api_url, account_auth_token, bucket.account_id, 'readBucketNotifications' + ) + return bucket.get_notification_rules() diff --git a/b2sdk/_internal/session.py b/b2sdk/_internal/session.py index e288fe54a..a2a97d647 100644 --- a/b2sdk/_internal/session.py +++ b/b2sdk/_internal/session.py @@ -572,3 +572,11 @@ def update_file_legal_hold( file_name, legal_hold, ) + + def get_bucket_notification_rules(self, bucket_id): + return self._wrap_default_token(self.raw_api.get_bucket_notification_rules, bucket_id) + + def set_bucket_notification_rules(self, bucket_id, rules): + return self._wrap_default_token( + self.raw_api.set_bucket_notification_rules, bucket_id, rules + ) diff --git a/b2sdk/_v3/__init__.py b/b2sdk/_v3/__init__.py index 51c388c0a..d374c0966 100644 --- a/b2sdk/_v3/__init__.py +++ b/b2sdk/_v3/__init__.py @@ -31,7 +31,7 @@ def filter(self, record): from b2sdk._internal.api import Services from b2sdk._internal.bucket import Bucket from b2sdk._internal.bucket import BucketFactory -from b2sdk._internal.raw_api import ALL_CAPABILITIES, REALM_URLS +from b2sdk._internal.raw_api import ALL_CAPABILITIES, REALM_URLS, EVENT_TYPE # encryption @@ -138,6 +138,7 @@ def filter(self, record): from b2sdk._internal.raw_api import B2RawHTTPApi from b2sdk._internal.raw_api import MetadataDirectiveMode from b2sdk._internal.raw_api import LifecycleRule +from b2sdk._internal.raw_api import NotificationRule, NotificationRuleResponse, notification_rule_response_to_request # stream diff --git a/changelog.d/+notification_rules.added.md b/changelog.d/+notification_rules.added.md new file mode 100644 index 000000000..2f2c3716c --- /dev/null +++ b/changelog.d/+notification_rules.added.md @@ -0,0 +1 @@ +Add set&get notification rules methods to Bucket API. \ No newline at end of file diff --git a/test/integration/test_bucket.py b/test/integration/test_bucket.py new file mode 100644 index 000000000..13e1ed18b --- /dev/null +++ b/test/integration/test_bucket.py @@ -0,0 +1,36 @@ +###################################################################### +# +# File: test/integration/test_bucket.py +# +# Copyright 2024 Backblaze Inc. All Rights Reserved. +# +# License https://www.backblaze.com/using_b2_code.html +# +###################################################################### +def test_bucket_notification_rules(bucket): + assert bucket.set_notification_rules([]) == [] + assert bucket.get_notification_rules() == [] + + notification_rule = { + "eventTypes": ["b2:ObjectCreated:*"], + "isEnabled": True, + "name": "test-rule", + "objectNamePrefix": "", + "targetConfiguration": + { + "customHeaders": [], + "targetType": "webhook", + "url": "https://example.com/webhook", + "hmacSha256SigningSecret": "stringOf32AlphaNumericCharacters", + } + } + + set_notification_rules = bucket.set_notification_rules([notification_rule]) + assert set_notification_rules == bucket.get_notification_rules() + assert set_notification_rules == [ + { + **notification_rule, "isSuspended": False, + "suspensionReason": "" + } + ] + assert bucket.set_notification_rules([]) == [] diff --git a/test/integration/test_raw_api.py b/test/integration/test_raw_api.py index cbc83a3d5..ab839b71f 100644 --- a/test/integration/test_raw_api.py +++ b/test/integration/test_raw_api.py @@ -16,6 +16,8 @@ import sys import time import traceback +from test.helpers import type_validator_factory +from typing import List import pytest @@ -33,13 +35,18 @@ RetentionMode, RetentionPeriod, ) -from b2sdk._internal.raw_api import ALL_CAPABILITIES, REALM_URLS, B2RawHTTPApi +from b2sdk._internal.raw_api import ( + ALL_CAPABILITIES, + REALM_URLS, + B2RawHTTPApi, + NotificationRuleResponse, +) from b2sdk._internal.replication.setting import ReplicationConfiguration, ReplicationRule from b2sdk._internal.replication.types import ReplicationStatus from b2sdk._internal.utils import hex_sha1_of_stream -# TODO: rewrite to separate test cases +# TODO: rewrite to separate test cases after introduction of reusable bucket def test_raw_api(dont_cleanup_old_buckets): """ Exercise the code in B2RawHTTPApi by making each call once, just @@ -568,6 +575,44 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): raw_api.delete_file_version(api_url, account_auth_token, file_id, file_name) raw_api.delete_file_version(api_url, account_auth_token, file_id, file_name, True) + print('b2_get_bucket_notification_rules & b2_set_bucket_notification_rules') + notification_rule = { + 'eventTypes': ['b2:ObjectCreated:Copy'], + 'isEnabled': False, + 'name': 'test-notification-rule', + 'objectNamePrefix': 'test/object/prefix/', + 'targetConfiguration': { + 'targetType': 'webhook', + 'url': 'https://example.com/webhook', + 'hmacSha256SigningSecret': 'a' * 32, + }, + } + + notification_rules_response_list = raw_api.set_bucket_notification_rules( + api_url, account_auth_token, bucket_id, [notification_rule] + ) + notification_rule_response_list_validate = type_validator_factory( + List[NotificationRuleResponse] + ) + notification_rule_response_list_validate(notification_rules_response_list) + expected_notification_rule_response_list = [ + { + **notification_rule, 'isSuspended': False, + 'suspensionReason': '', + 'targetConfiguration': + { + **notification_rule['targetConfiguration'], + 'customHeaders': + None, + 'hmacSha256SigningSecret': 'a' * 32, + } + } + ] + assert notification_rules_response_list == expected_notification_rule_response_list + + assert raw_api.set_bucket_notification_rules(api_url, account_auth_token, bucket_id, []) == [] + assert raw_api.get_bucket_notification_rules(api_url, account_auth_token, bucket_id) == [] + # Clean up this test. _clean_and_delete_bucket(raw_api, api_url, account_auth_token, account_id, bucket_id) diff --git a/test/unit/bucket/test_bucket.py b/test/unit/bucket/test_bucket.py index ba2ea4bc5..3525825df 100644 --- a/test/unit/bucket/test_bucket.py +++ b/test/unit/bucket/test_bucket.py @@ -3242,3 +3242,42 @@ class TestEmptyListVersions(TestListVersions): class TestEmptyLs(TestLs): RAW_SIMULATOR_CLASS = EmptyListSimulator + + +def test_bucket_notification_rules(bucket, b2api_simulator): + assert bucket.get_notification_rules() == [] + + notification_rule = { + "eventTypes": ["b2:ObjectCreated:*"], + "isEnabled": True, + "name": "test-rule", + "objectNamePrefix": "", + "targetConfiguration": + { + "customHeaders": [], + "targetType": "webhook", + "url": "https://example.com/webhook", + } + } + + set_notification_rules = bucket.set_notification_rules([notification_rule]) + assert set_notification_rules == bucket.get_notification_rules() + assert set_notification_rules == [ + { + **notification_rule, "isSuspended": False, + "suspensionReason": "" + } + ] + + b2api_simulator.bucket_id_to_bucket[bucket.id_].simulate_notification_rule_suspension( + notification_rule["name"], "simulated suspension" + ) + assert bucket.get_notification_rules() == [ + { + **notification_rule, "isSuspended": True, + "suspensionReason": "simulated suspension" + } + ] + + assert bucket.set_notification_rules([]) == [] + assert bucket.get_notification_rules() == [] diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 859b4e75f..d318ac8e5 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -152,3 +152,38 @@ def apiver(request): def apiver_int(apiver): """Get apiver as an int, e.g. `2`.""" return int(apiver[1:]) + + +@pytest.fixture +def b2api(): + from apiver_deps import ( + B2Api, + B2HttpApiConfig, + RawSimulator, + StubAccountInfo, + ) + + account_info = StubAccountInfo() + api = B2Api( + account_info, + api_config=B2HttpApiConfig(_raw_api_class=RawSimulator), + ) + + simulator = api.session.raw_api + account_id, master_key = simulator.create_account() + api.authorize_account( + application_key_id=account_id, + application_key=master_key, + realm='production', + ) + return api + + +@pytest.fixture +def b2api_simulator(b2api): + return b2api.session.raw_api + + +@pytest.fixture +def bucket(b2api): + return b2api.create_bucket('test-bucket', 'allPublic') diff --git a/test/unit/replication/conftest.py b/test/unit/replication/conftest.py index c91542644..6aa680bb2 100644 --- a/test/unit/replication/conftest.py +++ b/test/unit/replication/conftest.py @@ -11,45 +11,21 @@ import pytest from apiver_deps import ( - B2Api, - B2HttpApiConfig, Bucket, - RawSimulator, ReplicationConfiguration, ReplicationMonitor, ReplicationRule, - StubAccountInfo, ) @pytest.fixture -def api() -> B2Api: - account_info = StubAccountInfo() - api = B2Api( - account_info, - api_config=B2HttpApiConfig(_raw_api_class=RawSimulator), - ) - - simulator = api.session.raw_api - account_id, master_key = simulator.create_account() - api.authorize_account( - application_key_id=account_id, - application_key=master_key, - realm='production', - ) - # api_url = account_info.get_api_url() - # account_auth_token = account_info.get_account_auth_token()1 - return api - - -@pytest.fixture -def destination_bucket(api) -> Bucket: - return api.create_bucket('destination-bucket', 'allPublic') +def destination_bucket(b2api) -> Bucket: + return b2api.create_bucket('destination-bucket', 'allPublic') @pytest.fixture -def source_bucket(api, destination_bucket) -> Bucket: - bucket = api.create_bucket('source-bucket', 'allPublic') +def source_bucket(b2api, destination_bucket) -> Bucket: + bucket = b2api.create_bucket('source-bucket', 'allPublic') bucket.replication = ReplicationConfiguration( rules=[ From 3d47ee1de43f6d6d20d2da4e30341d7bb864bfb5 Mon Sep 17 00:00:00 2001 From: Maciej Urbanski Date: Wed, 10 Apr 2024 20:51:21 +0200 Subject: [PATCH 3/3] feature preview of notification rules setting --- b2sdk/_internal/raw_api.py | 20 ++++++++++++++++++++ b2sdk/_internal/raw_simulator.py | 1 - b2sdk/_internal/version_utils.py | 10 ++++++++++ b2sdk/_v3/__init__.py | 2 +- changelog.d/+notification_rules.added.md | 3 ++- doc/source/index.rst | 2 ++ test/integration/test_raw_api.py | 14 +++++++------- 7 files changed, 42 insertions(+), 10 deletions(-) diff --git a/b2sdk/_internal/raw_api.py b/b2sdk/_internal/raw_api.py index 33679d425..3699d51b9 100644 --- a/b2sdk/_internal/raw_api.py +++ b/b2sdk/_internal/raw_api.py @@ -10,6 +10,8 @@ from __future__ import annotations import base64 +import functools +import warnings from abc import ABCMeta, abstractmethod from enum import Enum, unique from logging import getLogger @@ -17,6 +19,7 @@ from .utils.escape import unprintable_to_hex from .utils.typing import JSON +from .version_utils import FeaturePreviewWarning try: from typing_extensions import Literal, NotRequired, TypedDict @@ -155,6 +158,21 @@ class NotificationRuleResponse(_NotificationRule): isSuspended: bool +def _bucket_notification_rule_feature_preview_warning(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + warnings.warn( + "Event Notifications feature is in \"Private Preview\" state and may change without notice. " + "See https://www.backblaze.com/blog/announcing-event-notifications/ for details.", + FeaturePreviewWarning, + stacklevel=2, + ) + return func(*args, **kwargs) + + return wrapper + + +@_bucket_notification_rule_feature_preview_warning def notification_rule_response_to_request(rule: NotificationRuleResponse) -> NotificationRule: """ Convert NotificationRuleResponse to NotificationRule. @@ -1163,6 +1181,7 @@ def copy_part( except AccessDenied: raise SSECKeyError() + @_bucket_notification_rule_feature_preview_warning def set_bucket_notification_rules( self, api_url: str, account_auth_token: str, bucket_id: str, rules: list[NotificationRule] ) -> list[NotificationRuleResponse]: @@ -1176,6 +1195,7 @@ def set_bucket_notification_rules( }, )["eventNotificationRules"] + @_bucket_notification_rule_feature_preview_warning def get_bucket_notification_rules(self, api_url: str, account_auth_token: str, bucket_id: str) -> list[NotificationRuleResponse]: return self._get_json( diff --git a/b2sdk/_internal/raw_simulator.py b/b2sdk/_internal/raw_simulator.py index 029c4cff3..952a0b7e6 100644 --- a/b2sdk/_internal/raw_simulator.py +++ b/b2sdk/_internal/raw_simulator.py @@ -15,7 +15,6 @@ import logging import random import re -import secrets import threading import time from contextlib import contextmanager, suppress diff --git a/b2sdk/_internal/version_utils.py b/b2sdk/_internal/version_utils.py index 2147a3562..582bad8de 100644 --- a/b2sdk/_internal/version_utils.py +++ b/b2sdk/_internal/version_utils.py @@ -194,3 +194,13 @@ def wrapper(*args, **kwargs): class rename_method(rename_function): WHAT = 'method' ALTERNATIVE_DECORATOR = 'discourage_method' + + +class FeaturePreviewWarning(FutureWarning): + """ + Feature Preview Warning + + Marks a feature, that is in "Feature Preview" state. + Such features are not yet fully stable and are subject to change or even outright removal. + Do not rely on them in production code. + """ diff --git a/b2sdk/_v3/__init__.py b/b2sdk/_v3/__init__.py index d374c0966..e03e37dca 100644 --- a/b2sdk/_v3/__init__.py +++ b/b2sdk/_v3/__init__.py @@ -59,7 +59,7 @@ def filter(self, record): # version & version utils from b2sdk.version import VERSION, USER_AGENT -from b2sdk._internal.version_utils import rename_argument, rename_function +from b2sdk._internal.version_utils import rename_argument, rename_function, FeaturePreviewWarning # utils diff --git a/changelog.d/+notification_rules.added.md b/changelog.d/+notification_rules.added.md index 2f2c3716c..d6329f332 100644 --- a/changelog.d/+notification_rules.added.md +++ b/changelog.d/+notification_rules.added.md @@ -1 +1,2 @@ -Add set&get notification rules methods to Bucket API. \ No newline at end of file +Add set&get notification rules methods to Bucket API as part of Event Notifications feature Private Preview. +See https://www.backblaze.com/blog/announcing-event-notifications/ for details. diff --git a/doc/source/index.rst b/doc/source/index.rst index 60413e511..f1f2f61fa 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,5 +1,7 @@ .. todolist:: +.. note:: **Event Notifications** feature is now in **Private Preview**. See https://www.backblaze.com/blog/announcing-event-notifications/ for details. + ######################################### Overview ######################################### diff --git a/test/integration/test_raw_api.py b/test/integration/test_raw_api.py index ab839b71f..1ed0faaab 100644 --- a/test/integration/test_raw_api.py +++ b/test/integration/test_raw_api.py @@ -581,11 +581,12 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): 'isEnabled': False, 'name': 'test-notification-rule', 'objectNamePrefix': 'test/object/prefix/', - 'targetConfiguration': { - 'targetType': 'webhook', - 'url': 'https://example.com/webhook', - 'hmacSha256SigningSecret': 'a' * 32, - }, + 'targetConfiguration': + { + 'targetType': 'webhook', + 'url': 'https://example.com/webhook', + 'hmacSha256SigningSecret': 'a' * 32, + }, } notification_rules_response_list = raw_api.set_bucket_notification_rules( @@ -602,8 +603,7 @@ def raw_api_test_helper(raw_api, should_cleanup_old_buckets): 'targetConfiguration': { **notification_rule['targetConfiguration'], - 'customHeaders': - None, + 'customHeaders': None, 'hmacSha256SigningSecret': 'a' * 32, } }